@hasna/economy 0.2.15 → 0.2.16
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/dist/cli/index.js +231 -22
- package/dist/db/database.d.ts +13 -0
- package/dist/db/database.d.ts.map +1 -1
- package/dist/index.js +76 -21
- package/dist/ingest/billing.d.ts +18 -0
- package/dist/ingest/billing.d.ts.map +1 -0
- package/dist/mcp/index.js +53 -21
- package/dist/server/index.js +53 -21
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -96,9 +96,9 @@ var init_pricing = __esm(() => {
|
|
|
96
96
|
"gemini-2.0-flash": { inputPer1M: 0.075, outputPer1M: 0.3, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
97
97
|
"gemini-1.5-pro": { inputPer1M: 1.25, outputPer1M: 5, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
98
98
|
"gemini-1.5-flash": { inputPer1M: 0.075, outputPer1M: 0.3, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
99
|
-
"gpt-5.4": { inputPer1M: 2.5, outputPer1M:
|
|
100
|
-
"gpt-5.4-pro": { inputPer1M:
|
|
101
|
-
"gpt-5.4-mini": { inputPer1M: 0.
|
|
99
|
+
"gpt-5.4": { inputPer1M: 2.5, outputPer1M: 15, cacheReadPer1M: 0.25, cacheWritePer1M: 0 },
|
|
100
|
+
"gpt-5.4-pro": { inputPer1M: 30, outputPer1M: 180, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
101
|
+
"gpt-5.4-mini": { inputPer1M: 0.75, outputPer1M: 4.5, cacheReadPer1M: 0.075, cacheWritePer1M: 0 },
|
|
102
102
|
"gpt-5.3-codex": { inputPer1M: 1.75, outputPer1M: 14, cacheReadPer1M: 0.44, cacheWritePer1M: 0 },
|
|
103
103
|
"gpt-5.3-chat": { inputPer1M: 2, outputPer1M: 8, cacheReadPer1M: 0.5, cacheWritePer1M: 0 },
|
|
104
104
|
"gpt-5.2-codex": { inputPer1M: 1.75, outputPer1M: 14, cacheReadPer1M: 0.44, cacheWritePer1M: 0 },
|
|
@@ -134,6 +134,7 @@ __export(exports_database, {
|
|
|
134
134
|
upsertModelPricing: () => upsertModelPricing,
|
|
135
135
|
upsertGoal: () => upsertGoal,
|
|
136
136
|
upsertBudget: () => upsertBudget,
|
|
137
|
+
upsertBillingDaily: () => upsertBillingDaily,
|
|
137
138
|
setIngestState: () => setIngestState,
|
|
138
139
|
seedModelPricing: () => seedModelPricing,
|
|
139
140
|
rollupSession: () => rollupSession,
|
|
@@ -144,6 +145,7 @@ __export(exports_database, {
|
|
|
144
145
|
queryProjectBreakdown: () => queryProjectBreakdown,
|
|
145
146
|
queryModelBreakdown: () => queryModelBreakdown,
|
|
146
147
|
queryDailyBreakdown: () => queryDailyBreakdown,
|
|
148
|
+
queryBillingSummary: () => queryBillingSummary,
|
|
147
149
|
openDatabase: () => openDatabase,
|
|
148
150
|
listProjects: () => listProjects,
|
|
149
151
|
listModelPricing: () => listModelPricing,
|
|
@@ -161,7 +163,8 @@ __export(exports_database, {
|
|
|
161
163
|
deleteProject: () => deleteProject,
|
|
162
164
|
deleteModelPricing: () => deleteModelPricing,
|
|
163
165
|
deleteGoal: () => deleteGoal,
|
|
164
|
-
deleteBudget: () => deleteBudget
|
|
166
|
+
deleteBudget: () => deleteBudget,
|
|
167
|
+
clearBillingRange: () => clearBillingRange
|
|
165
168
|
});
|
|
166
169
|
import { SqliteAdapter as Database } from "@hasna/cloud";
|
|
167
170
|
import { copyFileSync, existsSync, mkdirSync, readdirSync, statSync } from "fs";
|
|
@@ -309,6 +312,18 @@ function initSchema(db) {
|
|
|
309
312
|
machine_id TEXT,
|
|
310
313
|
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
311
314
|
);
|
|
315
|
+
|
|
316
|
+
CREATE TABLE IF NOT EXISTS billing_daily (
|
|
317
|
+
date TEXT NOT NULL,
|
|
318
|
+
provider TEXT NOT NULL,
|
|
319
|
+
description TEXT DEFAULT '',
|
|
320
|
+
cost_usd REAL NOT NULL DEFAULT 0,
|
|
321
|
+
updated_at TEXT NOT NULL,
|
|
322
|
+
PRIMARY KEY (date, provider, description)
|
|
323
|
+
);
|
|
324
|
+
|
|
325
|
+
CREATE INDEX IF NOT EXISTS idx_billing_date ON billing_daily(date);
|
|
326
|
+
CREATE INDEX IF NOT EXISTS idx_billing_provider ON billing_daily(provider);
|
|
312
327
|
`);
|
|
313
328
|
const cols = db.prepare(`PRAGMA table_info(requests)`).all();
|
|
314
329
|
if (!cols.some((c) => c.name === "machine_id")) {
|
|
@@ -327,11 +342,11 @@ function periodWhere(period) {
|
|
|
327
342
|
case "yesterday":
|
|
328
343
|
return `DATE(timestamp) = DATE('now', '-1 day')`;
|
|
329
344
|
case "week":
|
|
330
|
-
return `timestamp >= DATE('now', '-7 days')`;
|
|
345
|
+
return `timestamp >= DATE('now', 'weekday 0', '-7 days')`;
|
|
331
346
|
case "month":
|
|
332
|
-
return `timestamp >= DATE('now', '
|
|
347
|
+
return `timestamp >= DATE('now', 'start of month')`;
|
|
333
348
|
case "year":
|
|
334
|
-
return `timestamp >= DATE('now', '
|
|
349
|
+
return `timestamp >= DATE('now', 'start of year')`;
|
|
335
350
|
case "all":
|
|
336
351
|
return "1=1";
|
|
337
352
|
}
|
|
@@ -343,11 +358,11 @@ function sessionPeriodWhere(period) {
|
|
|
343
358
|
case "yesterday":
|
|
344
359
|
return `DATE(started_at) = DATE('now', '-1 day')`;
|
|
345
360
|
case "week":
|
|
346
|
-
return `started_at >= DATE('now', '-7 days')`;
|
|
361
|
+
return `started_at >= DATE('now', 'weekday 0', '-7 days')`;
|
|
347
362
|
case "month":
|
|
348
|
-
return `started_at >= DATE('now', '
|
|
363
|
+
return `started_at >= DATE('now', 'start of month')`;
|
|
349
364
|
case "year":
|
|
350
|
-
return `started_at >= DATE('now', '
|
|
365
|
+
return `started_at >= DATE('now', 'start of year')`;
|
|
351
366
|
case "all":
|
|
352
367
|
return "1=1";
|
|
353
368
|
}
|
|
@@ -459,19 +474,39 @@ function queryModelBreakdown(db) {
|
|
|
459
474
|
}
|
|
460
475
|
function queryProjectBreakdown(db) {
|
|
461
476
|
return db.prepare(`
|
|
477
|
+
WITH labeled AS (
|
|
478
|
+
SELECT
|
|
479
|
+
s.id,
|
|
480
|
+
s.project_path,
|
|
481
|
+
s.total_cost_usd,
|
|
482
|
+
s.started_at,
|
|
483
|
+
COALESCE(
|
|
484
|
+
NULLIF(s.project_name, ''),
|
|
485
|
+
CASE
|
|
486
|
+
WHEN s.project_path LIKE '%/%'
|
|
487
|
+
THEN substr(s.project_path, length(rtrim(s.project_path, replace(s.project_path, '/', ''))) + 1)
|
|
488
|
+
ELSE s.project_path
|
|
489
|
+
END
|
|
490
|
+
) as label
|
|
491
|
+
FROM sessions s
|
|
492
|
+
WHERE s.project_path != '' OR s.project_name != ''
|
|
493
|
+
)
|
|
462
494
|
SELECT
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
COUNT(DISTINCT
|
|
466
|
-
COUNT(r.id) as requests,
|
|
467
|
-
COALESCE(
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
495
|
+
MIN(l.project_path) as project_path,
|
|
496
|
+
l.label as project_name,
|
|
497
|
+
COUNT(DISTINCT l.id) as sessions,
|
|
498
|
+
COALESCE((SELECT COUNT(*) FROM requests r WHERE r.session_id IN (SELECT id FROM labeled WHERE label = l.label)), 0) as requests,
|
|
499
|
+
COALESCE(
|
|
500
|
+
(SELECT SUM(r.cost_usd) FROM requests r WHERE r.session_id IN (SELECT id FROM labeled WHERE label = l.label)),
|
|
501
|
+
SUM(l.total_cost_usd)
|
|
502
|
+
) as cost_usd,
|
|
503
|
+
COALESCE(
|
|
504
|
+
(SELECT SUM(r.input_tokens + r.output_tokens + r.cache_read_tokens + r.cache_create_tokens) FROM requests r WHERE r.session_id IN (SELECT id FROM labeled WHERE label = l.label)),
|
|
505
|
+
0
|
|
506
|
+
) as total_tokens,
|
|
507
|
+
MAX(l.started_at) as last_active
|
|
508
|
+
FROM labeled l
|
|
509
|
+
GROUP BY l.label
|
|
475
510
|
ORDER BY cost_usd DESC
|
|
476
511
|
`).all();
|
|
477
512
|
}
|
|
@@ -591,6 +626,26 @@ function setIngestState(db, source, key, value) {
|
|
|
591
626
|
function queryRequestsSince(db, since) {
|
|
592
627
|
return db.prepare(`SELECT * FROM requests WHERE timestamp > ? ORDER BY timestamp ASC`).all(since);
|
|
593
628
|
}
|
|
629
|
+
function upsertBillingDaily(db, row) {
|
|
630
|
+
db.prepare(`
|
|
631
|
+
INSERT OR REPLACE INTO billing_daily (date, provider, description, cost_usd, updated_at)
|
|
632
|
+
VALUES (?, ?, ?, ?, ?)
|
|
633
|
+
`).run(row.date, row.provider, row.description, row.cost_usd, row.updated_at);
|
|
634
|
+
}
|
|
635
|
+
function clearBillingRange(db, provider, fromDate, toDate) {
|
|
636
|
+
db.prepare(`DELETE FROM billing_daily WHERE provider = ? AND date >= ? AND date <= ?`).run(provider, fromDate, toDate);
|
|
637
|
+
}
|
|
638
|
+
function queryBillingSummary(db, period) {
|
|
639
|
+
const where = period === "today" ? `date = DATE('now')` : period === "yesterday" ? `date = DATE('now', '-1 day')` : period === "week" ? `date >= DATE('now', 'weekday 0', '-7 days')` : period === "month" ? `date >= DATE('now', 'start of month')` : period === "year" ? `date >= DATE('now', 'start of year')` : "1=1";
|
|
640
|
+
const rows = db.prepare(`SELECT provider, SUM(cost_usd) as cost FROM billing_daily WHERE ${where} GROUP BY provider`).all();
|
|
641
|
+
const by_provider = {};
|
|
642
|
+
let total = 0;
|
|
643
|
+
for (const r of rows) {
|
|
644
|
+
by_provider[r.provider] = r.cost;
|
|
645
|
+
total += r.cost;
|
|
646
|
+
}
|
|
647
|
+
return { total_usd: total, by_provider };
|
|
648
|
+
}
|
|
594
649
|
function listMachines(db) {
|
|
595
650
|
return db.prepare(`
|
|
596
651
|
SELECT
|
|
@@ -1971,6 +2026,119 @@ init_claude();
|
|
|
1971
2026
|
init_codex();
|
|
1972
2027
|
init_gemini();
|
|
1973
2028
|
|
|
2029
|
+
// src/ingest/billing.ts
|
|
2030
|
+
init_database();
|
|
2031
|
+
function getAnthropicAdminKey() {
|
|
2032
|
+
return process.env["HASNAXYZ_ANTHROPIC_LIVE_ADMIN_API_KEY"] ?? process.env["ANTHROPIC_ADMIN_API_KEY"] ?? null;
|
|
2033
|
+
}
|
|
2034
|
+
function getOpenAIAdminKey() {
|
|
2035
|
+
return process.env["HASNAXYZ_OPENAI_LIVE_ADMIN_API_KEY"] ?? process.env["OPENAI_ADMIN_API_KEY"] ?? null;
|
|
2036
|
+
}
|
|
2037
|
+
function toISODate(d) {
|
|
2038
|
+
return d.toISOString().substring(0, 10);
|
|
2039
|
+
}
|
|
2040
|
+
async function syncAnthropicBilling(db, opts = {}) {
|
|
2041
|
+
const key = getAnthropicAdminKey();
|
|
2042
|
+
if (!key)
|
|
2043
|
+
throw new Error("Missing Anthropic admin key (HASNAXYZ_ANTHROPIC_LIVE_ADMIN_API_KEY)");
|
|
2044
|
+
const now = new Date;
|
|
2045
|
+
const end = opts.toDate ? new Date(opts.toDate) : new Date(now.getTime() + 24 * 3600000);
|
|
2046
|
+
const days = opts.days ?? 31;
|
|
2047
|
+
const start = opts.fromDate ? new Date(opts.fromDate) : new Date(end.getTime() - days * 24 * 3600000);
|
|
2048
|
+
const startIso = start.toISOString().replace(/\.\d+/, "").replace(/:\d{2}Z$/, ":00Z");
|
|
2049
|
+
const endIso = end.toISOString().replace(/\.\d+/, "").replace(/:\d{2}Z$/, ":00Z");
|
|
2050
|
+
let totalUsd = 0;
|
|
2051
|
+
const buckets = [];
|
|
2052
|
+
let nextPage;
|
|
2053
|
+
do {
|
|
2054
|
+
const url = new URL("https://api.anthropic.com/v1/organizations/cost_report");
|
|
2055
|
+
url.searchParams.set("starting_at", startIso);
|
|
2056
|
+
url.searchParams.set("ending_at", endIso);
|
|
2057
|
+
url.searchParams.set("bucket_width", "1d");
|
|
2058
|
+
url.searchParams.set("limit", "31");
|
|
2059
|
+
url.searchParams.append("group_by[]", "description");
|
|
2060
|
+
if (nextPage)
|
|
2061
|
+
url.searchParams.set("page", nextPage);
|
|
2062
|
+
const res = await fetch(url.toString(), {
|
|
2063
|
+
headers: { "anthropic-version": "2023-06-01", "x-api-key": key }
|
|
2064
|
+
});
|
|
2065
|
+
const data = await res.json();
|
|
2066
|
+
if (data.error)
|
|
2067
|
+
throw new Error(`Anthropic API: ${data.error.message}`);
|
|
2068
|
+
if (data.data)
|
|
2069
|
+
buckets.push(...data.data);
|
|
2070
|
+
nextPage = data.has_more ? data.next_page : undefined;
|
|
2071
|
+
} while (nextPage);
|
|
2072
|
+
const fromDateStr = toISODate(start);
|
|
2073
|
+
const toDateStr = toISODate(new Date(end.getTime() - 1000));
|
|
2074
|
+
clearBillingRange(db, "anthropic", fromDateStr, toDateStr);
|
|
2075
|
+
const updatedAt = new Date().toISOString();
|
|
2076
|
+
for (const bucket of buckets) {
|
|
2077
|
+
const date = bucket.starting_at.substring(0, 10);
|
|
2078
|
+
for (const r of bucket.results) {
|
|
2079
|
+
const usd = Number(r.amount) / 100;
|
|
2080
|
+
if (usd === 0)
|
|
2081
|
+
continue;
|
|
2082
|
+
const desc = (r.description ?? "unknown").substring(0, 200);
|
|
2083
|
+
upsertBillingDaily(db, { date, provider: "anthropic", description: desc, cost_usd: usd, updated_at: updatedAt });
|
|
2084
|
+
totalUsd += usd;
|
|
2085
|
+
}
|
|
2086
|
+
}
|
|
2087
|
+
return { days: buckets.length, totalUsd };
|
|
2088
|
+
}
|
|
2089
|
+
async function syncOpenAIBilling(db, opts = {}) {
|
|
2090
|
+
const key = getOpenAIAdminKey();
|
|
2091
|
+
if (!key)
|
|
2092
|
+
throw new Error("Missing OpenAI admin key (HASNAXYZ_OPENAI_LIVE_ADMIN_API_KEY)");
|
|
2093
|
+
const now = new Date;
|
|
2094
|
+
const end = opts.toDate ? new Date(opts.toDate) : now;
|
|
2095
|
+
const days = opts.days ?? 31;
|
|
2096
|
+
const start = opts.fromDate ? new Date(opts.fromDate) : new Date(end.getTime() - days * 24 * 3600000);
|
|
2097
|
+
const startSec = Math.floor(start.getTime() / 1000);
|
|
2098
|
+
const endSec = Math.floor(end.getTime() / 1000);
|
|
2099
|
+
let totalUsd = 0;
|
|
2100
|
+
const buckets = [];
|
|
2101
|
+
let nextPage;
|
|
2102
|
+
do {
|
|
2103
|
+
const url = new URL("https://api.openai.com/v1/organization/costs");
|
|
2104
|
+
url.searchParams.set("start_time", String(startSec));
|
|
2105
|
+
url.searchParams.set("end_time", String(endSec));
|
|
2106
|
+
url.searchParams.set("bucket_width", "1d");
|
|
2107
|
+
url.searchParams.set("limit", "31");
|
|
2108
|
+
url.searchParams.append("group_by[]", "line_item");
|
|
2109
|
+
if (nextPage)
|
|
2110
|
+
url.searchParams.set("page", nextPage);
|
|
2111
|
+
const res = await fetch(url.toString(), {
|
|
2112
|
+
headers: { Authorization: `Bearer ${key}` }
|
|
2113
|
+
});
|
|
2114
|
+
const data = await res.json();
|
|
2115
|
+
if (data.error)
|
|
2116
|
+
throw new Error(`OpenAI API: ${data.error.message}`);
|
|
2117
|
+
if (data.data)
|
|
2118
|
+
buckets.push(...data.data);
|
|
2119
|
+
nextPage = data.has_more ? data.next_page : undefined;
|
|
2120
|
+
} while (nextPage);
|
|
2121
|
+
const fromDateStr = toISODate(start);
|
|
2122
|
+
const toDateStr = toISODate(new Date(end.getTime() - 1000));
|
|
2123
|
+
clearBillingRange(db, "openai", fromDateStr, toDateStr);
|
|
2124
|
+
const updatedAt = new Date().toISOString();
|
|
2125
|
+
for (const bucket of buckets) {
|
|
2126
|
+
const date = new Date(bucket.start_time * 1000).toISOString().substring(0, 10);
|
|
2127
|
+
for (const r of bucket.results) {
|
|
2128
|
+
const usd = Number(r.amount?.value ?? 0);
|
|
2129
|
+
if (usd === 0)
|
|
2130
|
+
continue;
|
|
2131
|
+
const desc = (r.line_item ?? "unknown").substring(0, 200);
|
|
2132
|
+
upsertBillingDaily(db, { date, provider: "openai", description: desc, cost_usd: usd, updated_at: updatedAt });
|
|
2133
|
+
totalUsd += usd;
|
|
2134
|
+
}
|
|
2135
|
+
}
|
|
2136
|
+
return { days: buckets.length, totalUsd };
|
|
2137
|
+
}
|
|
2138
|
+
|
|
2139
|
+
// src/cli/index.ts
|
|
2140
|
+
init_database();
|
|
2141
|
+
|
|
1974
2142
|
// src/lib/package-metadata.ts
|
|
1975
2143
|
import { readFileSync as readFileSync5 } from "fs";
|
|
1976
2144
|
var cachedMetadata = null;
|
|
@@ -3035,5 +3203,46 @@ cloudCmd.command("status").description("Check cloud connection status").action(a
|
|
|
3035
3203
|
}
|
|
3036
3204
|
console.log();
|
|
3037
3205
|
});
|
|
3206
|
+
var billingCmd = program.command("billing").description("Pull actual billing from provider admin APIs (ground truth)");
|
|
3207
|
+
billingCmd.command("sync").description("Sync actual billing from Anthropic and OpenAI admin APIs").option("--days <n>", "Days of history to fetch", "31").option("--anthropic", "Only sync Anthropic").option("--openai", "Only sync OpenAI").action(async (opts) => {
|
|
3208
|
+
const db = openDatabase();
|
|
3209
|
+
const days = Number(opts.days ?? 31);
|
|
3210
|
+
const doBoth = !opts.anthropic && !opts.openai;
|
|
3211
|
+
if (opts.anthropic || doBoth) {
|
|
3212
|
+
process.stdout.write(chalk4.cyan("\u2192 Syncing Anthropic billing... "));
|
|
3213
|
+
try {
|
|
3214
|
+
const r = await syncAnthropicBilling(db, { days });
|
|
3215
|
+
console.log(chalk4.green(`\u2713 ${r.days} days, $${r.totalUsd.toFixed(2)}`));
|
|
3216
|
+
} catch (e) {
|
|
3217
|
+
console.log(chalk4.red(`\u2717 ${e instanceof Error ? e.message : String(e)}`));
|
|
3218
|
+
}
|
|
3219
|
+
}
|
|
3220
|
+
if (opts.openai || doBoth) {
|
|
3221
|
+
process.stdout.write(chalk4.cyan("\u2192 Syncing OpenAI billing... "));
|
|
3222
|
+
try {
|
|
3223
|
+
const r = await syncOpenAIBilling(db, { days });
|
|
3224
|
+
console.log(chalk4.green(`\u2713 ${r.days} days, $${r.totalUsd.toFixed(2)}`));
|
|
3225
|
+
} catch (e) {
|
|
3226
|
+
console.log(chalk4.red(`\u2717 ${e instanceof Error ? e.message : String(e)}`));
|
|
3227
|
+
}
|
|
3228
|
+
}
|
|
3229
|
+
});
|
|
3230
|
+
billingCmd.command("show").description("Show actual billing totals vs our estimated costs").option("--period <p>", "Period: today|yesterday|week|month|year|all", "month").action((opts) => {
|
|
3231
|
+
const db = openDatabase();
|
|
3232
|
+
const period = opts.period ?? "month";
|
|
3233
|
+
const actual = queryBillingSummary(db, period);
|
|
3234
|
+
const estimated = querySummary(db, period);
|
|
3235
|
+
console.log();
|
|
3236
|
+
console.log(chalk4.bold.cyan(` Billing ${period} (actual from admin APIs)
|
|
3237
|
+
`));
|
|
3238
|
+
printTable(["Provider", "Actual (billed)"], Object.entries(actual.by_provider).map(([p, c]) => [chalk4.white(p), fmt2(c)]));
|
|
3239
|
+
console.log();
|
|
3240
|
+
console.log(` ${chalk4.bold("Actual total:")} ${fmt2(actual.total_usd)}`);
|
|
3241
|
+
console.log(` ${chalk4.dim("Our estimate:")} ${fmt2(estimated.total_usd)}`);
|
|
3242
|
+
const diff = estimated.total_usd - actual.total_usd;
|
|
3243
|
+
const pct = actual.total_usd > 0 ? diff / actual.total_usd * 100 : 0;
|
|
3244
|
+
console.log(` ${chalk4.dim("Difference:")} ${fmt2(Math.abs(diff))} (${diff >= 0 ? "+" : ""}${pct.toFixed(1)}%)`);
|
|
3245
|
+
console.log();
|
|
3246
|
+
});
|
|
3038
3247
|
registerBrainsCommand(program);
|
|
3039
3248
|
program.parse();
|
package/dist/db/database.d.ts
CHANGED
|
@@ -48,6 +48,19 @@ export declare function getGoalStatuses(db: Database): GoalStatus[];
|
|
|
48
48
|
export declare function getIngestState(db: Database, source: string, key: string): string | null;
|
|
49
49
|
export declare function setIngestState(db: Database, source: string, key: string, value: string): void;
|
|
50
50
|
export declare function queryRequestsSince(db: Database, since: string): EconomyRequest[];
|
|
51
|
+
export interface BillingDaily {
|
|
52
|
+
date: string;
|
|
53
|
+
provider: 'anthropic' | 'openai' | string;
|
|
54
|
+
description: string;
|
|
55
|
+
cost_usd: number;
|
|
56
|
+
updated_at: string;
|
|
57
|
+
}
|
|
58
|
+
export declare function upsertBillingDaily(db: Database, row: BillingDaily): void;
|
|
59
|
+
export declare function clearBillingRange(db: Database, provider: string, fromDate: string, toDate: string): void;
|
|
60
|
+
export declare function queryBillingSummary(db: Database, period: Period): {
|
|
61
|
+
total_usd: number;
|
|
62
|
+
by_provider: Record<string, number>;
|
|
63
|
+
};
|
|
51
64
|
export interface MachineInfo {
|
|
52
65
|
machine_id: string;
|
|
53
66
|
sessions: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../src/db/database.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,IAAI,QAAQ,EAAE,MAAM,cAAc,CAAA;AAKxD,OAAO,KAAK,EACV,cAAc,EACd,cAAc,EACd,cAAc,EACd,MAAM,EACN,YAAY,EACZ,WAAW,EACX,cAAc,EACd,gBAAgB,EAChB,MAAM,EACN,aAAa,EACd,MAAM,mBAAmB,CAAA;AAE1B,wBAAgB,YAAY,IAAI,MAAM,CAKrC;AAED,wBAAgB,UAAU,IAAI,MAAM,CAkBnC;AAED,wBAAgB,SAAS,IAAI,MAAM,CAIlC;AAED,wBAAgB,YAAY,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,QAAQ,UAAQ,GAAG,QAAQ,CAgBxE;
|
|
1
|
+
{"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../src/db/database.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -94,9 +94,9 @@ var init_pricing = __esm(() => {
|
|
|
94
94
|
"gemini-2.0-flash": { inputPer1M: 0.075, outputPer1M: 0.3, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
95
95
|
"gemini-1.5-pro": { inputPer1M: 1.25, outputPer1M: 5, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
96
96
|
"gemini-1.5-flash": { inputPer1M: 0.075, outputPer1M: 0.3, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
97
|
-
"gpt-5.4": { inputPer1M: 2.5, outputPer1M:
|
|
98
|
-
"gpt-5.4-pro": { inputPer1M:
|
|
99
|
-
"gpt-5.4-mini": { inputPer1M: 0.
|
|
97
|
+
"gpt-5.4": { inputPer1M: 2.5, outputPer1M: 15, cacheReadPer1M: 0.25, cacheWritePer1M: 0 },
|
|
98
|
+
"gpt-5.4-pro": { inputPer1M: 30, outputPer1M: 180, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
99
|
+
"gpt-5.4-mini": { inputPer1M: 0.75, outputPer1M: 4.5, cacheReadPer1M: 0.075, cacheWritePer1M: 0 },
|
|
100
100
|
"gpt-5.3-codex": { inputPer1M: 1.75, outputPer1M: 14, cacheReadPer1M: 0.44, cacheWritePer1M: 0 },
|
|
101
101
|
"gpt-5.3-chat": { inputPer1M: 2, outputPer1M: 8, cacheReadPer1M: 0.5, cacheWritePer1M: 0 },
|
|
102
102
|
"gpt-5.2-codex": { inputPer1M: 1.75, outputPer1M: 14, cacheReadPer1M: 0.44, cacheWritePer1M: 0 },
|
|
@@ -270,6 +270,18 @@ function initSchema(db) {
|
|
|
270
270
|
machine_id TEXT,
|
|
271
271
|
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
272
272
|
);
|
|
273
|
+
|
|
274
|
+
CREATE TABLE IF NOT EXISTS billing_daily (
|
|
275
|
+
date TEXT NOT NULL,
|
|
276
|
+
provider TEXT NOT NULL,
|
|
277
|
+
description TEXT DEFAULT '',
|
|
278
|
+
cost_usd REAL NOT NULL DEFAULT 0,
|
|
279
|
+
updated_at TEXT NOT NULL,
|
|
280
|
+
PRIMARY KEY (date, provider, description)
|
|
281
|
+
);
|
|
282
|
+
|
|
283
|
+
CREATE INDEX IF NOT EXISTS idx_billing_date ON billing_daily(date);
|
|
284
|
+
CREATE INDEX IF NOT EXISTS idx_billing_provider ON billing_daily(provider);
|
|
273
285
|
`);
|
|
274
286
|
const cols = db.prepare(`PRAGMA table_info(requests)`).all();
|
|
275
287
|
if (!cols.some((c) => c.name === "machine_id")) {
|
|
@@ -288,11 +300,11 @@ function periodWhere(period) {
|
|
|
288
300
|
case "yesterday":
|
|
289
301
|
return `DATE(timestamp) = DATE('now', '-1 day')`;
|
|
290
302
|
case "week":
|
|
291
|
-
return `timestamp >= DATE('now', '-7 days')`;
|
|
303
|
+
return `timestamp >= DATE('now', 'weekday 0', '-7 days')`;
|
|
292
304
|
case "month":
|
|
293
|
-
return `timestamp >= DATE('now', '
|
|
305
|
+
return `timestamp >= DATE('now', 'start of month')`;
|
|
294
306
|
case "year":
|
|
295
|
-
return `timestamp >= DATE('now', '
|
|
307
|
+
return `timestamp >= DATE('now', 'start of year')`;
|
|
296
308
|
case "all":
|
|
297
309
|
return "1=1";
|
|
298
310
|
}
|
|
@@ -304,11 +316,11 @@ function sessionPeriodWhere(period) {
|
|
|
304
316
|
case "yesterday":
|
|
305
317
|
return `DATE(started_at) = DATE('now', '-1 day')`;
|
|
306
318
|
case "week":
|
|
307
|
-
return `started_at >= DATE('now', '-7 days')`;
|
|
319
|
+
return `started_at >= DATE('now', 'weekday 0', '-7 days')`;
|
|
308
320
|
case "month":
|
|
309
|
-
return `started_at >= DATE('now', '
|
|
321
|
+
return `started_at >= DATE('now', 'start of month')`;
|
|
310
322
|
case "year":
|
|
311
|
-
return `started_at >= DATE('now', '
|
|
323
|
+
return `started_at >= DATE('now', 'start of year')`;
|
|
312
324
|
case "all":
|
|
313
325
|
return "1=1";
|
|
314
326
|
}
|
|
@@ -420,19 +432,39 @@ function queryModelBreakdown(db) {
|
|
|
420
432
|
}
|
|
421
433
|
function queryProjectBreakdown(db) {
|
|
422
434
|
return db.prepare(`
|
|
435
|
+
WITH labeled AS (
|
|
436
|
+
SELECT
|
|
437
|
+
s.id,
|
|
438
|
+
s.project_path,
|
|
439
|
+
s.total_cost_usd,
|
|
440
|
+
s.started_at,
|
|
441
|
+
COALESCE(
|
|
442
|
+
NULLIF(s.project_name, ''),
|
|
443
|
+
CASE
|
|
444
|
+
WHEN s.project_path LIKE '%/%'
|
|
445
|
+
THEN substr(s.project_path, length(rtrim(s.project_path, replace(s.project_path, '/', ''))) + 1)
|
|
446
|
+
ELSE s.project_path
|
|
447
|
+
END
|
|
448
|
+
) as label
|
|
449
|
+
FROM sessions s
|
|
450
|
+
WHERE s.project_path != '' OR s.project_name != ''
|
|
451
|
+
)
|
|
423
452
|
SELECT
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
COUNT(DISTINCT
|
|
427
|
-
COUNT(r.id) as requests,
|
|
428
|
-
COALESCE(
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
453
|
+
MIN(l.project_path) as project_path,
|
|
454
|
+
l.label as project_name,
|
|
455
|
+
COUNT(DISTINCT l.id) as sessions,
|
|
456
|
+
COALESCE((SELECT COUNT(*) FROM requests r WHERE r.session_id IN (SELECT id FROM labeled WHERE label = l.label)), 0) as requests,
|
|
457
|
+
COALESCE(
|
|
458
|
+
(SELECT SUM(r.cost_usd) FROM requests r WHERE r.session_id IN (SELECT id FROM labeled WHERE label = l.label)),
|
|
459
|
+
SUM(l.total_cost_usd)
|
|
460
|
+
) as cost_usd,
|
|
461
|
+
COALESCE(
|
|
462
|
+
(SELECT SUM(r.input_tokens + r.output_tokens + r.cache_read_tokens + r.cache_create_tokens) FROM requests r WHERE r.session_id IN (SELECT id FROM labeled WHERE label = l.label)),
|
|
463
|
+
0
|
|
464
|
+
) as total_tokens,
|
|
465
|
+
MAX(l.started_at) as last_active
|
|
466
|
+
FROM labeled l
|
|
467
|
+
GROUP BY l.label
|
|
436
468
|
ORDER BY cost_usd DESC
|
|
437
469
|
`).all();
|
|
438
470
|
}
|
|
@@ -552,6 +584,26 @@ function setIngestState(db, source, key, value) {
|
|
|
552
584
|
function queryRequestsSince(db, since) {
|
|
553
585
|
return db.prepare(`SELECT * FROM requests WHERE timestamp > ? ORDER BY timestamp ASC`).all(since);
|
|
554
586
|
}
|
|
587
|
+
function upsertBillingDaily(db, row) {
|
|
588
|
+
db.prepare(`
|
|
589
|
+
INSERT OR REPLACE INTO billing_daily (date, provider, description, cost_usd, updated_at)
|
|
590
|
+
VALUES (?, ?, ?, ?, ?)
|
|
591
|
+
`).run(row.date, row.provider, row.description, row.cost_usd, row.updated_at);
|
|
592
|
+
}
|
|
593
|
+
function clearBillingRange(db, provider, fromDate, toDate) {
|
|
594
|
+
db.prepare(`DELETE FROM billing_daily WHERE provider = ? AND date >= ? AND date <= ?`).run(provider, fromDate, toDate);
|
|
595
|
+
}
|
|
596
|
+
function queryBillingSummary(db, period) {
|
|
597
|
+
const where = period === "today" ? `date = DATE('now')` : period === "yesterday" ? `date = DATE('now', '-1 day')` : period === "week" ? `date >= DATE('now', 'weekday 0', '-7 days')` : period === "month" ? `date >= DATE('now', 'start of month')` : period === "year" ? `date >= DATE('now', 'start of year')` : "1=1";
|
|
598
|
+
const rows = db.prepare(`SELECT provider, SUM(cost_usd) as cost FROM billing_daily WHERE ${where} GROUP BY provider`).all();
|
|
599
|
+
const by_provider = {};
|
|
600
|
+
let total = 0;
|
|
601
|
+
for (const r of rows) {
|
|
602
|
+
by_provider[r.provider] = r.cost;
|
|
603
|
+
total += r.cost;
|
|
604
|
+
}
|
|
605
|
+
return { total_usd: total, by_provider };
|
|
606
|
+
}
|
|
555
607
|
function listMachines(db) {
|
|
556
608
|
return db.prepare(`
|
|
557
609
|
SELECT
|
|
@@ -1037,6 +1089,7 @@ export {
|
|
|
1037
1089
|
upsertModelPricing,
|
|
1038
1090
|
upsertGoal,
|
|
1039
1091
|
upsertBudget,
|
|
1092
|
+
upsertBillingDaily,
|
|
1040
1093
|
setIngestState,
|
|
1041
1094
|
setActiveModel,
|
|
1042
1095
|
seedModelPricing,
|
|
@@ -1049,6 +1102,7 @@ export {
|
|
|
1049
1102
|
queryProjectBreakdown,
|
|
1050
1103
|
queryModelBreakdown,
|
|
1051
1104
|
queryDailyBreakdown,
|
|
1105
|
+
queryBillingSummary,
|
|
1052
1106
|
openDatabase,
|
|
1053
1107
|
normalizeModelName,
|
|
1054
1108
|
listProjects,
|
|
@@ -1078,6 +1132,7 @@ export {
|
|
|
1078
1132
|
deleteBudget,
|
|
1079
1133
|
computeCostFromDb,
|
|
1080
1134
|
computeCost,
|
|
1135
|
+
clearBillingRange,
|
|
1081
1136
|
clearActiveModel,
|
|
1082
1137
|
DEFAULT_PRICING,
|
|
1083
1138
|
DEFAULT_MODEL
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { SqliteAdapter as Database } from '@hasna/cloud';
|
|
2
|
+
export declare function syncAnthropicBilling(db: Database, opts?: {
|
|
3
|
+
days?: number;
|
|
4
|
+
fromDate?: string;
|
|
5
|
+
toDate?: string;
|
|
6
|
+
}): Promise<{
|
|
7
|
+
days: number;
|
|
8
|
+
totalUsd: number;
|
|
9
|
+
}>;
|
|
10
|
+
export declare function syncOpenAIBilling(db: Database, opts?: {
|
|
11
|
+
days?: number;
|
|
12
|
+
fromDate?: string;
|
|
13
|
+
toDate?: string;
|
|
14
|
+
}): Promise<{
|
|
15
|
+
days: number;
|
|
16
|
+
totalUsd: number;
|
|
17
|
+
}>;
|
|
18
|
+
//# sourceMappingURL=billing.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"billing.d.ts","sourceRoot":"","sources":["../../src/ingest/billing.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,IAAI,QAAQ,EAAE,MAAM,cAAc,CAAA;AAgC7D,wBAAsB,oBAAoB,CACxC,EAAE,EAAE,QAAQ,EACZ,IAAI,GAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAO,GAC/D,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAuD7C;AAeD,wBAAsB,iBAAiB,CACrC,EAAE,EAAE,QAAQ,EACZ,IAAI,GAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAO,GAC/D,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAqD7C"}
|
package/dist/mcp/index.js
CHANGED
|
@@ -95,9 +95,9 @@ var init_pricing = __esm(() => {
|
|
|
95
95
|
"gemini-2.0-flash": { inputPer1M: 0.075, outputPer1M: 0.3, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
96
96
|
"gemini-1.5-pro": { inputPer1M: 1.25, outputPer1M: 5, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
97
97
|
"gemini-1.5-flash": { inputPer1M: 0.075, outputPer1M: 0.3, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
98
|
-
"gpt-5.4": { inputPer1M: 2.5, outputPer1M:
|
|
99
|
-
"gpt-5.4-pro": { inputPer1M:
|
|
100
|
-
"gpt-5.4-mini": { inputPer1M: 0.
|
|
98
|
+
"gpt-5.4": { inputPer1M: 2.5, outputPer1M: 15, cacheReadPer1M: 0.25, cacheWritePer1M: 0 },
|
|
99
|
+
"gpt-5.4-pro": { inputPer1M: 30, outputPer1M: 180, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
100
|
+
"gpt-5.4-mini": { inputPer1M: 0.75, outputPer1M: 4.5, cacheReadPer1M: 0.075, cacheWritePer1M: 0 },
|
|
101
101
|
"gpt-5.3-codex": { inputPer1M: 1.75, outputPer1M: 14, cacheReadPer1M: 0.44, cacheWritePer1M: 0 },
|
|
102
102
|
"gpt-5.3-chat": { inputPer1M: 2, outputPer1M: 8, cacheReadPer1M: 0.5, cacheWritePer1M: 0 },
|
|
103
103
|
"gpt-5.2-codex": { inputPer1M: 1.75, outputPer1M: 14, cacheReadPer1M: 0.44, cacheWritePer1M: 0 },
|
|
@@ -271,6 +271,18 @@ function initSchema(db) {
|
|
|
271
271
|
machine_id TEXT,
|
|
272
272
|
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
273
273
|
);
|
|
274
|
+
|
|
275
|
+
CREATE TABLE IF NOT EXISTS billing_daily (
|
|
276
|
+
date TEXT NOT NULL,
|
|
277
|
+
provider TEXT NOT NULL,
|
|
278
|
+
description TEXT DEFAULT '',
|
|
279
|
+
cost_usd REAL NOT NULL DEFAULT 0,
|
|
280
|
+
updated_at TEXT NOT NULL,
|
|
281
|
+
PRIMARY KEY (date, provider, description)
|
|
282
|
+
);
|
|
283
|
+
|
|
284
|
+
CREATE INDEX IF NOT EXISTS idx_billing_date ON billing_daily(date);
|
|
285
|
+
CREATE INDEX IF NOT EXISTS idx_billing_provider ON billing_daily(provider);
|
|
274
286
|
`);
|
|
275
287
|
const cols = db.prepare(`PRAGMA table_info(requests)`).all();
|
|
276
288
|
if (!cols.some((c) => c.name === "machine_id")) {
|
|
@@ -289,11 +301,11 @@ function periodWhere(period) {
|
|
|
289
301
|
case "yesterday":
|
|
290
302
|
return `DATE(timestamp) = DATE('now', '-1 day')`;
|
|
291
303
|
case "week":
|
|
292
|
-
return `timestamp >= DATE('now', '-7 days')`;
|
|
304
|
+
return `timestamp >= DATE('now', 'weekday 0', '-7 days')`;
|
|
293
305
|
case "month":
|
|
294
|
-
return `timestamp >= DATE('now', '
|
|
306
|
+
return `timestamp >= DATE('now', 'start of month')`;
|
|
295
307
|
case "year":
|
|
296
|
-
return `timestamp >= DATE('now', '
|
|
308
|
+
return `timestamp >= DATE('now', 'start of year')`;
|
|
297
309
|
case "all":
|
|
298
310
|
return "1=1";
|
|
299
311
|
}
|
|
@@ -305,11 +317,11 @@ function sessionPeriodWhere(period) {
|
|
|
305
317
|
case "yesterday":
|
|
306
318
|
return `DATE(started_at) = DATE('now', '-1 day')`;
|
|
307
319
|
case "week":
|
|
308
|
-
return `started_at >= DATE('now', '-7 days')`;
|
|
320
|
+
return `started_at >= DATE('now', 'weekday 0', '-7 days')`;
|
|
309
321
|
case "month":
|
|
310
|
-
return `started_at >= DATE('now', '
|
|
322
|
+
return `started_at >= DATE('now', 'start of month')`;
|
|
311
323
|
case "year":
|
|
312
|
-
return `started_at >= DATE('now', '
|
|
324
|
+
return `started_at >= DATE('now', 'start of year')`;
|
|
313
325
|
case "all":
|
|
314
326
|
return "1=1";
|
|
315
327
|
}
|
|
@@ -421,19 +433,39 @@ function queryModelBreakdown(db) {
|
|
|
421
433
|
}
|
|
422
434
|
function queryProjectBreakdown(db) {
|
|
423
435
|
return db.prepare(`
|
|
436
|
+
WITH labeled AS (
|
|
437
|
+
SELECT
|
|
438
|
+
s.id,
|
|
439
|
+
s.project_path,
|
|
440
|
+
s.total_cost_usd,
|
|
441
|
+
s.started_at,
|
|
442
|
+
COALESCE(
|
|
443
|
+
NULLIF(s.project_name, ''),
|
|
444
|
+
CASE
|
|
445
|
+
WHEN s.project_path LIKE '%/%'
|
|
446
|
+
THEN substr(s.project_path, length(rtrim(s.project_path, replace(s.project_path, '/', ''))) + 1)
|
|
447
|
+
ELSE s.project_path
|
|
448
|
+
END
|
|
449
|
+
) as label
|
|
450
|
+
FROM sessions s
|
|
451
|
+
WHERE s.project_path != '' OR s.project_name != ''
|
|
452
|
+
)
|
|
424
453
|
SELECT
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
COUNT(DISTINCT
|
|
428
|
-
COUNT(r.id) as requests,
|
|
429
|
-
COALESCE(
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
454
|
+
MIN(l.project_path) as project_path,
|
|
455
|
+
l.label as project_name,
|
|
456
|
+
COUNT(DISTINCT l.id) as sessions,
|
|
457
|
+
COALESCE((SELECT COUNT(*) FROM requests r WHERE r.session_id IN (SELECT id FROM labeled WHERE label = l.label)), 0) as requests,
|
|
458
|
+
COALESCE(
|
|
459
|
+
(SELECT SUM(r.cost_usd) FROM requests r WHERE r.session_id IN (SELECT id FROM labeled WHERE label = l.label)),
|
|
460
|
+
SUM(l.total_cost_usd)
|
|
461
|
+
) as cost_usd,
|
|
462
|
+
COALESCE(
|
|
463
|
+
(SELECT SUM(r.input_tokens + r.output_tokens + r.cache_read_tokens + r.cache_create_tokens) FROM requests r WHERE r.session_id IN (SELECT id FROM labeled WHERE label = l.label)),
|
|
464
|
+
0
|
|
465
|
+
) as total_tokens,
|
|
466
|
+
MAX(l.started_at) as last_active
|
|
467
|
+
FROM labeled l
|
|
468
|
+
GROUP BY l.label
|
|
437
469
|
ORDER BY cost_usd DESC
|
|
438
470
|
`).all();
|
|
439
471
|
}
|
package/dist/server/index.js
CHANGED
|
@@ -96,9 +96,9 @@ var init_pricing = __esm(() => {
|
|
|
96
96
|
"gemini-2.0-flash": { inputPer1M: 0.075, outputPer1M: 0.3, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
97
97
|
"gemini-1.5-pro": { inputPer1M: 1.25, outputPer1M: 5, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
98
98
|
"gemini-1.5-flash": { inputPer1M: 0.075, outputPer1M: 0.3, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
99
|
-
"gpt-5.4": { inputPer1M: 2.5, outputPer1M:
|
|
100
|
-
"gpt-5.4-pro": { inputPer1M:
|
|
101
|
-
"gpt-5.4-mini": { inputPer1M: 0.
|
|
99
|
+
"gpt-5.4": { inputPer1M: 2.5, outputPer1M: 15, cacheReadPer1M: 0.25, cacheWritePer1M: 0 },
|
|
100
|
+
"gpt-5.4-pro": { inputPer1M: 30, outputPer1M: 180, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
101
|
+
"gpt-5.4-mini": { inputPer1M: 0.75, outputPer1M: 4.5, cacheReadPer1M: 0.075, cacheWritePer1M: 0 },
|
|
102
102
|
"gpt-5.3-codex": { inputPer1M: 1.75, outputPer1M: 14, cacheReadPer1M: 0.44, cacheWritePer1M: 0 },
|
|
103
103
|
"gpt-5.3-chat": { inputPer1M: 2, outputPer1M: 8, cacheReadPer1M: 0.5, cacheWritePer1M: 0 },
|
|
104
104
|
"gpt-5.2-codex": { inputPer1M: 1.75, outputPer1M: 14, cacheReadPer1M: 0.44, cacheWritePer1M: 0 },
|
|
@@ -272,6 +272,18 @@ function initSchema(db) {
|
|
|
272
272
|
machine_id TEXT,
|
|
273
273
|
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
274
274
|
);
|
|
275
|
+
|
|
276
|
+
CREATE TABLE IF NOT EXISTS billing_daily (
|
|
277
|
+
date TEXT NOT NULL,
|
|
278
|
+
provider TEXT NOT NULL,
|
|
279
|
+
description TEXT DEFAULT '',
|
|
280
|
+
cost_usd REAL NOT NULL DEFAULT 0,
|
|
281
|
+
updated_at TEXT NOT NULL,
|
|
282
|
+
PRIMARY KEY (date, provider, description)
|
|
283
|
+
);
|
|
284
|
+
|
|
285
|
+
CREATE INDEX IF NOT EXISTS idx_billing_date ON billing_daily(date);
|
|
286
|
+
CREATE INDEX IF NOT EXISTS idx_billing_provider ON billing_daily(provider);
|
|
275
287
|
`);
|
|
276
288
|
const cols = db.prepare(`PRAGMA table_info(requests)`).all();
|
|
277
289
|
if (!cols.some((c) => c.name === "machine_id")) {
|
|
@@ -290,11 +302,11 @@ function periodWhere(period) {
|
|
|
290
302
|
case "yesterday":
|
|
291
303
|
return `DATE(timestamp) = DATE('now', '-1 day')`;
|
|
292
304
|
case "week":
|
|
293
|
-
return `timestamp >= DATE('now', '-7 days')`;
|
|
305
|
+
return `timestamp >= DATE('now', 'weekday 0', '-7 days')`;
|
|
294
306
|
case "month":
|
|
295
|
-
return `timestamp >= DATE('now', '
|
|
307
|
+
return `timestamp >= DATE('now', 'start of month')`;
|
|
296
308
|
case "year":
|
|
297
|
-
return `timestamp >= DATE('now', '
|
|
309
|
+
return `timestamp >= DATE('now', 'start of year')`;
|
|
298
310
|
case "all":
|
|
299
311
|
return "1=1";
|
|
300
312
|
}
|
|
@@ -306,11 +318,11 @@ function sessionPeriodWhere(period) {
|
|
|
306
318
|
case "yesterday":
|
|
307
319
|
return `DATE(started_at) = DATE('now', '-1 day')`;
|
|
308
320
|
case "week":
|
|
309
|
-
return `started_at >= DATE('now', '-7 days')`;
|
|
321
|
+
return `started_at >= DATE('now', 'weekday 0', '-7 days')`;
|
|
310
322
|
case "month":
|
|
311
|
-
return `started_at >= DATE('now', '
|
|
323
|
+
return `started_at >= DATE('now', 'start of month')`;
|
|
312
324
|
case "year":
|
|
313
|
-
return `started_at >= DATE('now', '
|
|
325
|
+
return `started_at >= DATE('now', 'start of year')`;
|
|
314
326
|
case "all":
|
|
315
327
|
return "1=1";
|
|
316
328
|
}
|
|
@@ -422,19 +434,39 @@ function queryModelBreakdown(db) {
|
|
|
422
434
|
}
|
|
423
435
|
function queryProjectBreakdown(db) {
|
|
424
436
|
return db.prepare(`
|
|
437
|
+
WITH labeled AS (
|
|
438
|
+
SELECT
|
|
439
|
+
s.id,
|
|
440
|
+
s.project_path,
|
|
441
|
+
s.total_cost_usd,
|
|
442
|
+
s.started_at,
|
|
443
|
+
COALESCE(
|
|
444
|
+
NULLIF(s.project_name, ''),
|
|
445
|
+
CASE
|
|
446
|
+
WHEN s.project_path LIKE '%/%'
|
|
447
|
+
THEN substr(s.project_path, length(rtrim(s.project_path, replace(s.project_path, '/', ''))) + 1)
|
|
448
|
+
ELSE s.project_path
|
|
449
|
+
END
|
|
450
|
+
) as label
|
|
451
|
+
FROM sessions s
|
|
452
|
+
WHERE s.project_path != '' OR s.project_name != ''
|
|
453
|
+
)
|
|
425
454
|
SELECT
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
COUNT(DISTINCT
|
|
429
|
-
COUNT(r.id) as requests,
|
|
430
|
-
COALESCE(
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
455
|
+
MIN(l.project_path) as project_path,
|
|
456
|
+
l.label as project_name,
|
|
457
|
+
COUNT(DISTINCT l.id) as sessions,
|
|
458
|
+
COALESCE((SELECT COUNT(*) FROM requests r WHERE r.session_id IN (SELECT id FROM labeled WHERE label = l.label)), 0) as requests,
|
|
459
|
+
COALESCE(
|
|
460
|
+
(SELECT SUM(r.cost_usd) FROM requests r WHERE r.session_id IN (SELECT id FROM labeled WHERE label = l.label)),
|
|
461
|
+
SUM(l.total_cost_usd)
|
|
462
|
+
) as cost_usd,
|
|
463
|
+
COALESCE(
|
|
464
|
+
(SELECT SUM(r.input_tokens + r.output_tokens + r.cache_read_tokens + r.cache_create_tokens) FROM requests r WHERE r.session_id IN (SELECT id FROM labeled WHERE label = l.label)),
|
|
465
|
+
0
|
|
466
|
+
) as total_tokens,
|
|
467
|
+
MAX(l.started_at) as last_active
|
|
468
|
+
FROM labeled l
|
|
469
|
+
GROUP BY l.label
|
|
438
470
|
ORDER BY cost_usd DESC
|
|
439
471
|
`).all();
|
|
440
472
|
}
|
package/package.json
CHANGED