@hasna/economy 0.2.11 → 0.2.12
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 +102 -19
- package/dist/db/database.d.ts +10 -1
- package/dist/db/database.d.ts.map +1 -1
- package/dist/db/pg-migrations.d.ts.map +1 -1
- package/dist/index.js +62 -15
- package/dist/ingest/claude.d.ts.map +1 -1
- package/dist/ingest/codex.d.ts.map +1 -1
- package/dist/ingest/gemini.d.ts.map +1 -1
- package/dist/mcp/index.js +89 -24
- package/dist/server/index.js +70 -17
- package/dist/server/serve.d.ts.map +1 -1
- package/dist/types/index.d.ts +3 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -109,8 +109,17 @@ var init_pricing = __esm(() => {
|
|
|
109
109
|
// src/db/database.ts
|
|
110
110
|
import { SqliteAdapter as Database } from "@hasna/cloud";
|
|
111
111
|
import { copyFileSync, existsSync, mkdirSync, readdirSync, statSync } from "fs";
|
|
112
|
+
import { hostname } from "os";
|
|
112
113
|
import { homedir } from "os";
|
|
113
114
|
import { join } from "path";
|
|
115
|
+
function getMachineId() {
|
|
116
|
+
if (process.env["ECONOMY_MACHINE_ID"])
|
|
117
|
+
return process.env["ECONOMY_MACHINE_ID"];
|
|
118
|
+
const h = hostname().toLowerCase();
|
|
119
|
+
if (h.startsWith("spark") || h.startsWith("apple"))
|
|
120
|
+
return h.split(".")[0];
|
|
121
|
+
return h.split(".")[0];
|
|
122
|
+
}
|
|
114
123
|
function getDataDir() {
|
|
115
124
|
const home = process.env["HOME"] || process.env["USERPROFILE"] || homedir();
|
|
116
125
|
const newDir = join(home, ".hasna", "economy");
|
|
@@ -143,6 +152,7 @@ function openDatabase(dbPath, skipSeed = false) {
|
|
|
143
152
|
}
|
|
144
153
|
const db = new Database(path);
|
|
145
154
|
db.exec("PRAGMA journal_mode = WAL");
|
|
155
|
+
db.exec("PRAGMA busy_timeout = 5000");
|
|
146
156
|
db.exec("PRAGMA foreign_keys = ON");
|
|
147
157
|
initSchema(db);
|
|
148
158
|
if (!skipSeed) {
|
|
@@ -164,7 +174,8 @@ function initSchema(db) {
|
|
|
164
174
|
cost_usd REAL NOT NULL DEFAULT 0,
|
|
165
175
|
duration_ms INTEGER DEFAULT 0,
|
|
166
176
|
timestamp TEXT NOT NULL,
|
|
167
|
-
source_request_id TEXT
|
|
177
|
+
source_request_id TEXT,
|
|
178
|
+
machine_id TEXT DEFAULT ''
|
|
168
179
|
);
|
|
169
180
|
|
|
170
181
|
CREATE TABLE IF NOT EXISTS sessions (
|
|
@@ -176,7 +187,8 @@ function initSchema(db) {
|
|
|
176
187
|
ended_at TEXT,
|
|
177
188
|
total_cost_usd REAL DEFAULT 0,
|
|
178
189
|
total_tokens INTEGER DEFAULT 0,
|
|
179
|
-
request_count INTEGER DEFAULT 0
|
|
190
|
+
request_count INTEGER DEFAULT 0,
|
|
191
|
+
machine_id TEXT DEFAULT ''
|
|
180
192
|
);
|
|
181
193
|
|
|
182
194
|
CREATE TABLE IF NOT EXISTS projects (
|
|
@@ -242,6 +254,15 @@ function initSchema(db) {
|
|
|
242
254
|
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
243
255
|
);
|
|
244
256
|
`);
|
|
257
|
+
const cols = db.prepare(`PRAGMA table_info(requests)`).all();
|
|
258
|
+
if (!cols.some((c) => c.name === "machine_id")) {
|
|
259
|
+
db.exec(`ALTER TABLE requests ADD COLUMN machine_id TEXT DEFAULT ''`);
|
|
260
|
+
db.exec(`ALTER TABLE sessions ADD COLUMN machine_id TEXT DEFAULT ''`);
|
|
261
|
+
}
|
|
262
|
+
db.exec(`
|
|
263
|
+
CREATE INDEX IF NOT EXISTS idx_requests_machine ON requests(machine_id);
|
|
264
|
+
CREATE INDEX IF NOT EXISTS idx_sessions_machine ON sessions(machine_id);
|
|
265
|
+
`);
|
|
245
266
|
}
|
|
246
267
|
function periodWhere(period) {
|
|
247
268
|
switch (period) {
|
|
@@ -280,17 +301,17 @@ function upsertRequest(db, req) {
|
|
|
280
301
|
INSERT OR REPLACE INTO requests
|
|
281
302
|
(id, agent, session_id, model, input_tokens, output_tokens,
|
|
282
303
|
cache_read_tokens, cache_create_tokens, cost_usd, duration_ms,
|
|
283
|
-
timestamp, source_request_id)
|
|
284
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
285
|
-
`).run(req.id, req.agent, req.session_id, req.model, req.input_tokens, req.output_tokens, req.cache_read_tokens, req.cache_create_tokens, req.cost_usd, req.duration_ms, req.timestamp, req.source_request_id);
|
|
304
|
+
timestamp, source_request_id, machine_id)
|
|
305
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
306
|
+
`).run(req.id, req.agent, req.session_id, req.model, req.input_tokens, req.output_tokens, req.cache_read_tokens, req.cache_create_tokens, req.cost_usd, req.duration_ms, req.timestamp, req.source_request_id, req.machine_id ?? "");
|
|
286
307
|
}
|
|
287
308
|
function upsertSession(db, session) {
|
|
288
309
|
db.prepare(`
|
|
289
310
|
INSERT OR REPLACE INTO sessions
|
|
290
311
|
(id, agent, project_path, project_name, started_at, ended_at,
|
|
291
|
-
total_cost_usd, total_tokens, request_count)
|
|
292
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
293
|
-
`).run(session.id, session.agent, session.project_path, session.project_name, session.started_at, session.ended_at ?? null, session.total_cost_usd, session.total_tokens, session.request_count);
|
|
312
|
+
total_cost_usd, total_tokens, request_count, machine_id)
|
|
313
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
314
|
+
`).run(session.id, session.agent, session.project_path, session.project_name, session.started_at, session.ended_at ?? null, session.total_cost_usd, session.total_tokens, session.request_count, session.machine_id ?? "");
|
|
294
315
|
}
|
|
295
316
|
function rollupSession(db, sessionId) {
|
|
296
317
|
db.prepare(`
|
|
@@ -320,6 +341,10 @@ function querySessions(db, filter = {}) {
|
|
|
320
341
|
conditions.push("started_at >= ?");
|
|
321
342
|
params.push(filter.since);
|
|
322
343
|
}
|
|
344
|
+
if (filter.machine) {
|
|
345
|
+
conditions.push("machine_id = ?");
|
|
346
|
+
params.push(filter.machine);
|
|
347
|
+
}
|
|
323
348
|
if (filter.search) {
|
|
324
349
|
const q = `%${filter.search}%`;
|
|
325
350
|
conditions.push("(project_name LIKE ? OR agent LIKE ? OR id LIKE ?)");
|
|
@@ -338,24 +363,25 @@ function queryTopSessions(db, n = 10, agent) {
|
|
|
338
363
|
}
|
|
339
364
|
return db.prepare(`SELECT * FROM sessions ORDER BY total_cost_usd DESC LIMIT ?`).all(n);
|
|
340
365
|
}
|
|
341
|
-
function querySummary(db, period) {
|
|
366
|
+
function querySummary(db, period, machine) {
|
|
342
367
|
const rWhere = periodWhere(period);
|
|
343
368
|
const sWhere = sessionPeriodWhere(period);
|
|
369
|
+
const machineClause = machine ? ` AND machine_id = '${machine.replace(/'/g, "''")}'` : "";
|
|
344
370
|
const r = db.prepare(`
|
|
345
371
|
SELECT COALESCE(SUM(cost_usd), 0) as total_usd,
|
|
346
372
|
COUNT(*) as requests,
|
|
347
373
|
COALESCE(SUM(input_tokens + output_tokens + cache_read_tokens + cache_create_tokens), 0) as tokens
|
|
348
|
-
FROM requests WHERE ${rWhere}
|
|
374
|
+
FROM requests WHERE ${rWhere}${machineClause}
|
|
349
375
|
`).get();
|
|
350
376
|
const codexTotals = db.prepare(`
|
|
351
377
|
SELECT COALESCE(SUM(total_cost_usd), 0) as cost_usd,
|
|
352
378
|
COALESCE(SUM(total_tokens), 0) as tokens,
|
|
353
379
|
COUNT(*) as sessions
|
|
354
380
|
FROM sessions
|
|
355
|
-
WHERE ${sWhere}
|
|
381
|
+
WHERE ${sWhere}${machineClause}
|
|
356
382
|
AND id NOT IN (SELECT DISTINCT session_id FROM requests)
|
|
357
383
|
`).get();
|
|
358
|
-
const sessionCount = db.prepare(`SELECT COUNT(*) as sessions FROM sessions WHERE ${sWhere}`).get();
|
|
384
|
+
const sessionCount = db.prepare(`SELECT COUNT(*) as sessions FROM sessions WHERE ${sWhere}${machineClause}`).get();
|
|
359
385
|
return {
|
|
360
386
|
total_usd: r.total_usd + codexTotals.cost_usd,
|
|
361
387
|
requests: r.requests,
|
|
@@ -509,6 +535,20 @@ function setIngestState(db, source, key, value) {
|
|
|
509
535
|
function queryRequestsSince(db, since) {
|
|
510
536
|
return db.prepare(`SELECT * FROM requests WHERE timestamp > ? ORDER BY timestamp ASC`).all(since);
|
|
511
537
|
}
|
|
538
|
+
function listMachines(db) {
|
|
539
|
+
return db.prepare(`
|
|
540
|
+
SELECT
|
|
541
|
+
s.machine_id,
|
|
542
|
+
COUNT(DISTINCT s.id) as sessions,
|
|
543
|
+
COALESCE((SELECT COUNT(*) FROM requests r WHERE r.machine_id = s.machine_id), 0) as requests,
|
|
544
|
+
COALESCE(SUM(s.total_cost_usd), 0) as total_cost_usd,
|
|
545
|
+
MAX(s.started_at) as last_active
|
|
546
|
+
FROM sessions s
|
|
547
|
+
WHERE s.machine_id != ''
|
|
548
|
+
GROUP BY s.machine_id
|
|
549
|
+
ORDER BY total_cost_usd DESC
|
|
550
|
+
`).all();
|
|
551
|
+
}
|
|
512
552
|
function upsertModelPricing(db, p) {
|
|
513
553
|
db.prepare(`
|
|
514
554
|
INSERT OR REPLACE INTO model_pricing
|
|
@@ -574,6 +614,7 @@ async function ingestClaude(db, verbose = false, _telemetryDir) {
|
|
|
574
614
|
console.log("Claude projects dir not found:", PROJECTS_DIR);
|
|
575
615
|
return { files: 0, requests: 0, sessions: 0 };
|
|
576
616
|
}
|
|
617
|
+
const machineId = getMachineId();
|
|
577
618
|
let totalFiles = 0;
|
|
578
619
|
let totalRequests = 0;
|
|
579
620
|
const touchedSessions = new Set;
|
|
@@ -645,7 +686,8 @@ async function ingestClaude(db, verbose = false, _telemetryDir) {
|
|
|
645
686
|
cost_usd: costUsd,
|
|
646
687
|
duration_ms: 0,
|
|
647
688
|
timestamp,
|
|
648
|
-
source_request_id: reqId
|
|
689
|
+
source_request_id: reqId,
|
|
690
|
+
machine_id: machineId
|
|
649
691
|
});
|
|
650
692
|
if (!touchedSessions.has(sessionId)) {
|
|
651
693
|
const existing = db.prepare(`SELECT id FROM sessions WHERE id = ?`).get(sessionId);
|
|
@@ -661,7 +703,8 @@ async function ingestClaude(db, verbose = false, _telemetryDir) {
|
|
|
661
703
|
ended_at: null,
|
|
662
704
|
total_cost_usd: 0,
|
|
663
705
|
total_tokens: 0,
|
|
664
|
-
request_count: 0
|
|
706
|
+
request_count: 0,
|
|
707
|
+
machine_id: machineId
|
|
665
708
|
};
|
|
666
709
|
upsertSession(db, session);
|
|
667
710
|
}
|
|
@@ -696,6 +739,7 @@ async function ingestCodex(db, verbose = false) {
|
|
|
696
739
|
console.log("Codex DB not found:", CODEX_DB_PATH);
|
|
697
740
|
return { sessions: 0 };
|
|
698
741
|
}
|
|
742
|
+
const machineId = getMachineId();
|
|
699
743
|
let codexDb = null;
|
|
700
744
|
let ingested = 0;
|
|
701
745
|
try {
|
|
@@ -720,7 +764,8 @@ async function ingestCodex(db, verbose = false) {
|
|
|
720
764
|
ended_at: endedAt,
|
|
721
765
|
total_cost_usd: costUsd,
|
|
722
766
|
total_tokens: thread.tokens_used,
|
|
723
|
-
request_count: 1
|
|
767
|
+
request_count: 1,
|
|
768
|
+
machine_id: machineId
|
|
724
769
|
});
|
|
725
770
|
setIngestState(db, "codex", stateKey, "done");
|
|
726
771
|
ingested++;
|
|
@@ -749,6 +794,7 @@ async function ingestGemini(db, verbose) {
|
|
|
749
794
|
console.log("Gemini tmp dir not found:", GEMINI_TMP_DIR);
|
|
750
795
|
return { sessions: 0 };
|
|
751
796
|
}
|
|
797
|
+
const machineId = getMachineId();
|
|
752
798
|
let totalSessions = 0;
|
|
753
799
|
const touchedSessions = new Set;
|
|
754
800
|
let projectHashDirs = [];
|
|
@@ -799,7 +845,8 @@ async function ingestGemini(db, verbose) {
|
|
|
799
845
|
ended_at: chatData.lastUpdated ?? null,
|
|
800
846
|
total_cost_usd: 0,
|
|
801
847
|
total_tokens: 0,
|
|
802
|
-
request_count: 0
|
|
848
|
+
request_count: 0,
|
|
849
|
+
machine_id: machineId
|
|
803
850
|
};
|
|
804
851
|
upsertSession(db, session);
|
|
805
852
|
touchedSessions.add(sessionId);
|
|
@@ -1072,7 +1119,11 @@ function createHandler(db) {
|
|
|
1072
1119
|
return ok({ status: "ok", ts: new Date().toISOString() });
|
|
1073
1120
|
if (path === "/api/summary" && method === "GET") {
|
|
1074
1121
|
const period = url.searchParams.get("period") ?? "today";
|
|
1075
|
-
|
|
1122
|
+
const machine = url.searchParams.get("machine") ?? undefined;
|
|
1123
|
+
return ok(querySummary(db, period, machine));
|
|
1124
|
+
}
|
|
1125
|
+
if (path === "/api/machines" && method === "GET") {
|
|
1126
|
+
return ok(listMachines(db), { current_machine: getMachineId() });
|
|
1076
1127
|
}
|
|
1077
1128
|
if (path === "/api/daily" && method === "GET") {
|
|
1078
1129
|
const days = Number(url.searchParams.get("days") ?? 30);
|
|
@@ -1082,6 +1133,7 @@ function createHandler(db) {
|
|
|
1082
1133
|
const agent = url.searchParams.get("agent");
|
|
1083
1134
|
const project = url.searchParams.get("project") ?? undefined;
|
|
1084
1135
|
const search = url.searchParams.get("search") ?? undefined;
|
|
1136
|
+
const machine = url.searchParams.get("machine") ?? undefined;
|
|
1085
1137
|
const limit = Number(url.searchParams.get("limit") ?? 50);
|
|
1086
1138
|
const offset = Number(url.searchParams.get("offset") ?? 0);
|
|
1087
1139
|
const since = url.searchParams.get("since") ?? undefined;
|
|
@@ -1091,6 +1143,7 @@ function createHandler(db) {
|
|
|
1091
1143
|
agent: agent ?? undefined,
|
|
1092
1144
|
project,
|
|
1093
1145
|
search,
|
|
1146
|
+
machine,
|
|
1094
1147
|
limit,
|
|
1095
1148
|
offset,
|
|
1096
1149
|
since
|
|
@@ -1895,7 +1948,7 @@ program.action(async () => {
|
|
|
1895
1948
|
}
|
|
1896
1949
|
console.log();
|
|
1897
1950
|
});
|
|
1898
|
-
program.command("sync").description("Ingest cost data from Claude Code, Codex, and Gemini").option("--claude", "Only ingest Claude Code telemetry").option("--codex", "Only ingest Codex sessions").option("--gemini", "Only ingest Gemini CLI sessions").option("-v, --verbose", "Verbose output").option("--force", "Force re-process all files (ignore mtime cache)").action(async (opts) => {
|
|
1951
|
+
program.command("sync").description("Ingest cost data from Claude Code, Codex, and Gemini").option("--claude", "Only ingest Claude Code telemetry").option("--codex", "Only ingest Codex sessions").option("--gemini", "Only ingest Gemini CLI sessions").option("-v, --verbose", "Verbose output").option("--force", "Force re-process all files (ignore mtime cache)").option("--backfill-machine", "Tag existing records that have no machine_id with current hostname").action(async (opts) => {
|
|
1899
1952
|
const db = openDatabase();
|
|
1900
1953
|
ensurePricingSeeded(db);
|
|
1901
1954
|
if (opts.force) {
|
|
@@ -1922,6 +1975,12 @@ program.command("sync").description("Ingest cost data from Claude Code, Codex, a
|
|
|
1922
1975
|
const r = await ingestGemini(db, opts.verbose);
|
|
1923
1976
|
console.log(chalk4.green(`\u2713 ${r.sessions} sessions`));
|
|
1924
1977
|
}
|
|
1978
|
+
if (opts.backfillMachine) {
|
|
1979
|
+
const machine = getMachineId();
|
|
1980
|
+
const reqCount = db.prepare(`UPDATE requests SET machine_id = ? WHERE machine_id = '' OR machine_id IS NULL`).run(machine);
|
|
1981
|
+
const sessCount = db.prepare(`UPDATE sessions SET machine_id = ? WHERE machine_id = '' OR machine_id IS NULL`).run(machine);
|
|
1982
|
+
console.log(chalk4.cyan(`\u2192 Backfilled machine_id='${machine}': ${reqCount.changes} requests, ${sessCount.changes} sessions`));
|
|
1983
|
+
}
|
|
1925
1984
|
try {
|
|
1926
1985
|
const { checkAndFireWebhooks: checkAndFireWebhooks2 } = await Promise.resolve().then(() => (init_webhooks(), exports_webhooks));
|
|
1927
1986
|
await checkAndFireWebhooks2(db);
|
|
@@ -1941,13 +2000,14 @@ program.command("month").description("Cost summary for this month").action(async
|
|
|
1941
2000
|
await autoSync();
|
|
1942
2001
|
printSummary("This Month", "month");
|
|
1943
2002
|
});
|
|
1944
|
-
program.command("sessions").description("List coding sessions with costs").option("--agent <agent>", "Filter by agent (claude|codex)").option("--project <path>", "Filter by project path").option("--limit <n>", "Number of sessions", "20").option("--format <fmt>", "Output format: table|compact|csv|json", "table").option("--since <date>", "Filter sessions since date or relative (e.g. 2026-03-01, 7d, 30d)").option("--search <query>", "Search by project name, session id prefix, or agent").action(async (opts) => {
|
|
2003
|
+
program.command("sessions").description("List coding sessions with costs").option("--agent <agent>", "Filter by agent (claude|codex)").option("--project <path>", "Filter by project path").option("--machine <id>", "Filter by machine hostname (e.g. spark01, apple01)").option("--limit <n>", "Number of sessions", "20").option("--format <fmt>", "Output format: table|compact|csv|json", "table").option("--since <date>", "Filter sessions since date or relative (e.g. 2026-03-01, 7d, 30d)").option("--search <query>", "Search by project name, session id prefix, or agent").action(async (opts) => {
|
|
1945
2004
|
await autoSync();
|
|
1946
2005
|
const db = openDatabase();
|
|
1947
2006
|
const sinceDate = opts.since ? parseSinceDate(opts.since) : undefined;
|
|
1948
2007
|
let sessions = querySessions(db, {
|
|
1949
2008
|
agent: opts.agent,
|
|
1950
2009
|
project: opts.project,
|
|
2010
|
+
machine: opts.machine,
|
|
1951
2011
|
limit: Number(opts.limit ?? 20),
|
|
1952
2012
|
since: sinceDate,
|
|
1953
2013
|
search: opts.search
|
|
@@ -2396,6 +2456,29 @@ program.command("session <id>").description("Show detailed breakdown of a single
|
|
|
2396
2456
|
}
|
|
2397
2457
|
console.log();
|
|
2398
2458
|
});
|
|
2459
|
+
program.command("machines").description("List all machines that have synced data").action(async () => {
|
|
2460
|
+
await autoSync();
|
|
2461
|
+
const db = openDatabase();
|
|
2462
|
+
const machines = listMachines(db);
|
|
2463
|
+
const current = getMachineId();
|
|
2464
|
+
if (machines.length === 0) {
|
|
2465
|
+
console.log(chalk4.yellow(`No machine data yet. Current machine: ${current}`));
|
|
2466
|
+
return;
|
|
2467
|
+
}
|
|
2468
|
+
console.log();
|
|
2469
|
+
console.log(chalk4.bold.cyan(" Machines"));
|
|
2470
|
+
console.log();
|
|
2471
|
+
printTable(["Machine", "Sessions", "Requests", "Cost", "Last Active"], machines.map((m) => [
|
|
2472
|
+
m.machine_id === current ? chalk4.green(`${m.machine_id} (this)`) : chalk4.white(m.machine_id),
|
|
2473
|
+
fmtCount(m.sessions),
|
|
2474
|
+
fmtCount(m.requests),
|
|
2475
|
+
fmt2(m.total_cost_usd),
|
|
2476
|
+
chalk4.dim(m.last_active?.substring(0, 16) ?? "\u2014")
|
|
2477
|
+
]));
|
|
2478
|
+
console.log(`
|
|
2479
|
+
${chalk4.dim("Current machine:")} ${chalk4.bold(current)}`);
|
|
2480
|
+
console.log();
|
|
2481
|
+
});
|
|
2399
2482
|
program.command("export").description("Export data as CSV").option("--type <type>", "Data type: sessions or requests", "sessions").option("--period <period>", "Period: today|week|month|all", "month").option("--output <file>", "Output file path (default: stdout)").action(async (opts) => {
|
|
2400
2483
|
await autoSync();
|
|
2401
2484
|
const db = openDatabase();
|
package/dist/db/database.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { SqliteAdapter as Database } from '@hasna/cloud';
|
|
2
2
|
import type { EconomyRequest, EconomySession, EconomyProject, Budget, BudgetStatus, CostSummary, ModelBreakdown, ProjectBreakdown, Period, SessionFilter } from '../types/index.js';
|
|
3
|
+
export declare function getMachineId(): string;
|
|
3
4
|
export declare function getDataDir(): string;
|
|
4
5
|
export declare function getDbPath(): string;
|
|
5
6
|
export declare function openDatabase(dbPath?: string, skipSeed?: boolean): Database;
|
|
@@ -8,7 +9,7 @@ export declare function upsertSession(db: Database, session: EconomySession): vo
|
|
|
8
9
|
export declare function rollupSession(db: Database, sessionId: string): void;
|
|
9
10
|
export declare function querySessions(db: Database, filter?: SessionFilter): EconomySession[];
|
|
10
11
|
export declare function queryTopSessions(db: Database, n?: number, agent?: string): EconomySession[];
|
|
11
|
-
export declare function querySummary(db: Database, period: Period): CostSummary;
|
|
12
|
+
export declare function querySummary(db: Database, period: Period, machine?: string): CostSummary;
|
|
12
13
|
export declare function queryModelBreakdown(db: Database): ModelBreakdown[];
|
|
13
14
|
export declare function queryProjectBreakdown(db: Database): ProjectBreakdown[];
|
|
14
15
|
export declare function queryDailyBreakdown(db: Database, days?: number): Array<{
|
|
@@ -47,6 +48,14 @@ export declare function getGoalStatuses(db: Database): GoalStatus[];
|
|
|
47
48
|
export declare function getIngestState(db: Database, source: string, key: string): string | null;
|
|
48
49
|
export declare function setIngestState(db: Database, source: string, key: string, value: string): void;
|
|
49
50
|
export declare function queryRequestsSince(db: Database, since: string): EconomyRequest[];
|
|
51
|
+
export interface MachineInfo {
|
|
52
|
+
machine_id: string;
|
|
53
|
+
sessions: number;
|
|
54
|
+
requests: number;
|
|
55
|
+
total_cost_usd: number;
|
|
56
|
+
last_active: string;
|
|
57
|
+
}
|
|
58
|
+
export declare function listMachines(db: Database): MachineInfo[];
|
|
50
59
|
export interface DbModelPricing {
|
|
51
60
|
model: string;
|
|
52
61
|
input_per_1m: 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;
|
|
1
|
+
{"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../src/db/database.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pg-migrations.d.ts","sourceRoot":"","sources":["../../src/db/pg-migrations.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,eAAO,MAAM,aAAa,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"pg-migrations.d.ts","sourceRoot":"","sources":["../../src/db/pg-migrations.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,eAAO,MAAM,aAAa,EAAE,MAAM,EAuGjC,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -107,8 +107,17 @@ var init_pricing = __esm(() => {
|
|
|
107
107
|
// src/db/database.ts
|
|
108
108
|
import { SqliteAdapter as Database } from "@hasna/cloud";
|
|
109
109
|
import { copyFileSync, existsSync, mkdirSync, readdirSync, statSync } from "fs";
|
|
110
|
+
import { hostname } from "os";
|
|
110
111
|
import { homedir } from "os";
|
|
111
112
|
import { join } from "path";
|
|
113
|
+
function getMachineId() {
|
|
114
|
+
if (process.env["ECONOMY_MACHINE_ID"])
|
|
115
|
+
return process.env["ECONOMY_MACHINE_ID"];
|
|
116
|
+
const h = hostname().toLowerCase();
|
|
117
|
+
if (h.startsWith("spark") || h.startsWith("apple"))
|
|
118
|
+
return h.split(".")[0];
|
|
119
|
+
return h.split(".")[0];
|
|
120
|
+
}
|
|
112
121
|
function getDataDir() {
|
|
113
122
|
const home = process.env["HOME"] || process.env["USERPROFILE"] || homedir();
|
|
114
123
|
const newDir = join(home, ".hasna", "economy");
|
|
@@ -141,6 +150,7 @@ function openDatabase(dbPath, skipSeed = false) {
|
|
|
141
150
|
}
|
|
142
151
|
const db = new Database(path);
|
|
143
152
|
db.exec("PRAGMA journal_mode = WAL");
|
|
153
|
+
db.exec("PRAGMA busy_timeout = 5000");
|
|
144
154
|
db.exec("PRAGMA foreign_keys = ON");
|
|
145
155
|
initSchema(db);
|
|
146
156
|
if (!skipSeed) {
|
|
@@ -162,7 +172,8 @@ function initSchema(db) {
|
|
|
162
172
|
cost_usd REAL NOT NULL DEFAULT 0,
|
|
163
173
|
duration_ms INTEGER DEFAULT 0,
|
|
164
174
|
timestamp TEXT NOT NULL,
|
|
165
|
-
source_request_id TEXT
|
|
175
|
+
source_request_id TEXT,
|
|
176
|
+
machine_id TEXT DEFAULT ''
|
|
166
177
|
);
|
|
167
178
|
|
|
168
179
|
CREATE TABLE IF NOT EXISTS sessions (
|
|
@@ -174,7 +185,8 @@ function initSchema(db) {
|
|
|
174
185
|
ended_at TEXT,
|
|
175
186
|
total_cost_usd REAL DEFAULT 0,
|
|
176
187
|
total_tokens INTEGER DEFAULT 0,
|
|
177
|
-
request_count INTEGER DEFAULT 0
|
|
188
|
+
request_count INTEGER DEFAULT 0,
|
|
189
|
+
machine_id TEXT DEFAULT ''
|
|
178
190
|
);
|
|
179
191
|
|
|
180
192
|
CREATE TABLE IF NOT EXISTS projects (
|
|
@@ -240,6 +252,15 @@ function initSchema(db) {
|
|
|
240
252
|
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
241
253
|
);
|
|
242
254
|
`);
|
|
255
|
+
const cols = db.prepare(`PRAGMA table_info(requests)`).all();
|
|
256
|
+
if (!cols.some((c) => c.name === "machine_id")) {
|
|
257
|
+
db.exec(`ALTER TABLE requests ADD COLUMN machine_id TEXT DEFAULT ''`);
|
|
258
|
+
db.exec(`ALTER TABLE sessions ADD COLUMN machine_id TEXT DEFAULT ''`);
|
|
259
|
+
}
|
|
260
|
+
db.exec(`
|
|
261
|
+
CREATE INDEX IF NOT EXISTS idx_requests_machine ON requests(machine_id);
|
|
262
|
+
CREATE INDEX IF NOT EXISTS idx_sessions_machine ON sessions(machine_id);
|
|
263
|
+
`);
|
|
243
264
|
}
|
|
244
265
|
function periodWhere(period) {
|
|
245
266
|
switch (period) {
|
|
@@ -278,17 +299,17 @@ function upsertRequest(db, req) {
|
|
|
278
299
|
INSERT OR REPLACE INTO requests
|
|
279
300
|
(id, agent, session_id, model, input_tokens, output_tokens,
|
|
280
301
|
cache_read_tokens, cache_create_tokens, cost_usd, duration_ms,
|
|
281
|
-
timestamp, source_request_id)
|
|
282
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
283
|
-
`).run(req.id, req.agent, req.session_id, req.model, req.input_tokens, req.output_tokens, req.cache_read_tokens, req.cache_create_tokens, req.cost_usd, req.duration_ms, req.timestamp, req.source_request_id);
|
|
302
|
+
timestamp, source_request_id, machine_id)
|
|
303
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
304
|
+
`).run(req.id, req.agent, req.session_id, req.model, req.input_tokens, req.output_tokens, req.cache_read_tokens, req.cache_create_tokens, req.cost_usd, req.duration_ms, req.timestamp, req.source_request_id, req.machine_id ?? "");
|
|
284
305
|
}
|
|
285
306
|
function upsertSession(db, session) {
|
|
286
307
|
db.prepare(`
|
|
287
308
|
INSERT OR REPLACE INTO sessions
|
|
288
309
|
(id, agent, project_path, project_name, started_at, ended_at,
|
|
289
|
-
total_cost_usd, total_tokens, request_count)
|
|
290
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
291
|
-
`).run(session.id, session.agent, session.project_path, session.project_name, session.started_at, session.ended_at ?? null, session.total_cost_usd, session.total_tokens, session.request_count);
|
|
310
|
+
total_cost_usd, total_tokens, request_count, machine_id)
|
|
311
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
312
|
+
`).run(session.id, session.agent, session.project_path, session.project_name, session.started_at, session.ended_at ?? null, session.total_cost_usd, session.total_tokens, session.request_count, session.machine_id ?? "");
|
|
292
313
|
}
|
|
293
314
|
function rollupSession(db, sessionId) {
|
|
294
315
|
db.prepare(`
|
|
@@ -318,6 +339,10 @@ function querySessions(db, filter = {}) {
|
|
|
318
339
|
conditions.push("started_at >= ?");
|
|
319
340
|
params.push(filter.since);
|
|
320
341
|
}
|
|
342
|
+
if (filter.machine) {
|
|
343
|
+
conditions.push("machine_id = ?");
|
|
344
|
+
params.push(filter.machine);
|
|
345
|
+
}
|
|
321
346
|
if (filter.search) {
|
|
322
347
|
const q = `%${filter.search}%`;
|
|
323
348
|
conditions.push("(project_name LIKE ? OR agent LIKE ? OR id LIKE ?)");
|
|
@@ -336,24 +361,25 @@ function queryTopSessions(db, n = 10, agent) {
|
|
|
336
361
|
}
|
|
337
362
|
return db.prepare(`SELECT * FROM sessions ORDER BY total_cost_usd DESC LIMIT ?`).all(n);
|
|
338
363
|
}
|
|
339
|
-
function querySummary(db, period) {
|
|
364
|
+
function querySummary(db, period, machine) {
|
|
340
365
|
const rWhere = periodWhere(period);
|
|
341
366
|
const sWhere = sessionPeriodWhere(period);
|
|
367
|
+
const machineClause = machine ? ` AND machine_id = '${machine.replace(/'/g, "''")}'` : "";
|
|
342
368
|
const r = db.prepare(`
|
|
343
369
|
SELECT COALESCE(SUM(cost_usd), 0) as total_usd,
|
|
344
370
|
COUNT(*) as requests,
|
|
345
371
|
COALESCE(SUM(input_tokens + output_tokens + cache_read_tokens + cache_create_tokens), 0) as tokens
|
|
346
|
-
FROM requests WHERE ${rWhere}
|
|
372
|
+
FROM requests WHERE ${rWhere}${machineClause}
|
|
347
373
|
`).get();
|
|
348
374
|
const codexTotals = db.prepare(`
|
|
349
375
|
SELECT COALESCE(SUM(total_cost_usd), 0) as cost_usd,
|
|
350
376
|
COALESCE(SUM(total_tokens), 0) as tokens,
|
|
351
377
|
COUNT(*) as sessions
|
|
352
378
|
FROM sessions
|
|
353
|
-
WHERE ${sWhere}
|
|
379
|
+
WHERE ${sWhere}${machineClause}
|
|
354
380
|
AND id NOT IN (SELECT DISTINCT session_id FROM requests)
|
|
355
381
|
`).get();
|
|
356
|
-
const sessionCount = db.prepare(`SELECT COUNT(*) as sessions FROM sessions WHERE ${sWhere}`).get();
|
|
382
|
+
const sessionCount = db.prepare(`SELECT COUNT(*) as sessions FROM sessions WHERE ${sWhere}${machineClause}`).get();
|
|
357
383
|
return {
|
|
358
384
|
total_usd: r.total_usd + codexTotals.cost_usd,
|
|
359
385
|
requests: r.requests,
|
|
@@ -507,6 +533,20 @@ function setIngestState(db, source, key, value) {
|
|
|
507
533
|
function queryRequestsSince(db, since) {
|
|
508
534
|
return db.prepare(`SELECT * FROM requests WHERE timestamp > ? ORDER BY timestamp ASC`).all(since);
|
|
509
535
|
}
|
|
536
|
+
function listMachines(db) {
|
|
537
|
+
return db.prepare(`
|
|
538
|
+
SELECT
|
|
539
|
+
s.machine_id,
|
|
540
|
+
COUNT(DISTINCT s.id) as sessions,
|
|
541
|
+
COALESCE((SELECT COUNT(*) FROM requests r WHERE r.machine_id = s.machine_id), 0) as requests,
|
|
542
|
+
COALESCE(SUM(s.total_cost_usd), 0) as total_cost_usd,
|
|
543
|
+
MAX(s.started_at) as last_active
|
|
544
|
+
FROM sessions s
|
|
545
|
+
WHERE s.machine_id != ''
|
|
546
|
+
GROUP BY s.machine_id
|
|
547
|
+
ORDER BY total_cost_usd DESC
|
|
548
|
+
`).all();
|
|
549
|
+
}
|
|
510
550
|
function upsertModelPricing(db, p) {
|
|
511
551
|
db.prepare(`
|
|
512
552
|
INSERT OR REPLACE INTO model_pricing
|
|
@@ -794,6 +834,7 @@ async function ingestClaude(db, verbose = false, _telemetryDir) {
|
|
|
794
834
|
console.log("Claude projects dir not found:", PROJECTS_DIR);
|
|
795
835
|
return { files: 0, requests: 0, sessions: 0 };
|
|
796
836
|
}
|
|
837
|
+
const machineId = getMachineId();
|
|
797
838
|
let totalFiles = 0;
|
|
798
839
|
let totalRequests = 0;
|
|
799
840
|
const touchedSessions = new Set;
|
|
@@ -865,7 +906,8 @@ async function ingestClaude(db, verbose = false, _telemetryDir) {
|
|
|
865
906
|
cost_usd: costUsd,
|
|
866
907
|
duration_ms: 0,
|
|
867
908
|
timestamp,
|
|
868
|
-
source_request_id: reqId
|
|
909
|
+
source_request_id: reqId,
|
|
910
|
+
machine_id: machineId
|
|
869
911
|
});
|
|
870
912
|
if (!touchedSessions.has(sessionId)) {
|
|
871
913
|
const existing = db.prepare(`SELECT id FROM sessions WHERE id = ?`).get(sessionId);
|
|
@@ -881,7 +923,8 @@ async function ingestClaude(db, verbose = false, _telemetryDir) {
|
|
|
881
923
|
ended_at: null,
|
|
882
924
|
total_cost_usd: 0,
|
|
883
925
|
total_tokens: 0,
|
|
884
|
-
request_count: 0
|
|
926
|
+
request_count: 0,
|
|
927
|
+
machine_id: machineId
|
|
885
928
|
};
|
|
886
929
|
upsertSession(db, session);
|
|
887
930
|
}
|
|
@@ -923,6 +966,7 @@ async function ingestCodex(db, verbose = false) {
|
|
|
923
966
|
console.log("Codex DB not found:", CODEX_DB_PATH);
|
|
924
967
|
return { sessions: 0 };
|
|
925
968
|
}
|
|
969
|
+
const machineId = getMachineId();
|
|
926
970
|
let codexDb = null;
|
|
927
971
|
let ingested = 0;
|
|
928
972
|
try {
|
|
@@ -947,7 +991,8 @@ async function ingestCodex(db, verbose = false) {
|
|
|
947
991
|
ended_at: endedAt,
|
|
948
992
|
total_cost_usd: costUsd,
|
|
949
993
|
total_tokens: thread.tokens_used,
|
|
950
|
-
request_count: 1
|
|
994
|
+
request_count: 1,
|
|
995
|
+
machine_id: machineId
|
|
951
996
|
});
|
|
952
997
|
setIngestState(db, "codex", stateKey, "done");
|
|
953
998
|
ingested++;
|
|
@@ -982,6 +1027,7 @@ export {
|
|
|
982
1027
|
normalizeModelName,
|
|
983
1028
|
listProjects,
|
|
984
1029
|
listModelPricing,
|
|
1030
|
+
listMachines,
|
|
985
1031
|
listGoals,
|
|
986
1032
|
listBudgets,
|
|
987
1033
|
ingestCodex,
|
|
@@ -990,6 +1036,7 @@ export {
|
|
|
990
1036
|
getPricingFromDb,
|
|
991
1037
|
getPricing,
|
|
992
1038
|
getModelPricing,
|
|
1039
|
+
getMachineId,
|
|
993
1040
|
getIngestState,
|
|
994
1041
|
getGoalStatuses,
|
|
995
1042
|
getDbPath,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"claude.d.ts","sourceRoot":"","sources":["../../src/ingest/claude.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,IAAI,QAAQ,EAAE,MAAM,cAAc,CAAA;AA2D7D,wBAAsB,YAAY,CAChC,EAAE,EAAE,QAAQ,EACZ,OAAO,UAAQ,EACf,aAAa,CAAC,EAAE,MAAM,GACrB,OAAO,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,
|
|
1
|
+
{"version":3,"file":"claude.d.ts","sourceRoot":"","sources":["../../src/ingest/claude.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,IAAI,QAAQ,EAAE,MAAM,cAAc,CAAA;AA2D7D,wBAAsB,YAAY,CAChC,EAAE,EAAE,QAAQ,EACZ,OAAO,UAAQ,EACf,aAAa,CAAC,EAAE,MAAM,GACrB,OAAO,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CA8HhE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"codex.d.ts","sourceRoot":"","sources":["../../src/ingest/codex.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,IAAI,QAAQ,EAAE,MAAM,cAAc,CAAA;AAkB7D,iBAAS,cAAc,IAAI,MAAM,CAShC;AAED,wBAAsB,WAAW,CAAC,EAAE,EAAE,QAAQ,EAAE,OAAO,UAAQ,GAAG,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,
|
|
1
|
+
{"version":3,"file":"codex.d.ts","sourceRoot":"","sources":["../../src/ingest/codex.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,IAAI,QAAQ,EAAE,MAAM,cAAc,CAAA;AAkB7D,iBAAS,cAAc,IAAI,MAAM,CAShC;AAED,wBAAsB,WAAW,CAAC,EAAE,EAAE,QAAQ,EAAE,OAAO,UAAQ,GAAG,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CA2D9F;AAED,OAAO,EAAE,cAAc,EAAE,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gemini.d.ts","sourceRoot":"","sources":["../../src/ingest/gemini.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,IAAI,QAAQ,EAAE,MAAM,cAAc,CAAA;AA0B7D,wBAAsB,YAAY,CAAC,EAAE,EAAE,QAAQ,EAAE,OAAO,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,
|
|
1
|
+
{"version":3,"file":"gemini.d.ts","sourceRoot":"","sources":["../../src/ingest/gemini.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,IAAI,QAAQ,EAAE,MAAM,cAAc,CAAA;AA0B7D,wBAAsB,YAAY,CAAC,EAAE,EAAE,QAAQ,EAAE,OAAO,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CA6EjG"}
|
package/dist/mcp/index.js
CHANGED
|
@@ -108,8 +108,17 @@ var init_pricing = __esm(() => {
|
|
|
108
108
|
// src/db/database.ts
|
|
109
109
|
import { SqliteAdapter as Database } from "@hasna/cloud";
|
|
110
110
|
import { copyFileSync, existsSync, mkdirSync, readdirSync, statSync } from "fs";
|
|
111
|
+
import { hostname } from "os";
|
|
111
112
|
import { homedir } from "os";
|
|
112
113
|
import { join } from "path";
|
|
114
|
+
function getMachineId() {
|
|
115
|
+
if (process.env["ECONOMY_MACHINE_ID"])
|
|
116
|
+
return process.env["ECONOMY_MACHINE_ID"];
|
|
117
|
+
const h = hostname().toLowerCase();
|
|
118
|
+
if (h.startsWith("spark") || h.startsWith("apple"))
|
|
119
|
+
return h.split(".")[0];
|
|
120
|
+
return h.split(".")[0];
|
|
121
|
+
}
|
|
113
122
|
function getDataDir() {
|
|
114
123
|
const home = process.env["HOME"] || process.env["USERPROFILE"] || homedir();
|
|
115
124
|
const newDir = join(home, ".hasna", "economy");
|
|
@@ -142,6 +151,7 @@ function openDatabase(dbPath, skipSeed = false) {
|
|
|
142
151
|
}
|
|
143
152
|
const db = new Database(path);
|
|
144
153
|
db.exec("PRAGMA journal_mode = WAL");
|
|
154
|
+
db.exec("PRAGMA busy_timeout = 5000");
|
|
145
155
|
db.exec("PRAGMA foreign_keys = ON");
|
|
146
156
|
initSchema(db);
|
|
147
157
|
if (!skipSeed) {
|
|
@@ -163,7 +173,8 @@ function initSchema(db) {
|
|
|
163
173
|
cost_usd REAL NOT NULL DEFAULT 0,
|
|
164
174
|
duration_ms INTEGER DEFAULT 0,
|
|
165
175
|
timestamp TEXT NOT NULL,
|
|
166
|
-
source_request_id TEXT
|
|
176
|
+
source_request_id TEXT,
|
|
177
|
+
machine_id TEXT DEFAULT ''
|
|
167
178
|
);
|
|
168
179
|
|
|
169
180
|
CREATE TABLE IF NOT EXISTS sessions (
|
|
@@ -175,7 +186,8 @@ function initSchema(db) {
|
|
|
175
186
|
ended_at TEXT,
|
|
176
187
|
total_cost_usd REAL DEFAULT 0,
|
|
177
188
|
total_tokens INTEGER DEFAULT 0,
|
|
178
|
-
request_count INTEGER DEFAULT 0
|
|
189
|
+
request_count INTEGER DEFAULT 0,
|
|
190
|
+
machine_id TEXT DEFAULT ''
|
|
179
191
|
);
|
|
180
192
|
|
|
181
193
|
CREATE TABLE IF NOT EXISTS projects (
|
|
@@ -241,6 +253,15 @@ function initSchema(db) {
|
|
|
241
253
|
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
242
254
|
);
|
|
243
255
|
`);
|
|
256
|
+
const cols = db.prepare(`PRAGMA table_info(requests)`).all();
|
|
257
|
+
if (!cols.some((c) => c.name === "machine_id")) {
|
|
258
|
+
db.exec(`ALTER TABLE requests ADD COLUMN machine_id TEXT DEFAULT ''`);
|
|
259
|
+
db.exec(`ALTER TABLE sessions ADD COLUMN machine_id TEXT DEFAULT ''`);
|
|
260
|
+
}
|
|
261
|
+
db.exec(`
|
|
262
|
+
CREATE INDEX IF NOT EXISTS idx_requests_machine ON requests(machine_id);
|
|
263
|
+
CREATE INDEX IF NOT EXISTS idx_sessions_machine ON sessions(machine_id);
|
|
264
|
+
`);
|
|
244
265
|
}
|
|
245
266
|
function periodWhere(period) {
|
|
246
267
|
switch (period) {
|
|
@@ -279,17 +300,17 @@ function upsertRequest(db, req) {
|
|
|
279
300
|
INSERT OR REPLACE INTO requests
|
|
280
301
|
(id, agent, session_id, model, input_tokens, output_tokens,
|
|
281
302
|
cache_read_tokens, cache_create_tokens, cost_usd, duration_ms,
|
|
282
|
-
timestamp, source_request_id)
|
|
283
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
284
|
-
`).run(req.id, req.agent, req.session_id, req.model, req.input_tokens, req.output_tokens, req.cache_read_tokens, req.cache_create_tokens, req.cost_usd, req.duration_ms, req.timestamp, req.source_request_id);
|
|
303
|
+
timestamp, source_request_id, machine_id)
|
|
304
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
305
|
+
`).run(req.id, req.agent, req.session_id, req.model, req.input_tokens, req.output_tokens, req.cache_read_tokens, req.cache_create_tokens, req.cost_usd, req.duration_ms, req.timestamp, req.source_request_id, req.machine_id ?? "");
|
|
285
306
|
}
|
|
286
307
|
function upsertSession(db, session) {
|
|
287
308
|
db.prepare(`
|
|
288
309
|
INSERT OR REPLACE INTO sessions
|
|
289
310
|
(id, agent, project_path, project_name, started_at, ended_at,
|
|
290
|
-
total_cost_usd, total_tokens, request_count)
|
|
291
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
292
|
-
`).run(session.id, session.agent, session.project_path, session.project_name, session.started_at, session.ended_at ?? null, session.total_cost_usd, session.total_tokens, session.request_count);
|
|
311
|
+
total_cost_usd, total_tokens, request_count, machine_id)
|
|
312
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
313
|
+
`).run(session.id, session.agent, session.project_path, session.project_name, session.started_at, session.ended_at ?? null, session.total_cost_usd, session.total_tokens, session.request_count, session.machine_id ?? "");
|
|
293
314
|
}
|
|
294
315
|
function rollupSession(db, sessionId) {
|
|
295
316
|
db.prepare(`
|
|
@@ -319,6 +340,10 @@ function querySessions(db, filter = {}) {
|
|
|
319
340
|
conditions.push("started_at >= ?");
|
|
320
341
|
params.push(filter.since);
|
|
321
342
|
}
|
|
343
|
+
if (filter.machine) {
|
|
344
|
+
conditions.push("machine_id = ?");
|
|
345
|
+
params.push(filter.machine);
|
|
346
|
+
}
|
|
322
347
|
if (filter.search) {
|
|
323
348
|
const q = `%${filter.search}%`;
|
|
324
349
|
conditions.push("(project_name LIKE ? OR agent LIKE ? OR id LIKE ?)");
|
|
@@ -337,24 +362,25 @@ function queryTopSessions(db, n = 10, agent) {
|
|
|
337
362
|
}
|
|
338
363
|
return db.prepare(`SELECT * FROM sessions ORDER BY total_cost_usd DESC LIMIT ?`).all(n);
|
|
339
364
|
}
|
|
340
|
-
function querySummary(db, period) {
|
|
365
|
+
function querySummary(db, period, machine) {
|
|
341
366
|
const rWhere = periodWhere(period);
|
|
342
367
|
const sWhere = sessionPeriodWhere(period);
|
|
368
|
+
const machineClause = machine ? ` AND machine_id = '${machine.replace(/'/g, "''")}'` : "";
|
|
343
369
|
const r = db.prepare(`
|
|
344
370
|
SELECT COALESCE(SUM(cost_usd), 0) as total_usd,
|
|
345
371
|
COUNT(*) as requests,
|
|
346
372
|
COALESCE(SUM(input_tokens + output_tokens + cache_read_tokens + cache_create_tokens), 0) as tokens
|
|
347
|
-
FROM requests WHERE ${rWhere}
|
|
373
|
+
FROM requests WHERE ${rWhere}${machineClause}
|
|
348
374
|
`).get();
|
|
349
375
|
const codexTotals = db.prepare(`
|
|
350
376
|
SELECT COALESCE(SUM(total_cost_usd), 0) as cost_usd,
|
|
351
377
|
COALESCE(SUM(total_tokens), 0) as tokens,
|
|
352
378
|
COUNT(*) as sessions
|
|
353
379
|
FROM sessions
|
|
354
|
-
WHERE ${sWhere}
|
|
380
|
+
WHERE ${sWhere}${machineClause}
|
|
355
381
|
AND id NOT IN (SELECT DISTINCT session_id FROM requests)
|
|
356
382
|
`).get();
|
|
357
|
-
const sessionCount = db.prepare(`SELECT COUNT(*) as sessions FROM sessions WHERE ${sWhere}`).get();
|
|
383
|
+
const sessionCount = db.prepare(`SELECT COUNT(*) as sessions FROM sessions WHERE ${sWhere}${machineClause}`).get();
|
|
358
384
|
return {
|
|
359
385
|
total_usd: r.total_usd + codexTotals.cost_usd,
|
|
360
386
|
requests: r.requests,
|
|
@@ -477,6 +503,20 @@ function getIngestState(db, source, key) {
|
|
|
477
503
|
function setIngestState(db, source, key, value) {
|
|
478
504
|
db.prepare(`INSERT OR REPLACE INTO ingest_state (source, key, value) VALUES (?, ?, ?)`).run(source, key, value);
|
|
479
505
|
}
|
|
506
|
+
function listMachines(db) {
|
|
507
|
+
return db.prepare(`
|
|
508
|
+
SELECT
|
|
509
|
+
s.machine_id,
|
|
510
|
+
COUNT(DISTINCT s.id) as sessions,
|
|
511
|
+
COALESCE((SELECT COUNT(*) FROM requests r WHERE r.machine_id = s.machine_id), 0) as requests,
|
|
512
|
+
COALESCE(SUM(s.total_cost_usd), 0) as total_cost_usd,
|
|
513
|
+
MAX(s.started_at) as last_active
|
|
514
|
+
FROM sessions s
|
|
515
|
+
WHERE s.machine_id != ''
|
|
516
|
+
GROUP BY s.machine_id
|
|
517
|
+
ORDER BY total_cost_usd DESC
|
|
518
|
+
`).all();
|
|
519
|
+
}
|
|
480
520
|
function upsertModelPricing(db, p) {
|
|
481
521
|
db.prepare(`
|
|
482
522
|
INSERT OR REPLACE INTO model_pricing
|
|
@@ -547,6 +587,7 @@ async function ingestClaude(db, verbose = false, _telemetryDir) {
|
|
|
547
587
|
console.log("Claude projects dir not found:", PROJECTS_DIR);
|
|
548
588
|
return { files: 0, requests: 0, sessions: 0 };
|
|
549
589
|
}
|
|
590
|
+
const machineId = getMachineId();
|
|
550
591
|
let totalFiles = 0;
|
|
551
592
|
let totalRequests = 0;
|
|
552
593
|
const touchedSessions = new Set;
|
|
@@ -618,7 +659,8 @@ async function ingestClaude(db, verbose = false, _telemetryDir) {
|
|
|
618
659
|
cost_usd: costUsd,
|
|
619
660
|
duration_ms: 0,
|
|
620
661
|
timestamp,
|
|
621
|
-
source_request_id: reqId
|
|
662
|
+
source_request_id: reqId,
|
|
663
|
+
machine_id: machineId
|
|
622
664
|
});
|
|
623
665
|
if (!touchedSessions.has(sessionId)) {
|
|
624
666
|
const existing = db.prepare(`SELECT id FROM sessions WHERE id = ?`).get(sessionId);
|
|
@@ -634,7 +676,8 @@ async function ingestClaude(db, verbose = false, _telemetryDir) {
|
|
|
634
676
|
ended_at: null,
|
|
635
677
|
total_cost_usd: 0,
|
|
636
678
|
total_tokens: 0,
|
|
637
|
-
request_count: 0
|
|
679
|
+
request_count: 0,
|
|
680
|
+
machine_id: machineId
|
|
638
681
|
};
|
|
639
682
|
upsertSession(db, session);
|
|
640
683
|
}
|
|
@@ -666,6 +709,7 @@ async function ingestCodex(db, verbose = false) {
|
|
|
666
709
|
console.log("Codex DB not found:", CODEX_DB_PATH);
|
|
667
710
|
return { sessions: 0 };
|
|
668
711
|
}
|
|
712
|
+
const machineId = getMachineId();
|
|
669
713
|
let codexDb = null;
|
|
670
714
|
let ingested = 0;
|
|
671
715
|
try {
|
|
@@ -690,7 +734,8 @@ async function ingestCodex(db, verbose = false) {
|
|
|
690
734
|
ended_at: endedAt,
|
|
691
735
|
total_cost_usd: costUsd,
|
|
692
736
|
total_tokens: thread.tokens_used,
|
|
693
|
-
request_count: 1
|
|
737
|
+
request_count: 1,
|
|
738
|
+
machine_id: machineId
|
|
694
739
|
});
|
|
695
740
|
setIngestState(db, "codex", stateKey, "done");
|
|
696
741
|
ingested++;
|
|
@@ -715,6 +760,7 @@ async function ingestGemini(db, verbose) {
|
|
|
715
760
|
console.log("Gemini tmp dir not found:", GEMINI_TMP_DIR);
|
|
716
761
|
return { sessions: 0 };
|
|
717
762
|
}
|
|
763
|
+
const machineId = getMachineId();
|
|
718
764
|
let totalSessions = 0;
|
|
719
765
|
const touchedSessions = new Set;
|
|
720
766
|
let projectHashDirs = [];
|
|
@@ -765,7 +811,8 @@ async function ingestGemini(db, verbose) {
|
|
|
765
811
|
ended_at: chatData.lastUpdated ?? null,
|
|
766
812
|
total_cost_usd: 0,
|
|
767
813
|
total_tokens: 0,
|
|
768
|
-
request_count: 0
|
|
814
|
+
request_count: 0,
|
|
815
|
+
machine_id: machineId
|
|
769
816
|
};
|
|
770
817
|
upsertSession(db, session);
|
|
771
818
|
touchedSessions.add(sessionId);
|
|
@@ -838,6 +885,7 @@ var TOOL_NAMES = [
|
|
|
838
885
|
"get_goals",
|
|
839
886
|
"set_goal",
|
|
840
887
|
"remove_goal",
|
|
888
|
+
"list_machines",
|
|
841
889
|
"register_agent",
|
|
842
890
|
"heartbeat",
|
|
843
891
|
"set_focus",
|
|
@@ -845,9 +893,10 @@ var TOOL_NAMES = [
|
|
|
845
893
|
"send_feedback"
|
|
846
894
|
];
|
|
847
895
|
var TOOL_DESCRIPTIONS = {
|
|
848
|
-
get_cost_summary: "period(today|week|month|year|all) -> {total_usd, sessions, requests, tokens, summary}",
|
|
849
|
-
get_sessions: "agent(claude|codex|gemini), project(partial), limit(20) -> compact session table",
|
|
896
|
+
get_cost_summary: "period(today|week|month|year|all), machine?(hostname) -> {total_usd, sessions, requests, tokens, summary}",
|
|
897
|
+
get_sessions: "agent(claude|codex|gemini), project(partial), machine?(hostname), limit(20) -> compact session table",
|
|
850
898
|
get_top_sessions: "n(10), agent(claude|codex|gemini) -> top sessions by cost",
|
|
899
|
+
list_machines: "no params -> machine_id, sessions, requests, cost, last_active",
|
|
851
900
|
get_model_breakdown: "no params -> model, requests, tokens, cost",
|
|
852
901
|
get_project_breakdown: "no params -> project_name, sessions, cost",
|
|
853
902
|
get_budget_status: "no params -> budget limits, current spend, percent_used, is_over_alert",
|
|
@@ -891,27 +940,30 @@ server.tool("describe_tools", "Get param hints for specific tools by name.", { n
|
|
|
891
940
|
`);
|
|
892
941
|
return text(result);
|
|
893
942
|
});
|
|
894
|
-
server.tool("get_cost_summary", "Cost summary (total_usd, sessions, requests, tokens, human summary). period: today|week|month|year|all", { period: z.enum(["today", "week", "month", "year", "all"]).optional() }, async ({ period }) => {
|
|
943
|
+
server.tool("get_cost_summary", "Cost summary (total_usd, sessions, requests, tokens, human summary). period: today|week|month|year|all. machine: filter by hostname.", { period: z.enum(["today", "week", "month", "year", "all"]).optional(), machine: z.string().optional() }, async ({ period, machine }) => {
|
|
895
944
|
const resolved = period ?? "today";
|
|
896
|
-
const s = querySummary(db, resolved);
|
|
945
|
+
const s = querySummary(db, resolved, machine);
|
|
946
|
+
const machineLabel = machine ? ` on ${machine}` : "";
|
|
897
947
|
return text([
|
|
898
|
-
`period: ${resolved}`,
|
|
948
|
+
`period: ${resolved}${machineLabel}`,
|
|
899
949
|
`cost: ${fmtUsd(s.total_usd)}`,
|
|
900
950
|
`sessions: ${s.sessions}`,
|
|
901
951
|
`requests: ${s.requests.toLocaleString()}`,
|
|
902
952
|
`tokens: ${fmtTok(s.tokens)}`,
|
|
903
|
-
`summary: You've spent ${fmtUsd(s.total_usd)} ${resolved === "all" ? "total" : resolved} across ${s.sessions} sessions (${s.requests.toLocaleString()} requests, ${fmtTok(s.tokens)} tokens)`
|
|
953
|
+
`summary: You've spent ${fmtUsd(s.total_usd)} ${resolved === "all" ? "total" : resolved}${machineLabel} across ${s.sessions} sessions (${s.requests.toLocaleString()} requests, ${fmtTok(s.tokens)} tokens)`
|
|
904
954
|
].join(`
|
|
905
955
|
`));
|
|
906
956
|
});
|
|
907
|
-
server.tool("get_sessions", "List sessions. Returns compact table. Params: agent, project, limit(20)", {
|
|
957
|
+
server.tool("get_sessions", "List sessions. Returns compact table. Params: agent, project, machine, limit(20)", {
|
|
908
958
|
agent: z.enum(["claude", "codex", "gemini"]).optional(),
|
|
909
959
|
project: z.string().optional(),
|
|
960
|
+
machine: z.string().optional(),
|
|
910
961
|
limit: z.number().int().positive().max(100).optional()
|
|
911
|
-
}, async ({ agent, project, limit }) => {
|
|
962
|
+
}, async ({ agent, project, machine, limit }) => {
|
|
912
963
|
const sessions = querySessions(db, {
|
|
913
964
|
agent,
|
|
914
965
|
project,
|
|
966
|
+
machine,
|
|
915
967
|
limit: limit ?? 20
|
|
916
968
|
});
|
|
917
969
|
const lines = ["id agent cost tokens project"];
|
|
@@ -1057,6 +1109,19 @@ server.tool("remove_goal", "Delete a goal by id.", { id: z.string() }, async ({
|
|
|
1057
1109
|
deleteGoal(db, id);
|
|
1058
1110
|
return text("Goal removed.");
|
|
1059
1111
|
});
|
|
1112
|
+
server.tool("list_machines", "List all machines that have synced data. No params.", {}, async () => {
|
|
1113
|
+
const machines = listMachines(db);
|
|
1114
|
+
if (machines.length === 0)
|
|
1115
|
+
return text(`No machine data yet. Current machine: ${getMachineId()}`);
|
|
1116
|
+
const lines = ["machine sessions requests cost last_active"];
|
|
1117
|
+
for (const m of machines) {
|
|
1118
|
+
lines.push(`${m.machine_id.padEnd(17)}${String(m.sessions).padEnd(10)}${String(m.requests).padEnd(10)}${fmtUsd(m.total_cost_usd).padEnd(12)}${m.last_active?.substring(0, 16) ?? "\u2014"}`);
|
|
1119
|
+
}
|
|
1120
|
+
lines.push(`
|
|
1121
|
+
current machine: ${getMachineId()}`);
|
|
1122
|
+
return text(lines.join(`
|
|
1123
|
+
`));
|
|
1124
|
+
});
|
|
1060
1125
|
server.tool("register_agent", "Register agent session.", { name: z.string(), session_id: z.string().optional() }, async ({ name }) => {
|
|
1061
1126
|
const existing = [..._econAgents.values()].find((agent2) => agent2.name === name);
|
|
1062
1127
|
if (existing) {
|
package/dist/server/index.js
CHANGED
|
@@ -109,8 +109,17 @@ var init_pricing = __esm(() => {
|
|
|
109
109
|
// src/db/database.ts
|
|
110
110
|
import { SqliteAdapter as Database } from "@hasna/cloud";
|
|
111
111
|
import { copyFileSync, existsSync, mkdirSync, readdirSync, statSync } from "fs";
|
|
112
|
+
import { hostname } from "os";
|
|
112
113
|
import { homedir } from "os";
|
|
113
114
|
import { join } from "path";
|
|
115
|
+
function getMachineId() {
|
|
116
|
+
if (process.env["ECONOMY_MACHINE_ID"])
|
|
117
|
+
return process.env["ECONOMY_MACHINE_ID"];
|
|
118
|
+
const h = hostname().toLowerCase();
|
|
119
|
+
if (h.startsWith("spark") || h.startsWith("apple"))
|
|
120
|
+
return h.split(".")[0];
|
|
121
|
+
return h.split(".")[0];
|
|
122
|
+
}
|
|
114
123
|
function getDataDir() {
|
|
115
124
|
const home = process.env["HOME"] || process.env["USERPROFILE"] || homedir();
|
|
116
125
|
const newDir = join(home, ".hasna", "economy");
|
|
@@ -143,6 +152,7 @@ function openDatabase(dbPath, skipSeed = false) {
|
|
|
143
152
|
}
|
|
144
153
|
const db = new Database(path);
|
|
145
154
|
db.exec("PRAGMA journal_mode = WAL");
|
|
155
|
+
db.exec("PRAGMA busy_timeout = 5000");
|
|
146
156
|
db.exec("PRAGMA foreign_keys = ON");
|
|
147
157
|
initSchema(db);
|
|
148
158
|
if (!skipSeed) {
|
|
@@ -164,7 +174,8 @@ function initSchema(db) {
|
|
|
164
174
|
cost_usd REAL NOT NULL DEFAULT 0,
|
|
165
175
|
duration_ms INTEGER DEFAULT 0,
|
|
166
176
|
timestamp TEXT NOT NULL,
|
|
167
|
-
source_request_id TEXT
|
|
177
|
+
source_request_id TEXT,
|
|
178
|
+
machine_id TEXT DEFAULT ''
|
|
168
179
|
);
|
|
169
180
|
|
|
170
181
|
CREATE TABLE IF NOT EXISTS sessions (
|
|
@@ -176,7 +187,8 @@ function initSchema(db) {
|
|
|
176
187
|
ended_at TEXT,
|
|
177
188
|
total_cost_usd REAL DEFAULT 0,
|
|
178
189
|
total_tokens INTEGER DEFAULT 0,
|
|
179
|
-
request_count INTEGER DEFAULT 0
|
|
190
|
+
request_count INTEGER DEFAULT 0,
|
|
191
|
+
machine_id TEXT DEFAULT ''
|
|
180
192
|
);
|
|
181
193
|
|
|
182
194
|
CREATE TABLE IF NOT EXISTS projects (
|
|
@@ -242,6 +254,15 @@ function initSchema(db) {
|
|
|
242
254
|
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
243
255
|
);
|
|
244
256
|
`);
|
|
257
|
+
const cols = db.prepare(`PRAGMA table_info(requests)`).all();
|
|
258
|
+
if (!cols.some((c) => c.name === "machine_id")) {
|
|
259
|
+
db.exec(`ALTER TABLE requests ADD COLUMN machine_id TEXT DEFAULT ''`);
|
|
260
|
+
db.exec(`ALTER TABLE sessions ADD COLUMN machine_id TEXT DEFAULT ''`);
|
|
261
|
+
}
|
|
262
|
+
db.exec(`
|
|
263
|
+
CREATE INDEX IF NOT EXISTS idx_requests_machine ON requests(machine_id);
|
|
264
|
+
CREATE INDEX IF NOT EXISTS idx_sessions_machine ON sessions(machine_id);
|
|
265
|
+
`);
|
|
245
266
|
}
|
|
246
267
|
function periodWhere(period) {
|
|
247
268
|
switch (period) {
|
|
@@ -280,17 +301,17 @@ function upsertRequest(db, req) {
|
|
|
280
301
|
INSERT OR REPLACE INTO requests
|
|
281
302
|
(id, agent, session_id, model, input_tokens, output_tokens,
|
|
282
303
|
cache_read_tokens, cache_create_tokens, cost_usd, duration_ms,
|
|
283
|
-
timestamp, source_request_id)
|
|
284
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
285
|
-
`).run(req.id, req.agent, req.session_id, req.model, req.input_tokens, req.output_tokens, req.cache_read_tokens, req.cache_create_tokens, req.cost_usd, req.duration_ms, req.timestamp, req.source_request_id);
|
|
304
|
+
timestamp, source_request_id, machine_id)
|
|
305
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
306
|
+
`).run(req.id, req.agent, req.session_id, req.model, req.input_tokens, req.output_tokens, req.cache_read_tokens, req.cache_create_tokens, req.cost_usd, req.duration_ms, req.timestamp, req.source_request_id, req.machine_id ?? "");
|
|
286
307
|
}
|
|
287
308
|
function upsertSession(db, session) {
|
|
288
309
|
db.prepare(`
|
|
289
310
|
INSERT OR REPLACE INTO sessions
|
|
290
311
|
(id, agent, project_path, project_name, started_at, ended_at,
|
|
291
|
-
total_cost_usd, total_tokens, request_count)
|
|
292
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
293
|
-
`).run(session.id, session.agent, session.project_path, session.project_name, session.started_at, session.ended_at ?? null, session.total_cost_usd, session.total_tokens, session.request_count);
|
|
312
|
+
total_cost_usd, total_tokens, request_count, machine_id)
|
|
313
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
314
|
+
`).run(session.id, session.agent, session.project_path, session.project_name, session.started_at, session.ended_at ?? null, session.total_cost_usd, session.total_tokens, session.request_count, session.machine_id ?? "");
|
|
294
315
|
}
|
|
295
316
|
function rollupSession(db, sessionId) {
|
|
296
317
|
db.prepare(`
|
|
@@ -320,6 +341,10 @@ function querySessions(db, filter = {}) {
|
|
|
320
341
|
conditions.push("started_at >= ?");
|
|
321
342
|
params.push(filter.since);
|
|
322
343
|
}
|
|
344
|
+
if (filter.machine) {
|
|
345
|
+
conditions.push("machine_id = ?");
|
|
346
|
+
params.push(filter.machine);
|
|
347
|
+
}
|
|
323
348
|
if (filter.search) {
|
|
324
349
|
const q = `%${filter.search}%`;
|
|
325
350
|
conditions.push("(project_name LIKE ? OR agent LIKE ? OR id LIKE ?)");
|
|
@@ -338,24 +363,25 @@ function queryTopSessions(db, n = 10, agent) {
|
|
|
338
363
|
}
|
|
339
364
|
return db.prepare(`SELECT * FROM sessions ORDER BY total_cost_usd DESC LIMIT ?`).all(n);
|
|
340
365
|
}
|
|
341
|
-
function querySummary(db, period) {
|
|
366
|
+
function querySummary(db, period, machine) {
|
|
342
367
|
const rWhere = periodWhere(period);
|
|
343
368
|
const sWhere = sessionPeriodWhere(period);
|
|
369
|
+
const machineClause = machine ? ` AND machine_id = '${machine.replace(/'/g, "''")}'` : "";
|
|
344
370
|
const r = db.prepare(`
|
|
345
371
|
SELECT COALESCE(SUM(cost_usd), 0) as total_usd,
|
|
346
372
|
COUNT(*) as requests,
|
|
347
373
|
COALESCE(SUM(input_tokens + output_tokens + cache_read_tokens + cache_create_tokens), 0) as tokens
|
|
348
|
-
FROM requests WHERE ${rWhere}
|
|
374
|
+
FROM requests WHERE ${rWhere}${machineClause}
|
|
349
375
|
`).get();
|
|
350
376
|
const codexTotals = db.prepare(`
|
|
351
377
|
SELECT COALESCE(SUM(total_cost_usd), 0) as cost_usd,
|
|
352
378
|
COALESCE(SUM(total_tokens), 0) as tokens,
|
|
353
379
|
COUNT(*) as sessions
|
|
354
380
|
FROM sessions
|
|
355
|
-
WHERE ${sWhere}
|
|
381
|
+
WHERE ${sWhere}${machineClause}
|
|
356
382
|
AND id NOT IN (SELECT DISTINCT session_id FROM requests)
|
|
357
383
|
`).get();
|
|
358
|
-
const sessionCount = db.prepare(`SELECT COUNT(*) as sessions FROM sessions WHERE ${sWhere}`).get();
|
|
384
|
+
const sessionCount = db.prepare(`SELECT COUNT(*) as sessions FROM sessions WHERE ${sWhere}${machineClause}`).get();
|
|
359
385
|
return {
|
|
360
386
|
total_usd: r.total_usd + codexTotals.cost_usd,
|
|
361
387
|
requests: r.requests,
|
|
@@ -500,6 +526,20 @@ function getIngestState(db, source, key) {
|
|
|
500
526
|
function setIngestState(db, source, key, value) {
|
|
501
527
|
db.prepare(`INSERT OR REPLACE INTO ingest_state (source, key, value) VALUES (?, ?, ?)`).run(source, key, value);
|
|
502
528
|
}
|
|
529
|
+
function listMachines(db) {
|
|
530
|
+
return db.prepare(`
|
|
531
|
+
SELECT
|
|
532
|
+
s.machine_id,
|
|
533
|
+
COUNT(DISTINCT s.id) as sessions,
|
|
534
|
+
COALESCE((SELECT COUNT(*) FROM requests r WHERE r.machine_id = s.machine_id), 0) as requests,
|
|
535
|
+
COALESCE(SUM(s.total_cost_usd), 0) as total_cost_usd,
|
|
536
|
+
MAX(s.started_at) as last_active
|
|
537
|
+
FROM sessions s
|
|
538
|
+
WHERE s.machine_id != ''
|
|
539
|
+
GROUP BY s.machine_id
|
|
540
|
+
ORDER BY total_cost_usd DESC
|
|
541
|
+
`).all();
|
|
542
|
+
}
|
|
503
543
|
function upsertModelPricing(db, p) {
|
|
504
544
|
db.prepare(`
|
|
505
545
|
INSERT OR REPLACE INTO model_pricing
|
|
@@ -571,6 +611,7 @@ async function ingestClaude(db, verbose = false, _telemetryDir) {
|
|
|
571
611
|
console.log("Claude projects dir not found:", PROJECTS_DIR);
|
|
572
612
|
return { files: 0, requests: 0, sessions: 0 };
|
|
573
613
|
}
|
|
614
|
+
const machineId = getMachineId();
|
|
574
615
|
let totalFiles = 0;
|
|
575
616
|
let totalRequests = 0;
|
|
576
617
|
const touchedSessions = new Set;
|
|
@@ -642,7 +683,8 @@ async function ingestClaude(db, verbose = false, _telemetryDir) {
|
|
|
642
683
|
cost_usd: costUsd,
|
|
643
684
|
duration_ms: 0,
|
|
644
685
|
timestamp,
|
|
645
|
-
source_request_id: reqId
|
|
686
|
+
source_request_id: reqId,
|
|
687
|
+
machine_id: machineId
|
|
646
688
|
});
|
|
647
689
|
if (!touchedSessions.has(sessionId)) {
|
|
648
690
|
const existing = db.prepare(`SELECT id FROM sessions WHERE id = ?`).get(sessionId);
|
|
@@ -658,7 +700,8 @@ async function ingestClaude(db, verbose = false, _telemetryDir) {
|
|
|
658
700
|
ended_at: null,
|
|
659
701
|
total_cost_usd: 0,
|
|
660
702
|
total_tokens: 0,
|
|
661
|
-
request_count: 0
|
|
703
|
+
request_count: 0,
|
|
704
|
+
machine_id: machineId
|
|
662
705
|
};
|
|
663
706
|
upsertSession(db, session);
|
|
664
707
|
}
|
|
@@ -690,6 +733,7 @@ async function ingestCodex(db, verbose = false) {
|
|
|
690
733
|
console.log("Codex DB not found:", CODEX_DB_PATH);
|
|
691
734
|
return { sessions: 0 };
|
|
692
735
|
}
|
|
736
|
+
const machineId = getMachineId();
|
|
693
737
|
let codexDb = null;
|
|
694
738
|
let ingested = 0;
|
|
695
739
|
try {
|
|
@@ -714,7 +758,8 @@ async function ingestCodex(db, verbose = false) {
|
|
|
714
758
|
ended_at: endedAt,
|
|
715
759
|
total_cost_usd: costUsd,
|
|
716
760
|
total_tokens: thread.tokens_used,
|
|
717
|
-
request_count: 1
|
|
761
|
+
request_count: 1,
|
|
762
|
+
machine_id: machineId
|
|
718
763
|
});
|
|
719
764
|
setIngestState(db, "codex", stateKey, "done");
|
|
720
765
|
ingested++;
|
|
@@ -739,6 +784,7 @@ async function ingestGemini(db, verbose) {
|
|
|
739
784
|
console.log("Gemini tmp dir not found:", GEMINI_TMP_DIR);
|
|
740
785
|
return { sessions: 0 };
|
|
741
786
|
}
|
|
787
|
+
const machineId = getMachineId();
|
|
742
788
|
let totalSessions = 0;
|
|
743
789
|
const touchedSessions = new Set;
|
|
744
790
|
let projectHashDirs = [];
|
|
@@ -789,7 +835,8 @@ async function ingestGemini(db, verbose) {
|
|
|
789
835
|
ended_at: chatData.lastUpdated ?? null,
|
|
790
836
|
total_cost_usd: 0,
|
|
791
837
|
total_tokens: 0,
|
|
792
|
-
request_count: 0
|
|
838
|
+
request_count: 0,
|
|
839
|
+
machine_id: machineId
|
|
793
840
|
};
|
|
794
841
|
upsertSession(db, session);
|
|
795
842
|
touchedSessions.add(sessionId);
|
|
@@ -854,7 +901,11 @@ function createHandler(db) {
|
|
|
854
901
|
return ok({ status: "ok", ts: new Date().toISOString() });
|
|
855
902
|
if (path === "/api/summary" && method === "GET") {
|
|
856
903
|
const period = url.searchParams.get("period") ?? "today";
|
|
857
|
-
|
|
904
|
+
const machine = url.searchParams.get("machine") ?? undefined;
|
|
905
|
+
return ok(querySummary(db, period, machine));
|
|
906
|
+
}
|
|
907
|
+
if (path === "/api/machines" && method === "GET") {
|
|
908
|
+
return ok(listMachines(db), { current_machine: getMachineId() });
|
|
858
909
|
}
|
|
859
910
|
if (path === "/api/daily" && method === "GET") {
|
|
860
911
|
const days = Number(url.searchParams.get("days") ?? 30);
|
|
@@ -864,6 +915,7 @@ function createHandler(db) {
|
|
|
864
915
|
const agent = url.searchParams.get("agent");
|
|
865
916
|
const project = url.searchParams.get("project") ?? undefined;
|
|
866
917
|
const search = url.searchParams.get("search") ?? undefined;
|
|
918
|
+
const machine = url.searchParams.get("machine") ?? undefined;
|
|
867
919
|
const limit = Number(url.searchParams.get("limit") ?? 50);
|
|
868
920
|
const offset = Number(url.searchParams.get("offset") ?? 0);
|
|
869
921
|
const since = url.searchParams.get("since") ?? undefined;
|
|
@@ -873,6 +925,7 @@ function createHandler(db) {
|
|
|
873
925
|
agent: agent ?? undefined,
|
|
874
926
|
project,
|
|
875
927
|
search,
|
|
928
|
+
machine,
|
|
876
929
|
limit,
|
|
877
930
|
offset,
|
|
878
931
|
since
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"serve.d.ts","sourceRoot":"","sources":["../../src/server/serve.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,IAAI,QAAQ,EAAE,MAAM,cAAc,CAAA;
|
|
1
|
+
{"version":3,"file":"serve.d.ts","sourceRoot":"","sources":["../../src/server/serve.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,IAAI,QAAQ,EAAE,MAAM,cAAc,CAAA;AA4D7D,wBAAgB,aAAa,CAAC,EAAE,EAAE,QAAQ,IACV,KAAK,OAAO,KAAG,OAAO,CAAC,QAAQ,CAAC,CAgM/D;AAED,wBAAgB,WAAW,CAAC,IAAI,SAAO,GAAG,IAAI,CAuC7C"}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -13,6 +13,7 @@ export interface EconomyRequest {
|
|
|
13
13
|
duration_ms: number;
|
|
14
14
|
timestamp: string;
|
|
15
15
|
source_request_id: string;
|
|
16
|
+
machine_id?: string;
|
|
16
17
|
}
|
|
17
18
|
export interface EconomySession {
|
|
18
19
|
id: string;
|
|
@@ -24,6 +25,7 @@ export interface EconomySession {
|
|
|
24
25
|
total_cost_usd: number;
|
|
25
26
|
total_tokens: number;
|
|
26
27
|
request_count: number;
|
|
28
|
+
machine_id?: string;
|
|
27
29
|
}
|
|
28
30
|
export interface EconomyProject {
|
|
29
31
|
id: string;
|
|
@@ -97,5 +99,6 @@ export interface SessionFilter {
|
|
|
97
99
|
offset?: number;
|
|
98
100
|
since?: string;
|
|
99
101
|
search?: string;
|
|
102
|
+
machine?: string;
|
|
100
103
|
}
|
|
101
104
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,KAAK,GAAG,QAAQ,GAAG,OAAO,GAAG,QAAQ,CAAA;AAEjD,MAAM,MAAM,MAAM,GAAG,OAAO,GAAG,WAAW,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,GAAG,KAAK,CAAA;AAE9E,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,KAAK,CAAA;IACZ,UAAU,EAAE,MAAM,CAAA;IAClB,KAAK,EAAE,MAAM,CAAA;IACb,YAAY,EAAE,MAAM,CAAA;IACpB,aAAa,EAAE,MAAM,CAAA;IACrB,iBAAiB,EAAE,MAAM,CAAA;IACzB,mBAAmB,EAAE,MAAM,CAAA;IAC3B,QAAQ,EAAE,MAAM,CAAA;IAChB,WAAW,EAAE,MAAM,CAAA;IACnB,SAAS,EAAE,MAAM,CAAA;IACjB,iBAAiB,EAAE,MAAM,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED