@cognistore/mcp-server 1.1.0 → 1.3.0
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/index.js +434 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -288,6 +288,34 @@ CREATE TABLE IF NOT EXISTS plan_tasks (
|
|
|
288
288
|
);
|
|
289
289
|
CREATE INDEX IF NOT EXISTS idx_plan_tasks_plan ON plan_tasks(plan_id);
|
|
290
290
|
CREATE INDEX IF NOT EXISTS idx_plan_tasks_status ON plan_tasks(status);
|
|
291
|
+
`,
|
|
292
|
+
"1.3.0": `
|
|
293
|
+
CREATE TABLE IF NOT EXISTS token_usage (
|
|
294
|
+
id TEXT PRIMARY KEY,
|
|
295
|
+
source TEXT NOT NULL,
|
|
296
|
+
model TEXT NOT NULL,
|
|
297
|
+
project TEXT,
|
|
298
|
+
session_id TEXT,
|
|
299
|
+
message_id TEXT,
|
|
300
|
+
occurred_at TEXT NOT NULL,
|
|
301
|
+
input_tokens INTEGER NOT NULL DEFAULT 0,
|
|
302
|
+
output_tokens INTEGER NOT NULL DEFAULT 0,
|
|
303
|
+
cache_read_tokens INTEGER NOT NULL DEFAULT 0,
|
|
304
|
+
cache_creation_tokens INTEGER NOT NULL DEFAULT 0,
|
|
305
|
+
scanned_at TEXT NOT NULL
|
|
306
|
+
);
|
|
307
|
+
CREATE INDEX IF NOT EXISTS idx_token_usage_occurred ON token_usage (occurred_at);
|
|
308
|
+
CREATE INDEX IF NOT EXISTS idx_token_usage_project ON token_usage (project);
|
|
309
|
+
CREATE INDEX IF NOT EXISTS idx_token_usage_source_model ON token_usage (source, model);
|
|
310
|
+
|
|
311
|
+
CREATE TABLE IF NOT EXISTS scan_state (
|
|
312
|
+
source TEXT NOT NULL,
|
|
313
|
+
file_path TEXT NOT NULL,
|
|
314
|
+
last_offset INTEGER NOT NULL DEFAULT 0,
|
|
315
|
+
last_mtime TEXT NOT NULL,
|
|
316
|
+
last_scanned_at TEXT NOT NULL,
|
|
317
|
+
PRIMARY KEY (source, file_path)
|
|
318
|
+
);
|
|
291
319
|
`
|
|
292
320
|
};
|
|
293
321
|
function runMigrations(sqlite, migrationsDir) {
|
|
@@ -407,6 +435,7 @@ function createPlansEmbeddingsTable(sqlite, dimensions = DEFAULT_EMBEDDING_DIMEN
|
|
|
407
435
|
|
|
408
436
|
// ../../packages/core/dist/repositories/knowledge.repository.js
|
|
409
437
|
import { eq, ne, sql, and, or, isNull } from "drizzle-orm";
|
|
438
|
+
var OPERATIONS_RETENTION_DAYS = 30;
|
|
410
439
|
var KnowledgeRepository = class {
|
|
411
440
|
db;
|
|
412
441
|
sqlite;
|
|
@@ -796,7 +825,7 @@ var KnowledgeRepository = class {
|
|
|
796
825
|
return Object.entries(map).map(([date, counts]) => ({ date, ...counts }));
|
|
797
826
|
}
|
|
798
827
|
cleanupOldOperations() {
|
|
799
|
-
const cutoff = new Date(Date.now() -
|
|
828
|
+
const cutoff = new Date(Date.now() - OPERATIONS_RETENTION_DAYS * 24 * 60 * 60 * 1e3).toISOString();
|
|
800
829
|
return this.sqlite.prepare("DELETE FROM operations_log WHERE created_at < ?").run(cutoff).changes;
|
|
801
830
|
}
|
|
802
831
|
/**
|
|
@@ -1287,6 +1316,371 @@ var KnowledgeService = class {
|
|
|
1287
1316
|
}
|
|
1288
1317
|
};
|
|
1289
1318
|
|
|
1319
|
+
// ../../packages/core/dist/repositories/token-usage.repository.js
|
|
1320
|
+
var RANGE_CLAUSE = "occurred_at >= ? AND occurred_at <= ?";
|
|
1321
|
+
function applyOptionalFilters(filter) {
|
|
1322
|
+
const params = [filter.from, filter.to];
|
|
1323
|
+
let sql2 = RANGE_CLAUSE;
|
|
1324
|
+
if (filter.source) {
|
|
1325
|
+
sql2 += " AND source = ?";
|
|
1326
|
+
params.push(filter.source);
|
|
1327
|
+
}
|
|
1328
|
+
if (filter.model) {
|
|
1329
|
+
sql2 += " AND model = ?";
|
|
1330
|
+
params.push(filter.model);
|
|
1331
|
+
}
|
|
1332
|
+
if (filter.project) {
|
|
1333
|
+
sql2 += " AND project = ?";
|
|
1334
|
+
params.push(filter.project);
|
|
1335
|
+
}
|
|
1336
|
+
return { sql: sql2, params };
|
|
1337
|
+
}
|
|
1338
|
+
var TokenUsageRepository = class {
|
|
1339
|
+
sqlite;
|
|
1340
|
+
constructor(sqlite) {
|
|
1341
|
+
this.sqlite = sqlite;
|
|
1342
|
+
}
|
|
1343
|
+
// ─── Writes ────────────────────────────────────────────────────
|
|
1344
|
+
/** Insert if new; never throw on conflict (deterministic id makes this idempotent). */
|
|
1345
|
+
insertMany(records) {
|
|
1346
|
+
if (records.length === 0)
|
|
1347
|
+
return 0;
|
|
1348
|
+
const stmt = this.sqlite.prepare(`
|
|
1349
|
+
INSERT OR IGNORE INTO token_usage
|
|
1350
|
+
(id, source, model, project, session_id, message_id, occurred_at,
|
|
1351
|
+
input_tokens, output_tokens, cache_read_tokens, cache_creation_tokens, scanned_at)
|
|
1352
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1353
|
+
`);
|
|
1354
|
+
const scannedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1355
|
+
const tx = this.sqlite.transaction((rows) => {
|
|
1356
|
+
let inserted = 0;
|
|
1357
|
+
for (const r of rows) {
|
|
1358
|
+
const info = stmt.run(r.id, r.source, r.model, r.project, r.sessionId, r.messageId, r.occurredAt, r.inputTokens, r.outputTokens, r.cacheReadTokens, r.cacheCreationTokens, scannedAt);
|
|
1359
|
+
if (info.changes > 0)
|
|
1360
|
+
inserted++;
|
|
1361
|
+
}
|
|
1362
|
+
return inserted;
|
|
1363
|
+
});
|
|
1364
|
+
return tx(records);
|
|
1365
|
+
}
|
|
1366
|
+
// ─── Scan state ────────────────────────────────────────────────
|
|
1367
|
+
getScanState(source, filePath) {
|
|
1368
|
+
const row = this.sqlite.prepare(`
|
|
1369
|
+
SELECT source, file_path as filePath, last_offset as lastOffset, last_mtime as lastMtime
|
|
1370
|
+
FROM scan_state WHERE source = ? AND file_path = ?
|
|
1371
|
+
`).get(source, filePath);
|
|
1372
|
+
return row;
|
|
1373
|
+
}
|
|
1374
|
+
upsertScanState(state) {
|
|
1375
|
+
this.sqlite.prepare(`
|
|
1376
|
+
INSERT INTO scan_state (source, file_path, last_offset, last_mtime, last_scanned_at)
|
|
1377
|
+
VALUES (?, ?, ?, ?, ?)
|
|
1378
|
+
ON CONFLICT(source, file_path) DO UPDATE SET
|
|
1379
|
+
last_offset = excluded.last_offset,
|
|
1380
|
+
last_mtime = excluded.last_mtime,
|
|
1381
|
+
last_scanned_at = excluded.last_scanned_at
|
|
1382
|
+
`).run(state.source, state.filePath, state.lastOffset, state.lastMtime, (/* @__PURE__ */ new Date()).toISOString());
|
|
1383
|
+
}
|
|
1384
|
+
// ─── Aggregations ──────────────────────────────────────────────
|
|
1385
|
+
totals(filter) {
|
|
1386
|
+
const { sql: sql2, params } = applyOptionalFilters(filter);
|
|
1387
|
+
const row = this.sqlite.prepare(`
|
|
1388
|
+
SELECT
|
|
1389
|
+
COALESCE(SUM(input_tokens), 0) as inputTokens,
|
|
1390
|
+
COALESCE(SUM(output_tokens), 0) as outputTokens,
|
|
1391
|
+
COALESCE(SUM(cache_read_tokens), 0) as cacheReadTokens,
|
|
1392
|
+
COALESCE(SUM(cache_creation_tokens), 0) as cacheCreationTokens
|
|
1393
|
+
FROM token_usage WHERE ${sql2}
|
|
1394
|
+
`).get(...params);
|
|
1395
|
+
return row;
|
|
1396
|
+
}
|
|
1397
|
+
byDay(filter) {
|
|
1398
|
+
const { sql: sql2, params } = applyOptionalFilters(filter);
|
|
1399
|
+
const rows = this.sqlite.prepare(`
|
|
1400
|
+
SELECT
|
|
1401
|
+
date(occurred_at) as date,
|
|
1402
|
+
COALESCE(SUM(input_tokens), 0) as inputTokens,
|
|
1403
|
+
COALESCE(SUM(output_tokens), 0) as outputTokens,
|
|
1404
|
+
COALESCE(SUM(cache_read_tokens), 0) as cacheReadTokens,
|
|
1405
|
+
COALESCE(SUM(cache_creation_tokens), 0) as cacheCreationTokens
|
|
1406
|
+
FROM token_usage WHERE ${sql2}
|
|
1407
|
+
GROUP BY date(occurred_at)
|
|
1408
|
+
ORDER BY date(occurred_at)
|
|
1409
|
+
`).all(...params);
|
|
1410
|
+
return rows;
|
|
1411
|
+
}
|
|
1412
|
+
byModel(filter) {
|
|
1413
|
+
const { sql: sql2, params } = applyOptionalFilters(filter);
|
|
1414
|
+
const rows = this.sqlite.prepare(`
|
|
1415
|
+
SELECT
|
|
1416
|
+
model,
|
|
1417
|
+
COALESCE(SUM(input_tokens), 0) as inputTokens,
|
|
1418
|
+
COALESCE(SUM(output_tokens), 0) as outputTokens,
|
|
1419
|
+
COALESCE(SUM(cache_read_tokens), 0) as cacheReadTokens,
|
|
1420
|
+
COALESCE(SUM(cache_creation_tokens), 0) as cacheCreationTokens,
|
|
1421
|
+
COALESCE(SUM(input_tokens + output_tokens + cache_read_tokens + cache_creation_tokens), 0) as totalTokens
|
|
1422
|
+
FROM token_usage WHERE ${sql2}
|
|
1423
|
+
GROUP BY model
|
|
1424
|
+
ORDER BY totalTokens DESC
|
|
1425
|
+
`).all(...params);
|
|
1426
|
+
return rows;
|
|
1427
|
+
}
|
|
1428
|
+
byProject(filter) {
|
|
1429
|
+
const { sql: sql2, params } = applyOptionalFilters(filter);
|
|
1430
|
+
const rows = this.sqlite.prepare(`
|
|
1431
|
+
SELECT
|
|
1432
|
+
COALESCE(project, '(unknown)') as project,
|
|
1433
|
+
COALESCE(SUM(input_tokens), 0) as inputTokens,
|
|
1434
|
+
COALESCE(SUM(output_tokens), 0) as outputTokens,
|
|
1435
|
+
COALESCE(SUM(cache_read_tokens), 0) as cacheReadTokens,
|
|
1436
|
+
COALESCE(SUM(cache_creation_tokens), 0) as cacheCreationTokens,
|
|
1437
|
+
COALESCE(SUM(input_tokens + output_tokens + cache_read_tokens + cache_creation_tokens), 0) as totalTokens
|
|
1438
|
+
FROM token_usage WHERE ${sql2}
|
|
1439
|
+
GROUP BY project
|
|
1440
|
+
ORDER BY totalTokens DESC
|
|
1441
|
+
`).all(...params);
|
|
1442
|
+
return rows;
|
|
1443
|
+
}
|
|
1444
|
+
byHourDay(filter) {
|
|
1445
|
+
const { sql: sql2, params } = applyOptionalFilters(filter);
|
|
1446
|
+
const rows = this.sqlite.prepare(`
|
|
1447
|
+
SELECT
|
|
1448
|
+
CAST(strftime('%w', occurred_at) AS INTEGER) as dayOfWeek,
|
|
1449
|
+
CAST(strftime('%H', occurred_at) AS INTEGER) as hour,
|
|
1450
|
+
COALESCE(SUM(input_tokens + output_tokens + cache_read_tokens + cache_creation_tokens), 0) as totalTokens
|
|
1451
|
+
FROM token_usage WHERE ${sql2}
|
|
1452
|
+
GROUP BY dayOfWeek, hour
|
|
1453
|
+
`).all(...params);
|
|
1454
|
+
return rows;
|
|
1455
|
+
}
|
|
1456
|
+
topSessions(filter, limit = 20) {
|
|
1457
|
+
const { sql: sql2, params } = applyOptionalFilters(filter);
|
|
1458
|
+
const rows = this.sqlite.prepare(`
|
|
1459
|
+
SELECT
|
|
1460
|
+
session_id as sessionId,
|
|
1461
|
+
MAX(project) as project,
|
|
1462
|
+
MAX(model) as model,
|
|
1463
|
+
MIN(occurred_at) as startedAt,
|
|
1464
|
+
MAX(occurred_at) as endedAt,
|
|
1465
|
+
COUNT(*) as messageCount,
|
|
1466
|
+
COALESCE(SUM(input_tokens + output_tokens + cache_read_tokens + cache_creation_tokens), 0) as totalTokens
|
|
1467
|
+
FROM token_usage WHERE ${sql2} AND session_id IS NOT NULL
|
|
1468
|
+
GROUP BY session_id
|
|
1469
|
+
ORDER BY totalTokens DESC
|
|
1470
|
+
LIMIT ?
|
|
1471
|
+
`).all(...params, limit);
|
|
1472
|
+
return rows;
|
|
1473
|
+
}
|
|
1474
|
+
};
|
|
1475
|
+
|
|
1476
|
+
// ../../packages/core/dist/services/token-usage/scanner.js
|
|
1477
|
+
var BATCH_SIZE = 200;
|
|
1478
|
+
var TokenUsageScanner = class {
|
|
1479
|
+
repo;
|
|
1480
|
+
adapters;
|
|
1481
|
+
constructor(repo, adapters) {
|
|
1482
|
+
this.repo = repo;
|
|
1483
|
+
this.adapters = adapters;
|
|
1484
|
+
}
|
|
1485
|
+
async scanAll() {
|
|
1486
|
+
const bySource = {};
|
|
1487
|
+
let totalInserted = 0;
|
|
1488
|
+
let totalScanned = 0;
|
|
1489
|
+
for (const adapter of this.adapters) {
|
|
1490
|
+
const stats = { inserted: 0, scanned: 0 };
|
|
1491
|
+
bySource[adapter.name] = stats;
|
|
1492
|
+
let batch = [];
|
|
1493
|
+
const offsets = /* @__PURE__ */ new Map();
|
|
1494
|
+
const flush = () => {
|
|
1495
|
+
if (batch.length === 0)
|
|
1496
|
+
return;
|
|
1497
|
+
stats.inserted += this.repo.insertMany(batch);
|
|
1498
|
+
batch = [];
|
|
1499
|
+
};
|
|
1500
|
+
for await (const { record, filePath, byteOffset, mtime } of adapter.scan((fp) => this.repo.getScanState(adapter.name, fp))) {
|
|
1501
|
+
batch.push(record);
|
|
1502
|
+
offsets.set(filePath, { offset: byteOffset, mtime });
|
|
1503
|
+
stats.scanned++;
|
|
1504
|
+
if (batch.length >= BATCH_SIZE)
|
|
1505
|
+
flush();
|
|
1506
|
+
}
|
|
1507
|
+
flush();
|
|
1508
|
+
for (const [filePath, { offset, mtime }] of offsets) {
|
|
1509
|
+
this.repo.upsertScanState({
|
|
1510
|
+
source: adapter.name,
|
|
1511
|
+
filePath,
|
|
1512
|
+
lastOffset: offset,
|
|
1513
|
+
lastMtime: mtime
|
|
1514
|
+
});
|
|
1515
|
+
}
|
|
1516
|
+
totalInserted += stats.inserted;
|
|
1517
|
+
totalScanned += stats.scanned;
|
|
1518
|
+
}
|
|
1519
|
+
return { inserted: totalInserted, scanned: totalScanned, bySource };
|
|
1520
|
+
}
|
|
1521
|
+
};
|
|
1522
|
+
|
|
1523
|
+
// ../../packages/core/dist/services/token-usage/adapters/claude-code.js
|
|
1524
|
+
import { createHash } from "crypto";
|
|
1525
|
+
import { existsSync as existsSync2, readdirSync as readdirSync2, statSync, openSync, readSync, closeSync } from "fs";
|
|
1526
|
+
import { homedir as homedir2 } from "os";
|
|
1527
|
+
import { resolve as resolve3, basename } from "path";
|
|
1528
|
+
var SOURCE = "claude-code";
|
|
1529
|
+
function decodeProject(folderName) {
|
|
1530
|
+
const trimmed = folderName.replace(/^-+/, "");
|
|
1531
|
+
const parts = trimmed.split("-").filter(Boolean);
|
|
1532
|
+
return parts[parts.length - 1] ?? folderName;
|
|
1533
|
+
}
|
|
1534
|
+
function recordId(sessionId, messageId, occurredAt) {
|
|
1535
|
+
const key = `${SOURCE}|${sessionId ?? ""}|${messageId ?? ""}|${occurredAt}`;
|
|
1536
|
+
return createHash("sha256").update(key).digest("hex").slice(0, 32);
|
|
1537
|
+
}
|
|
1538
|
+
function* readNewLines(filePath, fromOffset, fileSize) {
|
|
1539
|
+
if (fromOffset >= fileSize)
|
|
1540
|
+
return;
|
|
1541
|
+
const fd = openSync(filePath, "r");
|
|
1542
|
+
try {
|
|
1543
|
+
const CHUNK = 64 * 1024;
|
|
1544
|
+
const buf = Buffer.alloc(CHUNK);
|
|
1545
|
+
let offset = fromOffset;
|
|
1546
|
+
let pending = "";
|
|
1547
|
+
while (offset < fileSize) {
|
|
1548
|
+
const toRead = Math.min(CHUNK, fileSize - offset);
|
|
1549
|
+
const bytesRead = readSync(fd, buf, 0, toRead, offset);
|
|
1550
|
+
if (bytesRead === 0)
|
|
1551
|
+
break;
|
|
1552
|
+
pending += buf.subarray(0, bytesRead).toString("utf8");
|
|
1553
|
+
offset += bytesRead;
|
|
1554
|
+
let nl;
|
|
1555
|
+
let pendingStart = 0;
|
|
1556
|
+
while ((nl = pending.indexOf("\n", pendingStart)) !== -1) {
|
|
1557
|
+
const line = pending.slice(pendingStart, nl);
|
|
1558
|
+
pendingStart = nl + 1;
|
|
1559
|
+
if (line.length > 0) {
|
|
1560
|
+
yield { line, byteOffsetAfter: -1 };
|
|
1561
|
+
}
|
|
1562
|
+
}
|
|
1563
|
+
pending = pending.slice(pendingStart);
|
|
1564
|
+
}
|
|
1565
|
+
void pending;
|
|
1566
|
+
} finally {
|
|
1567
|
+
closeSync(fd);
|
|
1568
|
+
}
|
|
1569
|
+
}
|
|
1570
|
+
function parseUsageLine(line) {
|
|
1571
|
+
let obj;
|
|
1572
|
+
try {
|
|
1573
|
+
obj = JSON.parse(line);
|
|
1574
|
+
} catch {
|
|
1575
|
+
return null;
|
|
1576
|
+
}
|
|
1577
|
+
if (!obj || typeof obj !== "object")
|
|
1578
|
+
return null;
|
|
1579
|
+
const message = obj.message ?? obj;
|
|
1580
|
+
const usage = message?.usage;
|
|
1581
|
+
if (!usage || typeof usage !== "object")
|
|
1582
|
+
return null;
|
|
1583
|
+
const occurredAt = obj.timestamp ?? message?.timestamp;
|
|
1584
|
+
if (!occurredAt)
|
|
1585
|
+
return null;
|
|
1586
|
+
const model = message?.model ?? "unknown";
|
|
1587
|
+
const sessionId = obj.sessionId ?? message?.sessionId ?? null;
|
|
1588
|
+
const messageId = message?.id ?? obj.uuid ?? null;
|
|
1589
|
+
return {
|
|
1590
|
+
source: SOURCE,
|
|
1591
|
+
model,
|
|
1592
|
+
sessionId,
|
|
1593
|
+
messageId,
|
|
1594
|
+
occurredAt,
|
|
1595
|
+
inputTokens: Number(usage.input_tokens ?? 0) || 0,
|
|
1596
|
+
outputTokens: Number(usage.output_tokens ?? 0) || 0,
|
|
1597
|
+
cacheReadTokens: Number(usage.cache_read_input_tokens ?? 0) || 0,
|
|
1598
|
+
cacheCreationTokens: Number(usage.cache_creation_input_tokens ?? 0) || 0
|
|
1599
|
+
};
|
|
1600
|
+
}
|
|
1601
|
+
var ClaudeCodeAdapter = class {
|
|
1602
|
+
name = SOURCE;
|
|
1603
|
+
projectsDir;
|
|
1604
|
+
constructor(options = {}) {
|
|
1605
|
+
this.projectsDir = options.projectsDir ?? resolve3(homedir2(), ".claude", "projects");
|
|
1606
|
+
}
|
|
1607
|
+
async *scan(getState) {
|
|
1608
|
+
if (!existsSync2(this.projectsDir))
|
|
1609
|
+
return;
|
|
1610
|
+
for (const folder of readdirSync2(this.projectsDir)) {
|
|
1611
|
+
const projectDir = resolve3(this.projectsDir, folder);
|
|
1612
|
+
let stat;
|
|
1613
|
+
try {
|
|
1614
|
+
stat = statSync(projectDir);
|
|
1615
|
+
} catch {
|
|
1616
|
+
continue;
|
|
1617
|
+
}
|
|
1618
|
+
if (!stat.isDirectory())
|
|
1619
|
+
continue;
|
|
1620
|
+
const project = decodeProject(basename(folder));
|
|
1621
|
+
let files;
|
|
1622
|
+
try {
|
|
1623
|
+
files = readdirSync2(projectDir);
|
|
1624
|
+
} catch {
|
|
1625
|
+
continue;
|
|
1626
|
+
}
|
|
1627
|
+
for (const fileName of files) {
|
|
1628
|
+
if (!fileName.endsWith(".jsonl"))
|
|
1629
|
+
continue;
|
|
1630
|
+
const filePath = resolve3(projectDir, fileName);
|
|
1631
|
+
let fstat;
|
|
1632
|
+
try {
|
|
1633
|
+
fstat = statSync(filePath);
|
|
1634
|
+
} catch {
|
|
1635
|
+
continue;
|
|
1636
|
+
}
|
|
1637
|
+
const fileSize = fstat.size;
|
|
1638
|
+
const mtime = fstat.mtime.toISOString();
|
|
1639
|
+
const state = getState(filePath);
|
|
1640
|
+
const fromOffset = state?.lastOffset ?? 0;
|
|
1641
|
+
if (fromOffset >= fileSize)
|
|
1642
|
+
continue;
|
|
1643
|
+
for (const { line } of readNewLines(filePath, fromOffset, fileSize)) {
|
|
1644
|
+
const parsed = parseUsageLine(line);
|
|
1645
|
+
if (!parsed)
|
|
1646
|
+
continue;
|
|
1647
|
+
const record = {
|
|
1648
|
+
...parsed,
|
|
1649
|
+
id: recordId(parsed.sessionId, parsed.messageId, parsed.occurredAt),
|
|
1650
|
+
project
|
|
1651
|
+
};
|
|
1652
|
+
yield { record, filePath, byteOffset: fileSize, mtime };
|
|
1653
|
+
}
|
|
1654
|
+
}
|
|
1655
|
+
}
|
|
1656
|
+
}
|
|
1657
|
+
};
|
|
1658
|
+
|
|
1659
|
+
// ../../packages/core/dist/services/token-usage/service.js
|
|
1660
|
+
var DEFAULT_TOP_SESSIONS = 20;
|
|
1661
|
+
var TokenUsageService = class {
|
|
1662
|
+
repo;
|
|
1663
|
+
scanner;
|
|
1664
|
+
constructor(repo, adapters) {
|
|
1665
|
+
this.repo = repo;
|
|
1666
|
+
this.scanner = new TokenUsageScanner(repo, adapters ?? [new ClaudeCodeAdapter()]);
|
|
1667
|
+
}
|
|
1668
|
+
scan() {
|
|
1669
|
+
return this.scanner.scanAll();
|
|
1670
|
+
}
|
|
1671
|
+
getAggregates(filter) {
|
|
1672
|
+
const totals = this.repo.totals(filter);
|
|
1673
|
+
const byDay = this.repo.byDay(filter);
|
|
1674
|
+
const byModel = this.repo.byModel(filter);
|
|
1675
|
+
const byProject = this.repo.byProject(filter);
|
|
1676
|
+
const byHourDay = this.repo.byHourDay(filter);
|
|
1677
|
+
const topSessions = this.repo.topSessions(filter, DEFAULT_TOP_SESSIONS);
|
|
1678
|
+
const cacheDenom = totals.inputTokens + totals.cacheReadTokens + totals.cacheCreationTokens;
|
|
1679
|
+
const cacheEfficiency = cacheDenom > 0 ? totals.cacheReadTokens / cacheDenom : 0;
|
|
1680
|
+
return { totals, byDay, byModel, byProject, byHourDay, topSessions, cacheEfficiency };
|
|
1681
|
+
}
|
|
1682
|
+
};
|
|
1683
|
+
|
|
1290
1684
|
// ../../packages/embeddings/dist/client.js
|
|
1291
1685
|
var OllamaEmbeddingClient = class {
|
|
1292
1686
|
host;
|
|
@@ -1493,6 +1887,7 @@ var KnowledgeSDK = class {
|
|
|
1493
1887
|
db = null;
|
|
1494
1888
|
sqlite = null;
|
|
1495
1889
|
service = null;
|
|
1890
|
+
tokenService = null;
|
|
1496
1891
|
ollamaClient;
|
|
1497
1892
|
initialized = false;
|
|
1498
1893
|
constructor(config) {
|
|
@@ -1522,6 +1917,8 @@ var KnowledgeSDK = class {
|
|
|
1522
1917
|
await this.detectAndMigrateDimensions();
|
|
1523
1918
|
const repository = new KnowledgeRepository(this.db, this.sqlite);
|
|
1524
1919
|
this.service = new KnowledgeService(repository, this.ollamaClient);
|
|
1920
|
+
const tokenRepo = new TokenUsageRepository(this.sqlite);
|
|
1921
|
+
this.tokenService = new TokenUsageService(tokenRepo);
|
|
1525
1922
|
this.initialized = true;
|
|
1526
1923
|
} catch (error) {
|
|
1527
1924
|
await this.cleanup();
|
|
@@ -1780,6 +2177,15 @@ var KnowledgeSDK = class {
|
|
|
1780
2177
|
return 0;
|
|
1781
2178
|
return this.service.cleanupOldOperations();
|
|
1782
2179
|
}
|
|
2180
|
+
// ─── Token usage ────────────────────────────────────────────
|
|
2181
|
+
async scanTokenUsage() {
|
|
2182
|
+
this.ensureInitialized();
|
|
2183
|
+
return this.tokenService.scan();
|
|
2184
|
+
}
|
|
2185
|
+
getTokenUsage(filter) {
|
|
2186
|
+
this.ensureInitialized();
|
|
2187
|
+
return this.tokenService.getAggregates(filter);
|
|
2188
|
+
}
|
|
1783
2189
|
cleanupCompletedPlanEmbeddings(maxAgeDays = 30) {
|
|
1784
2190
|
if (!this.initialized || !this.service)
|
|
1785
2191
|
return 0;
|
|
@@ -1856,6 +2262,7 @@ var KnowledgeSDK = class {
|
|
|
1856
2262
|
}
|
|
1857
2263
|
this.db = null;
|
|
1858
2264
|
this.service = null;
|
|
2265
|
+
this.tokenService = null;
|
|
1859
2266
|
}
|
|
1860
2267
|
/**
|
|
1861
2268
|
* Detect if stored embeddings have different dimensions than config.
|
|
@@ -2081,6 +2488,32 @@ function createServer(sdk) {
|
|
|
2081
2488
|
return { content: [{ type: "text", text: JSON.stringify(health, null, 2) }] };
|
|
2082
2489
|
}
|
|
2083
2490
|
);
|
|
2491
|
+
server.tool(
|
|
2492
|
+
"getTokenUsage",
|
|
2493
|
+
"Aggregated token usage for AI coding tools (input/output/cache reads/cache writes) for a date range, optionally filtered by source, model, or project.",
|
|
2494
|
+
{
|
|
2495
|
+
from: z2.string().describe('ISO date \u2014 start of range (e.g. "2025-05-01T00:00:00Z")'),
|
|
2496
|
+
to: z2.string().describe("ISO date \u2014 end of range"),
|
|
2497
|
+
source: z2.string().optional().describe('Filter by source (e.g. "claude-code")'),
|
|
2498
|
+
model: z2.string().optional().describe("Filter by model"),
|
|
2499
|
+
project: z2.string().optional().describe("Filter by project (decoded cwd basename)")
|
|
2500
|
+
},
|
|
2501
|
+
READ_ONLY,
|
|
2502
|
+
async (params) => {
|
|
2503
|
+
try {
|
|
2504
|
+
await sdk.scanTokenUsage();
|
|
2505
|
+
} catch {
|
|
2506
|
+
}
|
|
2507
|
+
const result = sdk.getTokenUsage({
|
|
2508
|
+
from: params.from,
|
|
2509
|
+
to: params.to,
|
|
2510
|
+
source: params.source,
|
|
2511
|
+
model: params.model,
|
|
2512
|
+
project: params.project
|
|
2513
|
+
});
|
|
2514
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
2515
|
+
}
|
|
2516
|
+
);
|
|
2084
2517
|
server.tool(
|
|
2085
2518
|
"createPlan",
|
|
2086
2519
|
"Create a plan with tasks. Plan auto-activates when the first task starts. Returns planId \u2014 SAVE IT and pass to addKnowledge calls.",
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cognistore/mcp-server",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "MCP server for CogniStore — integrates with Claude Code and GitHub Copilot",
|
|
7
|
-
"license": "
|
|
7
|
+
"license": "MIT",
|
|
8
8
|
"repository": {
|
|
9
9
|
"type": "git",
|
|
10
10
|
"url": "git+https://github.com/Sithion/cognistore.git",
|