@hasna/economy 0.2.3 → 0.2.5
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/mcp/index.js +300 -29
- package/package.json +1 -1
- package/dist/cli/commands/watch.d.ts +0 -8
- package/dist/cli/commands/watch.d.ts.map +0 -1
- package/dist/cli/index.d.ts +0 -3
- package/dist/cli/index.d.ts.map +0 -1
- package/dist/cli/index.js +0 -1725
- package/dist/db/database.d.ts +0 -47
- package/dist/db/database.d.ts.map +0 -1
- package/dist/index.d.ts +0 -6
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -674
- package/dist/ingest/claude.d.ts +0 -7
- package/dist/ingest/claude.d.ts.map +0 -1
- package/dist/ingest/codex.d.ts +0 -7
- package/dist/ingest/codex.d.ts.map +0 -1
- package/dist/lib/config.d.ts +0 -13
- package/dist/lib/config.d.ts.map +0 -1
- package/dist/lib/pricing.d.ts +0 -10
- package/dist/lib/pricing.d.ts.map +0 -1
- package/dist/lib/webhooks.d.ts +0 -3
- package/dist/lib/webhooks.d.ts.map +0 -1
- package/dist/mcp/index.d.ts +0 -3
- package/dist/mcp/index.d.ts.map +0 -1
- package/dist/server/index.d.ts +0 -2
- package/dist/server/index.d.ts.map +0 -1
- package/dist/server/index.js +0 -809
- package/dist/server/serve.d.ts +0 -4
- package/dist/server/serve.d.ts.map +0 -1
- package/dist/types/index.d.ts +0 -100
- package/dist/types/index.d.ts.map +0 -1
package/dist/mcp/index.js
CHANGED
|
@@ -1,16 +1,21 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
// @bun
|
|
3
3
|
var __defProp = Object.defineProperty;
|
|
4
|
+
var __returnValue = (v) => v;
|
|
5
|
+
function __exportSetter(name, newValue) {
|
|
6
|
+
this[name] = __returnValue.bind(null, newValue);
|
|
7
|
+
}
|
|
4
8
|
var __export = (target, all) => {
|
|
5
9
|
for (var name in all)
|
|
6
10
|
__defProp(target, name, {
|
|
7
11
|
get: all[name],
|
|
8
12
|
enumerable: true,
|
|
9
13
|
configurable: true,
|
|
10
|
-
set: (
|
|
14
|
+
set: __exportSetter.bind(all, name)
|
|
11
15
|
});
|
|
12
16
|
};
|
|
13
17
|
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
18
|
+
var __require = import.meta.require;
|
|
14
19
|
|
|
15
20
|
// src/lib/pricing.ts
|
|
16
21
|
var exports_pricing = {};
|
|
@@ -84,6 +89,10 @@ var init_pricing = __esm(() => {
|
|
|
84
89
|
"claude-3-opus": { inputPer1M: 15, outputPer1M: 75, cacheReadPer1M: 1.5, cacheWritePer1M: 18.75 },
|
|
85
90
|
"claude-3-sonnet": { inputPer1M: 3, outputPer1M: 15, cacheReadPer1M: 0.3, cacheWritePer1M: 3.75 },
|
|
86
91
|
"claude-3-haiku": { inputPer1M: 0.25, outputPer1M: 1.25, cacheReadPer1M: 0.03, cacheWritePer1M: 0.3 },
|
|
92
|
+
"gemini-2.0-flash": { inputPer1M: 0.075, outputPer1M: 0.3, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
93
|
+
"gemini-2.5-pro": { inputPer1M: 1.25, outputPer1M: 10, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
94
|
+
"gemini-1.5-pro": { inputPer1M: 1.25, outputPer1M: 5, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
95
|
+
"gemini-1.5-flash": { inputPer1M: 0.075, outputPer1M: 0.3, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
87
96
|
"gpt-5.3-codex": { inputPer1M: 1.75, outputPer1M: 14, cacheReadPer1M: 0.44, cacheWritePer1M: 0 },
|
|
88
97
|
"gpt-5.2-codex": { inputPer1M: 1.75, outputPer1M: 14, cacheReadPer1M: 0.44, cacheWritePer1M: 0 },
|
|
89
98
|
"gpt-5-codex": { inputPer1M: 1.75, outputPer1M: 14, cacheReadPer1M: 0.44, cacheWritePer1M: 0 },
|
|
@@ -170,6 +179,16 @@ function initSchema(db) {
|
|
|
170
179
|
updated_at TEXT NOT NULL
|
|
171
180
|
);
|
|
172
181
|
|
|
182
|
+
CREATE TABLE IF NOT EXISTS goals (
|
|
183
|
+
id TEXT PRIMARY KEY,
|
|
184
|
+
period TEXT NOT NULL,
|
|
185
|
+
project_path TEXT,
|
|
186
|
+
agent TEXT,
|
|
187
|
+
limit_usd REAL NOT NULL,
|
|
188
|
+
created_at TEXT NOT NULL,
|
|
189
|
+
updated_at TEXT NOT NULL
|
|
190
|
+
);
|
|
191
|
+
|
|
173
192
|
CREATE TABLE IF NOT EXISTS ingest_state (
|
|
174
193
|
source TEXT NOT NULL,
|
|
175
194
|
key TEXT NOT NULL,
|
|
@@ -202,6 +221,8 @@ function periodWhere(period) {
|
|
|
202
221
|
return `timestamp >= DATE('now', '-7 days')`;
|
|
203
222
|
case "month":
|
|
204
223
|
return `timestamp >= DATE('now', '-30 days')`;
|
|
224
|
+
case "year":
|
|
225
|
+
return `timestamp >= DATE('now', '-365 days')`;
|
|
205
226
|
case "all":
|
|
206
227
|
return "1=1";
|
|
207
228
|
}
|
|
@@ -214,6 +235,8 @@ function sessionPeriodWhere(period) {
|
|
|
214
235
|
return `started_at >= DATE('now', '-7 days')`;
|
|
215
236
|
case "month":
|
|
216
237
|
return `started_at >= DATE('now', '-30 days')`;
|
|
238
|
+
case "year":
|
|
239
|
+
return `started_at >= DATE('now', '-365 days')`;
|
|
217
240
|
case "all":
|
|
218
241
|
return "1=1";
|
|
219
242
|
}
|
|
@@ -263,6 +286,11 @@ function querySessions(db, filter = {}) {
|
|
|
263
286
|
conditions.push("started_at >= ?");
|
|
264
287
|
params.push(filter.since);
|
|
265
288
|
}
|
|
289
|
+
if (filter.search) {
|
|
290
|
+
const q = `%${filter.search}%`;
|
|
291
|
+
conditions.push("(project_name LIKE ? OR agent LIKE ? OR id LIKE ?)");
|
|
292
|
+
params.push(q, q, `${filter.search}%`);
|
|
293
|
+
}
|
|
266
294
|
const where = conditions.length ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
267
295
|
const limit = filter.limit ?? 50;
|
|
268
296
|
const offset = filter.offset ?? 0;
|
|
@@ -315,16 +343,31 @@ function queryModelBreakdown(db) {
|
|
|
315
343
|
}
|
|
316
344
|
function queryProjectBreakdown(db) {
|
|
317
345
|
return db.prepare(`
|
|
318
|
-
SELECT
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
346
|
+
SELECT
|
|
347
|
+
s.project_path,
|
|
348
|
+
COALESCE(p.name, s.project_name) as project_name,
|
|
349
|
+
COUNT(DISTINCT s.id) as sessions,
|
|
350
|
+
COUNT(r.id) as requests,
|
|
351
|
+
COALESCE(SUM(r.cost_usd), COALESCE(SUM(s.total_cost_usd), 0)) as cost_usd,
|
|
352
|
+
COALESCE(SUM(r.input_tokens + r.output_tokens + r.cache_read_tokens + r.cache_create_tokens), 0) as total_tokens,
|
|
353
|
+
MAX(s.started_at) as last_active
|
|
354
|
+
FROM sessions s
|
|
355
|
+
LEFT JOIN projects p ON p.path = s.project_path OR p.name = s.project_name
|
|
356
|
+
LEFT JOIN requests r ON r.session_id = s.id
|
|
357
|
+
WHERE s.project_path != '' OR s.project_name != ''
|
|
358
|
+
GROUP BY s.project_path
|
|
359
|
+
ORDER BY cost_usd DESC
|
|
326
360
|
`).all();
|
|
327
361
|
}
|
|
362
|
+
function queryDailyBreakdown(db, days = 30) {
|
|
363
|
+
return db.prepare(`
|
|
364
|
+
SELECT DATE(timestamp) as date, agent, COALESCE(SUM(cost_usd), 0) as cost_usd
|
|
365
|
+
FROM requests
|
|
366
|
+
WHERE timestamp >= DATE('now', ? || ' days')
|
|
367
|
+
GROUP BY DATE(timestamp), agent
|
|
368
|
+
ORDER BY date ASC
|
|
369
|
+
`).all(`-${days}`);
|
|
370
|
+
}
|
|
328
371
|
function listBudgets(db) {
|
|
329
372
|
return db.prepare(`SELECT * FROM budgets ORDER BY created_at DESC`).all();
|
|
330
373
|
}
|
|
@@ -354,6 +397,46 @@ function getBudgetStatuses(db) {
|
|
|
354
397
|
};
|
|
355
398
|
});
|
|
356
399
|
}
|
|
400
|
+
function upsertGoal(db, goal) {
|
|
401
|
+
db.prepare(`
|
|
402
|
+
INSERT OR REPLACE INTO goals
|
|
403
|
+
(id, period, project_path, agent, limit_usd, created_at, updated_at)
|
|
404
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
405
|
+
`).run(goal.id, goal.period, goal.project_path ?? null, goal.agent ?? null, goal.limit_usd, goal.created_at, goal.updated_at);
|
|
406
|
+
}
|
|
407
|
+
function deleteGoal(db, id) {
|
|
408
|
+
db.prepare(`DELETE FROM goals WHERE id = ?`).run(id);
|
|
409
|
+
}
|
|
410
|
+
function listGoals(db) {
|
|
411
|
+
return db.prepare(`SELECT * FROM goals ORDER BY created_at DESC`).all();
|
|
412
|
+
}
|
|
413
|
+
function getGoalStatuses(db) {
|
|
414
|
+
const goals = listGoals(db);
|
|
415
|
+
return goals.map((g) => {
|
|
416
|
+
const periodStart = g.period === "day" ? "DATE('now')" : g.period === "week" ? "DATE('now', '-7 days')" : g.period === "month" ? "DATE('now', '-30 days')" : "DATE('now', '-365 days')";
|
|
417
|
+
let spendQuery = `SELECT COALESCE(SUM(cost_usd), 0) as spend FROM requests WHERE timestamp >= ${periodStart}`;
|
|
418
|
+
const params = [];
|
|
419
|
+
if (g.project_path) {
|
|
420
|
+
spendQuery += ` AND session_id IN (SELECT id FROM sessions WHERE project_path = ?)`;
|
|
421
|
+
params.push(g.project_path);
|
|
422
|
+
}
|
|
423
|
+
if (g.agent) {
|
|
424
|
+
spendQuery += ` AND agent = ?`;
|
|
425
|
+
params.push(g.agent);
|
|
426
|
+
}
|
|
427
|
+
const row = db.prepare(spendQuery).get(...params);
|
|
428
|
+
const spend = row.spend;
|
|
429
|
+
const percent = g.limit_usd > 0 ? spend / g.limit_usd * 100 : 0;
|
|
430
|
+
return {
|
|
431
|
+
...g,
|
|
432
|
+
current_spend_usd: spend,
|
|
433
|
+
percent_used: percent,
|
|
434
|
+
is_on_track: percent < 70,
|
|
435
|
+
is_at_risk: percent >= 70 && percent <= 100,
|
|
436
|
+
is_over: percent > 100
|
|
437
|
+
};
|
|
438
|
+
});
|
|
439
|
+
}
|
|
357
440
|
function getIngestState(db, source, key) {
|
|
358
441
|
const row = db.prepare(`SELECT value FROM ingest_state WHERE source = ? AND key = ?`).get(source, key);
|
|
359
442
|
return row?.value ?? null;
|
|
@@ -401,6 +484,9 @@ init_pricing();
|
|
|
401
484
|
import { readdirSync, readFileSync, existsSync as existsSync2, statSync } from "fs";
|
|
402
485
|
import { homedir as homedir2 } from "os";
|
|
403
486
|
import { join as join2, basename } from "path";
|
|
487
|
+
function autoDetectProject(cwd, projects) {
|
|
488
|
+
return projects.find((p) => cwd === p.path || cwd.startsWith(p.path + "/"));
|
|
489
|
+
}
|
|
404
490
|
var PROJECTS_DIR = join2(homedir2(), ".claude", "projects");
|
|
405
491
|
function dirNameToPath(dirName) {
|
|
406
492
|
return dirName.replace(/^-/, "/").replace(/-/g, "/").replace(/\/\//g, "/-");
|
|
@@ -429,11 +515,11 @@ async function ingestClaude(db, verbose = false, _telemetryDir) {
|
|
|
429
515
|
let totalFiles = 0;
|
|
430
516
|
let totalRequests = 0;
|
|
431
517
|
const touchedSessions = new Set;
|
|
518
|
+
const registeredProjects = db.prepare(`SELECT path, name FROM projects ORDER BY LENGTH(path) DESC`).all();
|
|
432
519
|
const projectDirs = readdirSync(PROJECTS_DIR, { withFileTypes: true }).filter((d) => d.isDirectory());
|
|
433
520
|
for (const projectDirEntry of projectDirs) {
|
|
434
521
|
const projectDirPath = join2(PROJECTS_DIR, projectDirEntry.name);
|
|
435
522
|
const projectPath = dirNameToPath(projectDirEntry.name);
|
|
436
|
-
const projectName = basename(projectPath);
|
|
437
523
|
const jsonlFiles = collectJsonlFiles(projectDirPath);
|
|
438
524
|
for (const filePath of jsonlFiles) {
|
|
439
525
|
const stateKey = filePath.replace(PROJECTS_DIR, "");
|
|
@@ -502,11 +588,13 @@ async function ingestClaude(db, verbose = false, _telemetryDir) {
|
|
|
502
588
|
if (!touchedSessions.has(sessionId)) {
|
|
503
589
|
const existing = db.prepare(`SELECT id FROM sessions WHERE id = ?`).get(sessionId);
|
|
504
590
|
if (!existing) {
|
|
591
|
+
const effectiveCwd = sessionCwd || projectPath;
|
|
592
|
+
const detectedProject = autoDetectProject(effectiveCwd, registeredProjects);
|
|
505
593
|
const session = {
|
|
506
594
|
id: sessionId,
|
|
507
595
|
agent: "claude",
|
|
508
|
-
project_path:
|
|
509
|
-
project_name:
|
|
596
|
+
project_path: detectedProject ? detectedProject.path : effectiveCwd,
|
|
597
|
+
project_name: detectedProject ? detectedProject.name : "",
|
|
510
598
|
started_at: timestamp,
|
|
511
599
|
ended_at: null,
|
|
512
600
|
total_cost_usd: 0,
|
|
@@ -537,24 +625,12 @@ import { join as join3, basename as basename2 } from "path";
|
|
|
537
625
|
import { Database as Database2 } from "bun:sqlite";
|
|
538
626
|
var CODEX_DB_PATH = join3(homedir3(), ".codex", "state_5.sqlite");
|
|
539
627
|
var CODEX_CONFIG_PATH = join3(homedir3(), ".codex", "config.toml");
|
|
540
|
-
function readCodexModel() {
|
|
541
|
-
if (!existsSync3(CODEX_CONFIG_PATH))
|
|
542
|
-
return "gpt-5.3-codex";
|
|
543
|
-
try {
|
|
544
|
-
const content = readFileSync2(CODEX_CONFIG_PATH, "utf-8");
|
|
545
|
-
const match = content.match(/^model\s*=\s*"([^"]+)"/m);
|
|
546
|
-
return match?.[1] ?? "gpt-5.3-codex";
|
|
547
|
-
} catch {
|
|
548
|
-
return "gpt-5.3-codex";
|
|
549
|
-
}
|
|
550
|
-
}
|
|
551
628
|
async function ingestCodex(db, verbose = false) {
|
|
552
629
|
if (!existsSync3(CODEX_DB_PATH)) {
|
|
553
630
|
if (verbose)
|
|
554
631
|
console.log("Codex DB not found:", CODEX_DB_PATH);
|
|
555
632
|
return { sessions: 0 };
|
|
556
633
|
}
|
|
557
|
-
const model = readCodexModel();
|
|
558
634
|
let codexDb = null;
|
|
559
635
|
let ingested = 0;
|
|
560
636
|
try {
|
|
@@ -592,6 +668,83 @@ async function ingestCodex(db, verbose = false) {
|
|
|
592
668
|
return { sessions: ingested };
|
|
593
669
|
}
|
|
594
670
|
|
|
671
|
+
// src/ingest/gemini.ts
|
|
672
|
+
init_database();
|
|
673
|
+
import { readdirSync as readdirSync2, readFileSync as readFileSync3, existsSync as existsSync4, statSync as statSync2 } from "fs";
|
|
674
|
+
import { homedir as homedir4 } from "os";
|
|
675
|
+
import { join as join4 } from "path";
|
|
676
|
+
var GEMINI_TMP_DIR = join4(homedir4(), ".gemini", "tmp");
|
|
677
|
+
async function ingestGemini(db, verbose) {
|
|
678
|
+
if (!existsSync4(GEMINI_TMP_DIR)) {
|
|
679
|
+
if (verbose)
|
|
680
|
+
console.log("Gemini tmp dir not found:", GEMINI_TMP_DIR);
|
|
681
|
+
return { sessions: 0 };
|
|
682
|
+
}
|
|
683
|
+
let totalSessions = 0;
|
|
684
|
+
const touchedSessions = new Set;
|
|
685
|
+
let projectHashDirs = [];
|
|
686
|
+
try {
|
|
687
|
+
projectHashDirs = readdirSync2(GEMINI_TMP_DIR, { withFileTypes: true }).filter((d) => d.isDirectory() && /^[0-9a-f]{64}$/.test(d.name)).map((d) => join4(GEMINI_TMP_DIR, d.name));
|
|
688
|
+
} catch {
|
|
689
|
+
return { sessions: 0 };
|
|
690
|
+
}
|
|
691
|
+
for (const projectDir of projectHashDirs) {
|
|
692
|
+
const chatsDir = join4(projectDir, "chats");
|
|
693
|
+
if (!existsSync4(chatsDir))
|
|
694
|
+
continue;
|
|
695
|
+
let chatFiles = [];
|
|
696
|
+
try {
|
|
697
|
+
chatFiles = readdirSync2(chatsDir).filter((f) => f.endsWith(".json")).map((f) => join4(chatsDir, f));
|
|
698
|
+
} catch {
|
|
699
|
+
continue;
|
|
700
|
+
}
|
|
701
|
+
for (const filePath of chatFiles) {
|
|
702
|
+
const stateKey = filePath.replace(homedir4(), "~");
|
|
703
|
+
let fileMtime = "0";
|
|
704
|
+
try {
|
|
705
|
+
fileMtime = statSync2(filePath).mtimeMs.toString();
|
|
706
|
+
} catch {
|
|
707
|
+
continue;
|
|
708
|
+
}
|
|
709
|
+
const processed = getIngestState(db, "gemini", stateKey);
|
|
710
|
+
if (processed === fileMtime)
|
|
711
|
+
continue;
|
|
712
|
+
let chatData;
|
|
713
|
+
try {
|
|
714
|
+
chatData = JSON.parse(readFileSync3(filePath, "utf-8"));
|
|
715
|
+
} catch {
|
|
716
|
+
continue;
|
|
717
|
+
}
|
|
718
|
+
const sessionId = chatData.sessionId;
|
|
719
|
+
if (!sessionId)
|
|
720
|
+
continue;
|
|
721
|
+
const startTime = chatData.startTime ?? new Date().toISOString();
|
|
722
|
+
const existing = db.prepare(`SELECT id FROM sessions WHERE id = ?`).get(sessionId);
|
|
723
|
+
if (!existing) {
|
|
724
|
+
const session = {
|
|
725
|
+
id: sessionId,
|
|
726
|
+
agent: "gemini",
|
|
727
|
+
project_path: "",
|
|
728
|
+
project_name: "",
|
|
729
|
+
started_at: startTime,
|
|
730
|
+
ended_at: chatData.lastUpdated ?? null,
|
|
731
|
+
total_cost_usd: 0,
|
|
732
|
+
total_tokens: 0,
|
|
733
|
+
request_count: 0
|
|
734
|
+
};
|
|
735
|
+
upsertSession(db, session);
|
|
736
|
+
touchedSessions.add(sessionId);
|
|
737
|
+
totalSessions++;
|
|
738
|
+
}
|
|
739
|
+
setIngestState(db, "gemini", stateKey, fileMtime);
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
for (const sessionId of touchedSessions) {
|
|
743
|
+
rollupSession(db, sessionId);
|
|
744
|
+
}
|
|
745
|
+
return { sessions: totalSessions };
|
|
746
|
+
}
|
|
747
|
+
|
|
595
748
|
// src/mcp/index.ts
|
|
596
749
|
init_pricing();
|
|
597
750
|
var db = openDatabase();
|
|
@@ -608,24 +761,37 @@ function fmtSession(s) {
|
|
|
608
761
|
return `${id} ${agent.padEnd(6)} ${cost.padEnd(10)} ${tok.padEnd(8)} ${proj}`;
|
|
609
762
|
}
|
|
610
763
|
var TOOLS = [
|
|
611
|
-
{ name: "get_cost_summary", description: "Cost summary (total_usd, sessions, requests, tokens, human summary). period: today|week|month|all", inputSchema: { type: "object", properties: { period: { type: "string", enum: ["today", "week", "month", "all"] } } } },
|
|
764
|
+
{ name: "get_cost_summary", description: "Cost summary (total_usd, sessions, requests, tokens, human summary). period: today|week|month|year|all", inputSchema: { type: "object", properties: { period: { type: "string", enum: ["today", "week", "month", "year", "all"] } } } },
|
|
612
765
|
{ name: "get_sessions", description: "List sessions. Returns compact table. Params: agent, project, limit(20)", inputSchema: { type: "object", properties: { agent: { type: "string" }, project: { type: "string" }, limit: { type: "number" } } } },
|
|
613
766
|
{ name: "get_top_sessions", description: "Top sessions by cost. Params: n(10), agent", inputSchema: { type: "object", properties: { n: { type: "number" }, agent: { type: "string" } } } },
|
|
614
767
|
{ name: "get_model_breakdown", description: "Cost per model. No params.", inputSchema: { type: "object", properties: {} } },
|
|
615
768
|
{ name: "get_project_breakdown", description: "Cost per project. No params.", inputSchema: { type: "object", properties: {} } },
|
|
616
769
|
{ name: "get_budget_status", description: "Budget limits vs spend, percent used, alert flags. No params.", inputSchema: { type: "object", properties: {} } },
|
|
617
|
-
{ name: "
|
|
770
|
+
{ name: "get_daily", description: "Daily cost table by agent. Params: days(30)", inputSchema: { type: "object", properties: { days: { type: "number" } } } },
|
|
771
|
+
{ name: "get_session_detail", description: "Per-request breakdown of a single session. Params: session_id (prefix ok)", inputSchema: { type: "object", properties: { session_id: { type: "string" } }, required: ["session_id"] } },
|
|
772
|
+
{ name: "sync", description: "Ingest new cost data. sources: all|claude|codex|gemini", inputSchema: { type: "object", properties: { sources: { type: "string", enum: ["all", "claude", "codex", "gemini"] } } } },
|
|
618
773
|
{ name: "search_tools", description: "List tool names matching query. Use first to find relevant tools.", inputSchema: { type: "object", properties: { query: { type: "string" } } } },
|
|
619
|
-
{ name: "describe_tools", description: "Get param hints for specific tools by name.", inputSchema: { type: "object", properties: { names: { type: "array", items: { type: "string" } } }, required: ["names"] } }
|
|
774
|
+
{ name: "describe_tools", description: "Get param hints for specific tools by name.", inputSchema: { type: "object", properties: { names: { type: "array", items: { type: "string" } } }, required: ["names"] } },
|
|
775
|
+
{ name: "get_goals", description: "All spending goals with current progress. No params.", inputSchema: { type: "object", properties: {} } },
|
|
776
|
+
{ name: "set_goal", description: "Create/update a spending goal. period(day|week|month|year), limit_usd, project_path?, agent?", inputSchema: { type: "object", properties: { period: { type: "string" }, limit_usd: { type: "number" }, project_path: { type: "string" }, agent: { type: "string" } }, required: ["period", "limit_usd"] } },
|
|
777
|
+
{ name: "remove_goal", description: "Delete a goal by id.", inputSchema: { type: "object", properties: { id: { type: "string" } }, required: ["id"] } },
|
|
778
|
+
{ name: "register_agent", description: "Register agent session.", inputSchema: { type: "object", properties: { name: { type: "string" }, session_id: { type: "string" } }, required: ["name"] } },
|
|
779
|
+
{ name: "heartbeat", description: "Update last_seen_at.", inputSchema: { type: "object", properties: { agent_id: { type: "string" } }, required: ["agent_id"] } },
|
|
780
|
+
{ name: "set_focus", description: "Set active project context.", inputSchema: { type: "object", properties: { agent_id: { type: "string" }, project_id: { type: "string" } }, required: ["agent_id"] } }
|
|
620
781
|
];
|
|
621
782
|
var TOOL_DESCRIPTIONS = {
|
|
622
|
-
get_cost_summary: "period(today|week|month|all) \u2192 {total_usd, sessions, requests, tokens, summary}",
|
|
783
|
+
get_cost_summary: "period(today|week|month|year|all) \u2192 {total_usd, sessions, requests, tokens, summary}",
|
|
623
784
|
get_sessions: "agent(claude|codex), project(partial), limit(20) \u2192 compact session table",
|
|
624
785
|
get_top_sessions: "n(10), agent(claude|codex) \u2192 top sessions by cost",
|
|
625
786
|
get_model_breakdown: "no params \u2192 model, requests, tokens, cost",
|
|
626
787
|
get_project_breakdown: "no params \u2192 project_name, sessions, cost",
|
|
627
788
|
get_budget_status: "no params \u2192 budget limits, current spend, percent_used, is_over_alert",
|
|
628
|
-
|
|
789
|
+
get_daily: "days(30) \u2192 daily cost table grouped by date and agent",
|
|
790
|
+
get_session_detail: "session_id(prefix ok) \u2192 per-request breakdown with model, tokens, cost",
|
|
791
|
+
sync: "sources(all|claude|codex|gemini) \u2192 {files, requests, sessions} ingested",
|
|
792
|
+
get_goals: "no params \u2192 period, scope, limit, spent, percent, status(ON TRACK/AT RISK/OVER)",
|
|
793
|
+
set_goal: "period(day|week|month|year), limit_usd, project_path?, agent? \u2192 creates/updates goal",
|
|
794
|
+
remove_goal: "id \u2192 deletes goal"
|
|
629
795
|
};
|
|
630
796
|
server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS }));
|
|
631
797
|
server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
@@ -709,6 +875,48 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
709
875
|
lines.push(`${scope.padEnd(21)}${String(b["period"]).padEnd(9)}${fmtUsd(Number(b["current_spend_usd"])).padEnd(11)}${fmtUsd(Number(b["limit_usd"])).padEnd(11)}${pct}%`.padEnd(49) + ` ${status}`);
|
|
710
876
|
}
|
|
711
877
|
return { content: [{ type: "text", text: lines.join(`
|
|
878
|
+
`) }] };
|
|
879
|
+
}
|
|
880
|
+
case "get_daily": {
|
|
881
|
+
const days = Number(a["days"] ?? 30);
|
|
882
|
+
const rows = queryDailyBreakdown(db, days);
|
|
883
|
+
const lines = ["date claude codex gemini total"];
|
|
884
|
+
const byDate = new Map;
|
|
885
|
+
for (const r of rows) {
|
|
886
|
+
const d = String(r["date"]);
|
|
887
|
+
const entry = byDate.get(d) ?? { claude: 0, codex: 0, gemini: 0 };
|
|
888
|
+
if (r["agent"] === "claude")
|
|
889
|
+
entry.claude += Number(r["cost_usd"]);
|
|
890
|
+
else if (r["agent"] === "codex")
|
|
891
|
+
entry.codex += Number(r["cost_usd"]);
|
|
892
|
+
else if (r["agent"] === "gemini")
|
|
893
|
+
entry.gemini += Number(r["cost_usd"]);
|
|
894
|
+
byDate.set(d, entry);
|
|
895
|
+
}
|
|
896
|
+
for (const [date, costs] of [...byDate.entries()].sort()) {
|
|
897
|
+
const total = costs.claude + costs.codex + costs.gemini;
|
|
898
|
+
lines.push(`${date} ${fmtUsd(costs.claude).padEnd(11)}${fmtUsd(costs.codex).padEnd(11)}${fmtUsd(costs.gemini).padEnd(11)}${fmtUsd(total)}`);
|
|
899
|
+
}
|
|
900
|
+
return { content: [{ type: "text", text: lines.join(`
|
|
901
|
+
`) }] };
|
|
902
|
+
}
|
|
903
|
+
case "get_session_detail": {
|
|
904
|
+
const sid = String(a["session_id"] ?? "");
|
|
905
|
+
const session = db.prepare(`SELECT * FROM sessions WHERE id = ? OR id LIKE ?`).get(sid, `${sid}%`);
|
|
906
|
+
if (!session)
|
|
907
|
+
return { content: [{ type: "text", text: `Session not found: ${sid}` }], isError: true };
|
|
908
|
+
const requests = db.prepare(`SELECT * FROM requests WHERE session_id = ? ORDER BY timestamp ASC LIMIT 50`).all(session["id"]);
|
|
909
|
+
const lines = [
|
|
910
|
+
`session: ${String(session["id"]).slice(0, 16)}`,
|
|
911
|
+
`agent: ${session["agent"]} project: ${session["project_name"] || "\u2014"}`,
|
|
912
|
+
`cost: ${fmtUsd(Number(session["total_cost_usd"]))} tokens: ${fmtTok(Number(session["total_tokens"]))} requests: ${session["request_count"]}`,
|
|
913
|
+
"",
|
|
914
|
+
"time model input output cost"
|
|
915
|
+
];
|
|
916
|
+
for (const r of requests) {
|
|
917
|
+
lines.push(`${String(r["timestamp"]).slice(11, 19)} ${String(r["model"]).slice(0, 22).padEnd(23)}${fmtTok(Number(r["input_tokens"])).padEnd(9)}${fmtTok(Number(r["output_tokens"])).padEnd(9)}${fmtUsd(Number(r["cost_usd"]))}`);
|
|
918
|
+
}
|
|
919
|
+
return { content: [{ type: "text", text: lines.join(`
|
|
712
920
|
`) }] };
|
|
713
921
|
}
|
|
714
922
|
case "sync": {
|
|
@@ -722,9 +930,71 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
722
930
|
const r = await ingestCodex(db);
|
|
723
931
|
parts.push(`codex: ${r["sessions"]} sessions`);
|
|
724
932
|
}
|
|
933
|
+
if (sources === "all" || sources === "gemini") {
|
|
934
|
+
const r = await ingestGemini(db);
|
|
935
|
+
parts.push(`gemini: ${r["sessions"]} sessions`);
|
|
936
|
+
}
|
|
725
937
|
return { content: [{ type: "text", text: parts.join(`
|
|
726
938
|
`) || "done" }] };
|
|
727
939
|
}
|
|
940
|
+
case "get_goals": {
|
|
941
|
+
const goals = getGoalStatuses(db);
|
|
942
|
+
if (goals.length === 0)
|
|
943
|
+
return { content: [{ type: "text", text: "No goals set." }] };
|
|
944
|
+
const lines = ["period scope limit spent used% status"];
|
|
945
|
+
for (const g of goals) {
|
|
946
|
+
const scope = String(g["project_path"] ?? g["agent"] ?? "global").slice(0, 20);
|
|
947
|
+
const pct = Number(g["percent_used"]).toFixed(1);
|
|
948
|
+
const status = g["is_over"] ? "OVER" : g["is_at_risk"] ? "AT RISK" : "ON TRACK";
|
|
949
|
+
lines.push(`${String(g["period"]).padEnd(9)}${scope.padEnd(21)}${fmtUsd(Number(g["limit_usd"])).padEnd(11)}${fmtUsd(Number(g["current_spend_usd"])).padEnd(11)}${pct}% ${status}`);
|
|
950
|
+
}
|
|
951
|
+
return { content: [{ type: "text", text: lines.join(`
|
|
952
|
+
`) }] };
|
|
953
|
+
}
|
|
954
|
+
case "set_goal": {
|
|
955
|
+
const { randomUUID } = await import("crypto");
|
|
956
|
+
const now = new Date().toISOString();
|
|
957
|
+
upsertGoal(db, {
|
|
958
|
+
id: randomUUID(),
|
|
959
|
+
period: String(a["period"] ?? "month"),
|
|
960
|
+
project_path: a["project_path"] ?? null,
|
|
961
|
+
agent: a["agent"] ?? null,
|
|
962
|
+
limit_usd: Number(a["limit_usd"]),
|
|
963
|
+
created_at: now,
|
|
964
|
+
updated_at: now
|
|
965
|
+
});
|
|
966
|
+
return { content: [{ type: "text", text: `Goal set: ${a["period"]} $${a["limit_usd"]}` }] };
|
|
967
|
+
}
|
|
968
|
+
case "remove_goal": {
|
|
969
|
+
deleteGoal(db, String(a["id"] ?? ""));
|
|
970
|
+
return { content: [{ type: "text", text: "Goal removed." }] };
|
|
971
|
+
}
|
|
972
|
+
case "register_agent": {
|
|
973
|
+
const n = String(args["name"] ?? "");
|
|
974
|
+
const ex = [..._econAgents.values()].find((x) => x.name === n);
|
|
975
|
+
if (ex) {
|
|
976
|
+
ex.last_seen_at = new Date().toISOString();
|
|
977
|
+
return { content: [{ type: "text", text: JSON.stringify(ex) }] };
|
|
978
|
+
}
|
|
979
|
+
const id = Math.random().toString(36).slice(2, 10);
|
|
980
|
+
const ag = { id, name: n, last_seen_at: new Date().toISOString() };
|
|
981
|
+
_econAgents.set(id, ag);
|
|
982
|
+
return { content: [{ type: "text", text: JSON.stringify(ag) }] };
|
|
983
|
+
}
|
|
984
|
+
case "heartbeat": {
|
|
985
|
+
const ag = _econAgents.get(String(args["agent_id"] ?? ""));
|
|
986
|
+
if (!ag)
|
|
987
|
+
return { content: [{ type: "text", text: `Agent not found` }], isError: true };
|
|
988
|
+
ag.last_seen_at = new Date().toISOString();
|
|
989
|
+
return { content: [{ type: "text", text: `\u2665 ${ag.name}` }] };
|
|
990
|
+
}
|
|
991
|
+
case "set_focus": {
|
|
992
|
+
const ag = _econAgents.get(String(args["agent_id"] ?? ""));
|
|
993
|
+
if (!ag)
|
|
994
|
+
return { content: [{ type: "text", text: `Agent not found` }], isError: true };
|
|
995
|
+
ag["project_id"] = args["project_id"];
|
|
996
|
+
return { content: [{ type: "text", text: String(args["project_id"] ? `Focus: ${args["project_id"]}` : "Focus cleared") }] };
|
|
997
|
+
}
|
|
728
998
|
default:
|
|
729
999
|
return { content: [{ type: "text", text: `Unknown tool: ${name}` }], isError: true };
|
|
730
1000
|
}
|
|
@@ -732,5 +1002,6 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
732
1002
|
return { content: [{ type: "text", text: `Error: ${e instanceof Error ? e.message : String(e)}` }], isError: true };
|
|
733
1003
|
}
|
|
734
1004
|
});
|
|
1005
|
+
var _econAgents = new Map;
|
|
735
1006
|
var transport = new StdioServerTransport;
|
|
736
1007
|
await server.connect(transport);
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"watch.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/watch.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,sBAAsB,CAAA;AAEjD,UAAU,YAAY;IACpB,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,CAAC,EAAE,KAAK,CAAA;CACd;AAuBD,wBAAsB,UAAU,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAiElE"}
|
package/dist/cli/index.d.ts
DELETED
package/dist/cli/index.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":""}
|