@agentistics/mcp 1.3.3 → 1.7.6
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/README.md +7 -5
- package/dist/agentistics-mcp.js +384 -71
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@ MCP server that exposes your [Claude Code](https://claude.ai/code) analytics as
|
|
|
4
4
|
|
|
5
5
|
## What it does
|
|
6
6
|
|
|
7
|
-
Reads data from `~/.claude/` (the same data [agentistics](https://github.com/blpsoares/agentistics) visualizes) and exposes it as
|
|
7
|
+
Reads data from `~/.claude/` (the same data [agentistics](https://github.com/blpsoares/agentistics) visualizes) and exposes it as 13 structured MCP tools.
|
|
8
8
|
|
|
9
9
|
## Usage
|
|
10
10
|
|
|
@@ -36,10 +36,11 @@ The `AGENTISTICS_API` env var must point to a running agentistics server.
|
|
|
36
36
|
|
|
37
37
|
| Tool | Returns |
|
|
38
38
|
|------|---------|
|
|
39
|
-
| `agentistics_summary` | All-time totals: tokens, cost, sessions, streak
|
|
40
|
-
| `
|
|
41
|
-
| `
|
|
42
|
-
| `
|
|
39
|
+
| `agentistics_summary` | All-time totals: tokens, cost, sessions, streak — unified or per-harness |
|
|
40
|
+
| `agentistics_harnesses` | Side-by-side comparison of every tracked harness |
|
|
41
|
+
| `agentistics_projects` | Per-project token and cost breakdown (optionally per-harness) |
|
|
42
|
+
| `agentistics_sessions` | Recent sessions with harness, duration, model, cost |
|
|
43
|
+
| `agentistics_costs` | Model pricing breakdown and cache savings (optionally per-harness) |
|
|
43
44
|
| `agentistics_component_catalog` | Available dashboard components |
|
|
44
45
|
| `agentistics_get_layouts` | Current custom page layouts |
|
|
45
46
|
| `agentistics_build_layout` | Create a full layout from a component list |
|
|
@@ -48,6 +49,7 @@ The `AGENTISTICS_API` env var must point to a running agentistics server.
|
|
|
48
49
|
| `agentistics_create_layout` | Create a new empty layout |
|
|
49
50
|
| `agentistics_set_active_layout` | Switch the active /custom layout |
|
|
50
51
|
| `agentistics_delete_layout` | Delete a layout permanently |
|
|
52
|
+
| `agentistics_export_pdf` | Generate a PDF report download link for a date range |
|
|
51
53
|
|
|
52
54
|
## Requirements
|
|
53
55
|
|
package/dist/agentistics-mcp.js
CHANGED
|
@@ -8,9 +8,38 @@ import {
|
|
|
8
8
|
ListToolsRequestSchema
|
|
9
9
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
10
10
|
|
|
11
|
+
// ../core/src/local-models.ts
|
|
12
|
+
var LOCAL_RUNTIME_PREFIXES = [
|
|
13
|
+
"ollama",
|
|
14
|
+
"lmstudio",
|
|
15
|
+
"llamacpp",
|
|
16
|
+
"llama.cpp",
|
|
17
|
+
"local/",
|
|
18
|
+
"localhost"
|
|
19
|
+
];
|
|
20
|
+
function isLocalModelId(modelId) {
|
|
21
|
+
const id = String(modelId ?? "").toLowerCase();
|
|
22
|
+
return LOCAL_RUNTIME_PREFIXES.some((p) => id.startsWith(p));
|
|
23
|
+
}
|
|
24
|
+
var LOCAL_MODEL_PRICE = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
|
|
25
|
+
|
|
11
26
|
// ../core/src/types.ts
|
|
27
|
+
var HARNESS_SORT = {
|
|
28
|
+
claude: 0,
|
|
29
|
+
codex: 1,
|
|
30
|
+
gemini: 2,
|
|
31
|
+
copilot: 3,
|
|
32
|
+
antigravity: 4,
|
|
33
|
+
kimi: 5
|
|
34
|
+
};
|
|
35
|
+
var HARNESS_ORDER = Object.keys(HARNESS_SORT).sort((a, b) => HARNESS_SORT[a] - HARNESS_SORT[b]);
|
|
12
36
|
var MODEL_PRICING = {
|
|
37
|
+
"claude-fable-5": { input: 10, output: 50, cacheRead: 1, cacheWrite: 12.5 },
|
|
38
|
+
"claude-mythos-5": { input: 10, output: 50, cacheRead: 1, cacheWrite: 12.5 },
|
|
39
|
+
"claude-opus-5": { input: 5, output: 25, cacheRead: 0.5, cacheWrite: 6.25 },
|
|
40
|
+
"claude-opus-4-8": { input: 5, output: 25, cacheRead: 0.5, cacheWrite: 6.25 },
|
|
13
41
|
"claude-opus-4-7": { input: 5, output: 25, cacheRead: 0.5, cacheWrite: 6.25 },
|
|
42
|
+
"claude-sonnet-5": { input: 2, output: 10, cacheRead: 0.2, cacheWrite: 2.5 },
|
|
14
43
|
"claude-opus-4-6": { input: 5, output: 25, cacheRead: 0.5, cacheWrite: 6.25 },
|
|
15
44
|
"claude-sonnet-4-6": { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 },
|
|
16
45
|
"claude-haiku-4-5-20251001": { input: 1, output: 5, cacheRead: 0.1, cacheWrite: 1.25 },
|
|
@@ -20,21 +49,57 @@ var MODEL_PRICING = {
|
|
|
20
49
|
"claude-sonnet-4-5-20250929": { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 },
|
|
21
50
|
"claude-sonnet-4-20250514": { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 },
|
|
22
51
|
"claude-haiku-3-5-20241022": { input: 0.8, output: 4, cacheRead: 0.08, cacheWrite: 1 },
|
|
23
|
-
"claude-3-haiku-20240307": { input: 0.25, output: 1.25, cacheRead: 0.03, cacheWrite: 0.3 }
|
|
52
|
+
"claude-3-haiku-20240307": { input: 0.25, output: 1.25, cacheRead: 0.03, cacheWrite: 0.3 },
|
|
53
|
+
"gemini-3.6-flash": { input: 1.5, output: 7.5, cacheRead: 0.15, cacheWrite: 1.5 },
|
|
54
|
+
"gemini-3.5-flash-lite": { input: 0.3, output: 2.5, cacheRead: 0.03, cacheWrite: 0.3 },
|
|
55
|
+
"gemini-3.5-flash": { input: 1.5, output: 9, cacheRead: 0.15, cacheWrite: 1.5 },
|
|
56
|
+
"gemini-3.1-flash-lite": { input: 0.25, output: 1.5, cacheRead: 0.025, cacheWrite: 0.25 },
|
|
57
|
+
"gemini-3.1-pro": { input: 2, output: 12, cacheRead: 0.2, cacheWrite: 2 },
|
|
58
|
+
"gemini-3-flash-preview": { input: 0.5, output: 3, cacheRead: 0.05, cacheWrite: 0.5 },
|
|
59
|
+
"gemini-3-flash": { input: 0.5, output: 3, cacheRead: 0.05, cacheWrite: 0.5 },
|
|
60
|
+
"gemini-2.5-flash": { input: 0.3, output: 2.5, cacheRead: 0.03, cacheWrite: 0.3 },
|
|
61
|
+
"gpt-5.6-sol": { input: 5, output: 30, cacheRead: 0.5, cacheWrite: 5 },
|
|
62
|
+
"gpt-5.6-terra": { input: 2.5, output: 15, cacheRead: 0.25, cacheWrite: 2.5 },
|
|
63
|
+
"gpt-5.6-luna": { input: 1, output: 6, cacheRead: 0.1, cacheWrite: 1 },
|
|
64
|
+
"gpt-5.5": { input: 5, output: 30, cacheRead: 0.5, cacheWrite: 5 },
|
|
65
|
+
"gpt-5.4-mini": { input: 0.75, output: 4.5, cacheRead: 0.075, cacheWrite: 0.75 },
|
|
66
|
+
"gpt-5.4": { input: 2.5, output: 15, cacheRead: 0.25, cacheWrite: 2.5 },
|
|
67
|
+
"gpt-5-mini": { input: 0.25, output: 2, cacheRead: 0.025, cacheWrite: 0.25 },
|
|
68
|
+
"gpt-5": { input: 1.25, output: 10, cacheRead: 0.125, cacheWrite: 1.25 }
|
|
24
69
|
};
|
|
25
70
|
function getModelPrice(modelId) {
|
|
26
71
|
if (MODEL_PRICING[modelId])
|
|
27
72
|
return MODEL_PRICING[modelId];
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
73
|
+
if (isLocalModelId(modelId))
|
|
74
|
+
return LOCAL_MODEL_PRICE;
|
|
75
|
+
const id = String(modelId ?? "");
|
|
76
|
+
let forwardKey = "";
|
|
77
|
+
let reverseKey = "";
|
|
78
|
+
for (const key of Object.keys(MODEL_PRICING)) {
|
|
79
|
+
if (id.startsWith(key)) {
|
|
80
|
+
if (key.length > forwardKey.length)
|
|
81
|
+
forwardKey = key;
|
|
82
|
+
} else if (id && key.startsWith(id) && key[id.length] === "-") {
|
|
83
|
+
if (!reverseKey || key.length < reverseKey.length)
|
|
84
|
+
reverseKey = key;
|
|
85
|
+
}
|
|
31
86
|
}
|
|
87
|
+
const hit = forwardKey || reverseKey;
|
|
88
|
+
if (hit)
|
|
89
|
+
return MODEL_PRICING[hit];
|
|
32
90
|
return { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 };
|
|
33
91
|
}
|
|
34
92
|
function calcCost(usage, modelId) {
|
|
35
93
|
const price = getModelPrice(modelId);
|
|
36
94
|
return usage.inputTokens / 1e6 * price.input + usage.outputTokens / 1e6 * price.output + usage.cacheReadInputTokens / 1e6 * price.cacheRead + usage.cacheCreationInputTokens / 1e6 * price.cacheWrite;
|
|
37
95
|
}
|
|
96
|
+
// ../core/src/team.ts
|
|
97
|
+
var DEFAULT_TEAM = Object.freeze({
|
|
98
|
+
schema: 2,
|
|
99
|
+
mode: "solo",
|
|
100
|
+
connections: Object.freeze([])
|
|
101
|
+
});
|
|
102
|
+
var SHARE_SOURCE_TYPES = new Set(["repo", "project", "none"]);
|
|
38
103
|
// agentistics-mcp.ts
|
|
39
104
|
var API = process.env.AGENTISTICS_API ?? "http://localhost:47291";
|
|
40
105
|
function calcCostUSD(input, output, cacheRead, cacheWrite, model) {
|
|
@@ -47,6 +112,30 @@ function calcCostUSD(input, output, cacheRead, cacheWrite, model) {
|
|
|
47
112
|
costUSD: 0
|
|
48
113
|
}, model);
|
|
49
114
|
}
|
|
115
|
+
var HARNESS_IDS = ["claude", "codex", "gemini", "copilot", "antigravity"];
|
|
116
|
+
var HARNESS_PARAM = {
|
|
117
|
+
type: "string",
|
|
118
|
+
enum: ["all", ...HARNESS_IDS],
|
|
119
|
+
description: "Scope to one harness (claude | codex | gemini | copilot | antigravity), or 'all' (default) for the unified view across every harness."
|
|
120
|
+
};
|
|
121
|
+
function sessionHarness(s) {
|
|
122
|
+
return s.harness ?? "claude";
|
|
123
|
+
}
|
|
124
|
+
function filterSessions(sessions, harness) {
|
|
125
|
+
if (!harness || harness === "all")
|
|
126
|
+
return sessions;
|
|
127
|
+
return sessions.filter((s) => sessionHarness(s) === harness);
|
|
128
|
+
}
|
|
129
|
+
function sessionTokens(s) {
|
|
130
|
+
const input = s.input_tokens ?? 0;
|
|
131
|
+
const output = s.output_tokens ?? 0;
|
|
132
|
+
const cacheRead = s.cache_read_input_tokens ?? 0;
|
|
133
|
+
const cacheWrite = s.cache_creation_input_tokens ?? 0;
|
|
134
|
+
return { input, output, cacheRead, cacheWrite, cost: calcCostUSD(input, output, cacheRead, cacheWrite, s.model ?? "") };
|
|
135
|
+
}
|
|
136
|
+
function sessionMessages(s) {
|
|
137
|
+
return (s.user_message_count ?? 0) + (s.assistant_message_count ?? 0);
|
|
138
|
+
}
|
|
50
139
|
var CATALOG = [
|
|
51
140
|
{ id: "kpi.messages", label: "Messages", category: "kpi", defaultW: 3, defaultH: 3, minW: 2, minH: 2, description: "Total message count in selected period" },
|
|
52
141
|
{ id: "kpi.sessions", label: "Sessions", category: "kpi", defaultW: 3, defaultH: 3, minW: 2, minH: 2, description: "Session count + avg msgs/session" },
|
|
@@ -138,31 +227,37 @@ var server = new Server({ name: "agentistics", version: "1.0.0" }, { capabilitie
|
|
|
138
227
|
var TOOLS = [
|
|
139
228
|
{
|
|
140
229
|
name: "agentistics_summary",
|
|
141
|
-
description: "Get an overview of Claude Code
|
|
230
|
+
description: "Get an overview of AI coding usage metrics (across all tracked harnesses — Claude Code, Codex, Gemini, Copilot, Antigravity — or scoped to one): total tokens, estimated cost, sessions, streak, most used model, and top project. Good starting point for any metrics question.",
|
|
231
|
+
inputSchema: { type: "object", properties: { harness: HARNESS_PARAM }, required: [] }
|
|
232
|
+
},
|
|
233
|
+
{
|
|
234
|
+
name: "agentistics_harnesses",
|
|
235
|
+
description: "Compare the tracked AI coding harnesses side by side. Lists every harness present in the data with its sessions, messages, token usage, estimated cost, and last-active date. Use this to answer 'which harness do I use most / costs most' questions.",
|
|
142
236
|
inputSchema: { type: "object", properties: {}, required: [] }
|
|
143
237
|
},
|
|
144
238
|
{
|
|
145
239
|
name: "agentistics_projects",
|
|
146
|
-
description: "List
|
|
147
|
-
inputSchema: { type: "object", properties: {}, required: [] }
|
|
240
|
+
description: "List projects with session counts, message counts, token usage, estimated cost, and last active date. Optionally scope to a single harness.",
|
|
241
|
+
inputSchema: { type: "object", properties: { harness: HARNESS_PARAM }, required: [] }
|
|
148
242
|
},
|
|
149
243
|
{
|
|
150
244
|
name: "agentistics_sessions",
|
|
151
|
-
description: "Get the most recent
|
|
245
|
+
description: "Get the most recent sessions with harness, project path, duration, message count, token usage, and model. Optionally scope to a single harness.",
|
|
152
246
|
inputSchema: {
|
|
153
247
|
type: "object",
|
|
154
248
|
properties: {
|
|
155
249
|
limit: {
|
|
156
250
|
type: "number",
|
|
157
251
|
description: "Max sessions to return (default 20, max 50)"
|
|
158
|
-
}
|
|
252
|
+
},
|
|
253
|
+
harness: HARNESS_PARAM
|
|
159
254
|
}
|
|
160
255
|
}
|
|
161
256
|
},
|
|
162
257
|
{
|
|
163
258
|
name: "agentistics_costs",
|
|
164
|
-
description: "Get cost breakdown by model: token counts (input/output/cache read/write) and estimated USD cost per model.",
|
|
165
|
-
inputSchema: { type: "object", properties: {}, required: [] }
|
|
259
|
+
description: "Get cost breakdown by model: token counts (input/output/cache read/write) and estimated USD cost per model. Scope to a single harness, or 'all' (default) for the unified breakdown.",
|
|
260
|
+
inputSchema: { type: "object", properties: { harness: HARNESS_PARAM }, required: [] }
|
|
166
261
|
},
|
|
167
262
|
{
|
|
168
263
|
name: "agentistics_component_catalog",
|
|
@@ -300,6 +395,37 @@ var TOOLS = [
|
|
|
300
395
|
},
|
|
301
396
|
required: ["name", "componentIds"]
|
|
302
397
|
}
|
|
398
|
+
},
|
|
399
|
+
{
|
|
400
|
+
name: "agentistics_tags",
|
|
401
|
+
description: "List all tags — saved cross-cutting filters over repos/projects/machines/teams/accounts — with their aggregate sessions, cost, and tokens. Use for 'what tags exist' or 'how much did tag X cost' questions.",
|
|
402
|
+
inputSchema: { type: "object", properties: {}, required: [] }
|
|
403
|
+
},
|
|
404
|
+
{
|
|
405
|
+
name: "agentistics_tag_detail",
|
|
406
|
+
description: "Get full detail for one tag by name or id: aggregate totals plus per-source breakdown and distributions (top projects, models, harnesses, repos, members/users), daily activity series, and the active date window. Call agentistics_tags first to find the id/name.",
|
|
407
|
+
inputSchema: {
|
|
408
|
+
type: "object",
|
|
409
|
+
properties: {
|
|
410
|
+
tag: { type: "string", description: "Tag name or id, from agentistics_tags" }
|
|
411
|
+
},
|
|
412
|
+
required: ["tag"]
|
|
413
|
+
}
|
|
414
|
+
},
|
|
415
|
+
{
|
|
416
|
+
name: "agentistics_repos",
|
|
417
|
+
description: "List repositories, grouped by normalized git remote (independent of local path or which machine produced the session), with session/message/token/cost totals and last-active date. Sessions with no linked repository are grouped under 'unlinked'. Optionally scope to a single harness.",
|
|
418
|
+
inputSchema: { type: "object", properties: { harness: HARNESS_PARAM }, required: [] }
|
|
419
|
+
},
|
|
420
|
+
{
|
|
421
|
+
name: "agentistics_team_status",
|
|
422
|
+
description: "Get this machine's team-mode status: solo, central, or member. In member mode, lists its central connections (label, endpoint, connected/error state, last push). Use for 'am I connected to a team' or 'what central do I push to' questions.",
|
|
423
|
+
inputSchema: { type: "object", properties: {}, required: [] }
|
|
424
|
+
},
|
|
425
|
+
{
|
|
426
|
+
name: "agentistics_team_members",
|
|
427
|
+
description: "List members of this team, central only: display name, online/presence status, latency, and last-seen timestamp. For usage/cost per member use agentistics_summary or agentistics_sessions against this central's own /api/data. Returns an explanatory message when this machine is not running as a central (solo/member mode).",
|
|
428
|
+
inputSchema: { type: "object", properties: {}, required: [] }
|
|
303
429
|
}
|
|
304
430
|
];
|
|
305
431
|
server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS }));
|
|
@@ -308,71 +434,106 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
308
434
|
try {
|
|
309
435
|
switch (name) {
|
|
310
436
|
case "agentistics_summary": {
|
|
437
|
+
const harness = args?.harness;
|
|
438
|
+
const unified = !harness || harness === "all";
|
|
311
439
|
const data = await apiGet("/api/data");
|
|
312
440
|
const sc = data.statsCache ?? {};
|
|
313
441
|
const totals = sc.allTimeTotals ?? {};
|
|
314
|
-
const
|
|
315
|
-
const topModel = Object.entries(models).sort(([, a], [, b]) => (b.totalTokens ?? 0) - (a.totalTokens ?? 0))[0]?.[0] ?? "—";
|
|
316
|
-
const projects = data.projects ?? [];
|
|
317
|
-
const topProject = [...projects].sort((a, b) => (b.sessions?.length ?? 0) - (a.sessions?.length ?? 0))[0]?.name ?? "—";
|
|
318
|
-
const allSessions = data.sessions ?? [];
|
|
442
|
+
const allSessions = filterSessions(data.sessions ?? [], harness);
|
|
319
443
|
let totalCostUSD = 0;
|
|
320
444
|
let totalInput = 0, totalOutput = 0, totalCacheRead = 0, totalCacheWrite = 0;
|
|
445
|
+
const modelTokens = {};
|
|
446
|
+
const projectSessions = {};
|
|
447
|
+
const activeDates = new Set;
|
|
321
448
|
for (const s of allSessions) {
|
|
322
|
-
const
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
449
|
+
const { input, output, cacheRead, cacheWrite, cost } = sessionTokens(s);
|
|
450
|
+
totalInput += input;
|
|
451
|
+
totalOutput += output;
|
|
452
|
+
totalCacheRead += cacheRead;
|
|
453
|
+
totalCacheWrite += cacheWrite;
|
|
454
|
+
totalCostUSD += cost;
|
|
455
|
+
if (s.model)
|
|
456
|
+
modelTokens[s.model] = (modelTokens[s.model] ?? 0) + input + output;
|
|
457
|
+
if (s.project_path)
|
|
458
|
+
projectSessions[s.project_path] = (projectSessions[s.project_path] ?? 0) + 1;
|
|
459
|
+
if (s.start_time)
|
|
460
|
+
activeDates.add(String(s.start_time).slice(0, 10));
|
|
331
461
|
}
|
|
332
|
-
const
|
|
333
|
-
const
|
|
334
|
-
const
|
|
335
|
-
const finalCacheWrite = totalCacheWrite || (totals.cacheWriteTokens ?? 0);
|
|
462
|
+
const claudeFallback = unified || harness === "claude";
|
|
463
|
+
const topModel = Object.entries(modelTokens).sort(([, a], [, b]) => b - a)[0]?.[0] ?? (claudeFallback ? Object.entries(sc.modelUsage ?? {}).sort(([, a], [, b]) => (b.totalTokens ?? 0) - (a.totalTokens ?? 0))[0]?.[0] ?? "—" : "—");
|
|
464
|
+
const topProject = Object.entries(projectSessions).sort(([, a], [, b]) => b - a)[0]?.[0] ?? "—";
|
|
336
465
|
return {
|
|
337
466
|
content: [{
|
|
338
467
|
type: "text",
|
|
339
468
|
text: JSON.stringify({
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
469
|
+
harness: unified ? "all" : harness,
|
|
470
|
+
totalInputTokens: totalInput || (claudeFallback ? totals.inputTokens ?? 0 : 0),
|
|
471
|
+
totalOutputTokens: totalOutput || (claudeFallback ? totals.outputTokens ?? 0 : 0),
|
|
472
|
+
totalCacheReadTokens: totalCacheRead || (claudeFallback ? totals.cacheReadTokens ?? 0 : 0),
|
|
473
|
+
totalCacheWriteTokens: totalCacheWrite || (claudeFallback ? totals.cacheWriteTokens ?? 0 : 0),
|
|
344
474
|
estimatedCostUSD: Math.round(totalCostUSD * 100) / 100,
|
|
345
475
|
totalSessions: allSessions.length,
|
|
346
|
-
totalProjects: projects.length,
|
|
347
476
|
topModel,
|
|
348
477
|
topProject,
|
|
349
|
-
activeDays: sc.activeDays ?? 0,
|
|
350
|
-
currentStreak: sc.currentStreak ?? 0
|
|
478
|
+
activeDays: activeDates.size || (claudeFallback ? sc.activeDays ?? 0 : 0),
|
|
479
|
+
currentStreak: claudeFallback ? sc.currentStreak ?? 0 : null
|
|
351
480
|
}, null, 2)
|
|
352
481
|
}]
|
|
353
482
|
};
|
|
354
483
|
}
|
|
484
|
+
case "agentistics_harnesses": {
|
|
485
|
+
const data = await apiGet("/api/data");
|
|
486
|
+
const present = data.harnesses ?? ["claude"];
|
|
487
|
+
const sessions = data.sessions ?? [];
|
|
488
|
+
const rows = present.map((h) => {
|
|
489
|
+
const hs = sessions.filter((s) => sessionHarness(s) === h);
|
|
490
|
+
let input = 0, output = 0, cacheRead = 0, cacheWrite = 0, cost = 0, messages = 0, lastActive = "";
|
|
491
|
+
for (const s of hs) {
|
|
492
|
+
const t = sessionTokens(s);
|
|
493
|
+
input += t.input;
|
|
494
|
+
output += t.output;
|
|
495
|
+
cacheRead += t.cacheRead;
|
|
496
|
+
cacheWrite += t.cacheWrite;
|
|
497
|
+
cost += t.cost;
|
|
498
|
+
messages += sessionMessages(s);
|
|
499
|
+
if (s.start_time && String(s.start_time) > lastActive)
|
|
500
|
+
lastActive = String(s.start_time);
|
|
501
|
+
}
|
|
502
|
+
return {
|
|
503
|
+
harness: h,
|
|
504
|
+
sessions: hs.length,
|
|
505
|
+
messages,
|
|
506
|
+
inputTokens: input,
|
|
507
|
+
outputTokens: output,
|
|
508
|
+
cacheReadTokens: cacheRead,
|
|
509
|
+
cacheWriteTokens: cacheWrite,
|
|
510
|
+
totalTokens: input + output + cacheRead + cacheWrite,
|
|
511
|
+
estimatedCostUSD: Math.round(cost * 100) / 100,
|
|
512
|
+
lastActive: lastActive || null
|
|
513
|
+
};
|
|
514
|
+
}).sort((a, b) => b.totalTokens - a.totalTokens);
|
|
515
|
+
return { content: [{ type: "text", text: JSON.stringify(rows, null, 2) }] };
|
|
516
|
+
}
|
|
355
517
|
case "agentistics_projects": {
|
|
518
|
+
const harness = args?.harness;
|
|
356
519
|
const data = await apiGet("/api/data");
|
|
357
|
-
const allSessions = data.sessions ?? [];
|
|
520
|
+
const allSessions = filterSessions(data.sessions ?? [], harness);
|
|
358
521
|
const byPath = {};
|
|
359
522
|
for (const s of allSessions) {
|
|
360
523
|
const key = s.project_path;
|
|
361
524
|
if (!key)
|
|
362
525
|
continue;
|
|
363
526
|
if (!byPath[key])
|
|
364
|
-
byPath[key] = { inputTokens: 0, outputTokens: 0, cacheRead: 0, cacheWrite: 0, costUSD: 0, messages: 0, lastActive: "", languages: [] };
|
|
527
|
+
byPath[key] = { sessions: 0, inputTokens: 0, outputTokens: 0, cacheRead: 0, cacheWrite: 0, costUSD: 0, messages: 0, lastActive: "", languages: [] };
|
|
365
528
|
const agg = byPath[key];
|
|
366
|
-
const
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
agg.
|
|
371
|
-
agg.
|
|
372
|
-
agg.
|
|
373
|
-
agg.
|
|
374
|
-
agg.costUSD += calcCostUSD(inp, out, cr, cw, s.model ?? "");
|
|
375
|
-
agg.messages += (s.user_message_count ?? 0) + (s.assistant_message_count ?? 0);
|
|
529
|
+
const { input, output, cacheRead, cacheWrite, cost } = sessionTokens(s);
|
|
530
|
+
agg.sessions += 1;
|
|
531
|
+
agg.inputTokens += input;
|
|
532
|
+
agg.outputTokens += output;
|
|
533
|
+
agg.cacheRead += cacheRead;
|
|
534
|
+
agg.cacheWrite += cacheWrite;
|
|
535
|
+
agg.costUSD += cost;
|
|
536
|
+
agg.messages += sessionMessages(s);
|
|
376
537
|
if (!agg.lastActive || (s.start_time ?? "") > agg.lastActive)
|
|
377
538
|
agg.lastActive = s.start_time ?? "";
|
|
378
539
|
for (const lang of s.languages ?? [])
|
|
@@ -380,12 +541,12 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
380
541
|
agg.languages.push(lang);
|
|
381
542
|
}
|
|
382
543
|
const projects = data.projects ?? [];
|
|
383
|
-
const summary = projects.map((p) => {
|
|
384
|
-
const agg = byPath[p.path] ?? { inputTokens: 0, outputTokens: 0, cacheRead: 0, cacheWrite: 0, costUSD: 0, messages: 0, lastActive: "", languages: [] };
|
|
544
|
+
const summary = projects.filter((p) => !harness || harness === "all" || byPath[p.path]).map((p) => {
|
|
545
|
+
const agg = byPath[p.path] ?? { sessions: 0, inputTokens: 0, outputTokens: 0, cacheRead: 0, cacheWrite: 0, costUSD: 0, messages: 0, lastActive: "", languages: [] };
|
|
385
546
|
return {
|
|
386
547
|
name: p.name,
|
|
387
548
|
path: p.path,
|
|
388
|
-
sessions: (p.sessions ?? []).length,
|
|
549
|
+
sessions: !harness || harness === "all" ? (p.sessions ?? []).length : agg.sessions,
|
|
389
550
|
messages: agg.messages,
|
|
390
551
|
inputTokens: agg.inputTokens,
|
|
391
552
|
outputTokens: agg.outputTokens,
|
|
@@ -399,40 +560,65 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
399
560
|
}
|
|
400
561
|
case "agentistics_sessions": {
|
|
401
562
|
const limit = Math.min(args?.limit ?? 20, 50);
|
|
563
|
+
const harness = args?.harness;
|
|
402
564
|
const data = await apiGet("/api/data");
|
|
403
|
-
const rows = (data.sessions ?? []).slice(0, limit).map((s) => {
|
|
404
|
-
const
|
|
405
|
-
const out = s.output_tokens ?? 0;
|
|
406
|
-
const cr = s.cache_read_input_tokens ?? 0;
|
|
407
|
-
const cw = s.cache_creation_input_tokens ?? 0;
|
|
565
|
+
const rows = filterSessions(data.sessions ?? [], harness).slice(0, limit).map((s) => {
|
|
566
|
+
const { input, output, cacheRead, cacheWrite, cost } = sessionTokens(s);
|
|
408
567
|
return {
|
|
409
568
|
id: s.session_id,
|
|
569
|
+
harness: sessionHarness(s),
|
|
410
570
|
project: s.project_path,
|
|
411
571
|
startedAt: s.start_time,
|
|
412
572
|
durationMinutes: s.duration_minutes,
|
|
413
|
-
messages: (s
|
|
414
|
-
inputTokens:
|
|
415
|
-
outputTokens:
|
|
416
|
-
cacheReadTokens:
|
|
417
|
-
cacheWriteTokens:
|
|
418
|
-
totalTokens:
|
|
419
|
-
estimatedCostUSD: Math.round(
|
|
573
|
+
messages: sessionMessages(s),
|
|
574
|
+
inputTokens: input,
|
|
575
|
+
outputTokens: output,
|
|
576
|
+
cacheReadTokens: cacheRead,
|
|
577
|
+
cacheWriteTokens: cacheWrite,
|
|
578
|
+
totalTokens: input + output + cacheRead + cacheWrite,
|
|
579
|
+
estimatedCostUSD: Math.round(cost * 1e4) / 1e4,
|
|
420
580
|
model: s.model ?? null
|
|
421
581
|
};
|
|
422
582
|
});
|
|
423
583
|
return { content: [{ type: "text", text: JSON.stringify(rows, null, 2) }] };
|
|
424
584
|
}
|
|
425
585
|
case "agentistics_costs": {
|
|
586
|
+
const harness = args?.harness;
|
|
426
587
|
const data = await apiGet("/api/data");
|
|
427
|
-
|
|
428
|
-
|
|
588
|
+
if (!harness || harness === "all" || harness === "claude") {
|
|
589
|
+
const usage = data.statsCache?.modelUsage ?? {};
|
|
590
|
+
const breakdown2 = Object.entries(usage).map(([model, u]) => ({
|
|
591
|
+
model,
|
|
592
|
+
inputTokens: u.inputTokens ?? 0,
|
|
593
|
+
outputTokens: u.outputTokens ?? 0,
|
|
594
|
+
cacheReadTokens: u.cacheReadTokens ?? 0,
|
|
595
|
+
cacheWriteTokens: u.cacheWriteTokens ?? 0,
|
|
596
|
+
totalTokens: u.totalTokens ?? 0,
|
|
597
|
+
estimatedCostUSD: u.costUSD ?? 0
|
|
598
|
+
})).sort((a, b) => b.totalTokens - a.totalTokens);
|
|
599
|
+
return { content: [{ type: "text", text: JSON.stringify(breakdown2, null, 2) }] };
|
|
600
|
+
}
|
|
601
|
+
const byModel = {};
|
|
602
|
+
for (const s of filterSessions(data.sessions ?? [], harness)) {
|
|
603
|
+
const model = s.model || "unknown";
|
|
604
|
+
if (!byModel[model])
|
|
605
|
+
byModel[model] = { inputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0, estimatedCostUSD: 0 };
|
|
606
|
+
const agg = byModel[model];
|
|
607
|
+
const { input, output, cacheRead, cacheWrite, cost } = sessionTokens(s);
|
|
608
|
+
agg.inputTokens += input;
|
|
609
|
+
agg.outputTokens += output;
|
|
610
|
+
agg.cacheReadTokens += cacheRead;
|
|
611
|
+
agg.cacheWriteTokens += cacheWrite;
|
|
612
|
+
agg.estimatedCostUSD += cost;
|
|
613
|
+
}
|
|
614
|
+
const breakdown = Object.entries(byModel).map(([model, u]) => ({
|
|
429
615
|
model,
|
|
430
|
-
inputTokens: u.inputTokens
|
|
431
|
-
outputTokens: u.outputTokens
|
|
432
|
-
cacheReadTokens: u.cacheReadTokens
|
|
433
|
-
cacheWriteTokens: u.cacheWriteTokens
|
|
434
|
-
totalTokens: u.
|
|
435
|
-
estimatedCostUSD: u.
|
|
616
|
+
inputTokens: u.inputTokens,
|
|
617
|
+
outputTokens: u.outputTokens,
|
|
618
|
+
cacheReadTokens: u.cacheReadTokens,
|
|
619
|
+
cacheWriteTokens: u.cacheWriteTokens,
|
|
620
|
+
totalTokens: u.inputTokens + u.outputTokens + u.cacheReadTokens + u.cacheWriteTokens,
|
|
621
|
+
estimatedCostUSD: Math.round(u.estimatedCostUSD * 1e4) / 1e4
|
|
436
622
|
})).sort((a, b) => b.totalTokens - a.totalTokens);
|
|
437
623
|
return { content: [{ type: "text", text: JSON.stringify(breakdown, null, 2) }] };
|
|
438
624
|
}
|
|
@@ -610,6 +796,133 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
610
796
|
}]
|
|
611
797
|
};
|
|
612
798
|
}
|
|
799
|
+
case "agentistics_tags": {
|
|
800
|
+
const data = await apiGet("/api/tags");
|
|
801
|
+
const tags = data.tags ?? [];
|
|
802
|
+
const rows = tags.map((t) => ({
|
|
803
|
+
id: t._id,
|
|
804
|
+
name: t.name,
|
|
805
|
+
color: t.color ?? null,
|
|
806
|
+
sessions: t.aggregate?.sessions ?? 0,
|
|
807
|
+
estimatedCostUSD: Math.round((t.aggregate?.costUSD ?? 0) * 1e4) / 1e4,
|
|
808
|
+
inputTokens: t.aggregate?.inputTokens ?? 0,
|
|
809
|
+
outputTokens: t.aggregate?.outputTokens ?? 0,
|
|
810
|
+
topProject: t.aggregate?.topProject ?? null,
|
|
811
|
+
topModel: t.aggregate?.topModel ?? null,
|
|
812
|
+
topHarness: t.aggregate?.topHarness ?? null,
|
|
813
|
+
window: t.window ?? null
|
|
814
|
+
}));
|
|
815
|
+
return { content: [{ type: "text", text: JSON.stringify(rows, null, 2) }] };
|
|
816
|
+
}
|
|
817
|
+
case "agentistics_tag_detail": {
|
|
818
|
+
const wanted = (args?.tag ?? "").trim();
|
|
819
|
+
if (!wanted)
|
|
820
|
+
throw new Error("tag is required — pass a name or id from agentistics_tags.");
|
|
821
|
+
const list = await apiGet("/api/tags");
|
|
822
|
+
const tags = list.tags ?? [];
|
|
823
|
+
const match = tags.find((t) => t._id === wanted) ?? tags.find((t) => t.name?.toLowerCase() === wanted.toLowerCase());
|
|
824
|
+
if (!match)
|
|
825
|
+
throw new Error(`No tag matching "${wanted}". Available: ${tags.map((t) => t.name).join(", ") || "(none)"}`);
|
|
826
|
+
const detail = await apiGet(`/api/tags/${encodeURIComponent(match._id)}`);
|
|
827
|
+
const d = detail;
|
|
828
|
+
const bucketTop = (b, n = 5) => (b ?? []).slice(0, n).map((x) => ({ key: x.key, sessions: x.sessions, estimatedCostUSD: Math.round((x.costUSD ?? 0) * 1e4) / 1e4 }));
|
|
829
|
+
return {
|
|
830
|
+
content: [{
|
|
831
|
+
type: "text",
|
|
832
|
+
text: JSON.stringify({
|
|
833
|
+
id: d.tag?._id,
|
|
834
|
+
name: d.tag?.name,
|
|
835
|
+
color: d.tag?.color ?? null,
|
|
836
|
+
window: d.tag?.window ?? null,
|
|
837
|
+
sources: d.tag?.sources ?? [],
|
|
838
|
+
aggregate: {
|
|
839
|
+
sessions: d.tag?.aggregate?.sessions ?? 0,
|
|
840
|
+
estimatedCostUSD: Math.round((d.tag?.aggregate?.costUSD ?? 0) * 1e4) / 1e4,
|
|
841
|
+
inputTokens: d.tag?.aggregate?.inputTokens ?? 0,
|
|
842
|
+
outputTokens: d.tag?.aggregate?.outputTokens ?? 0
|
|
843
|
+
},
|
|
844
|
+
topProjects: bucketTop(d.stats?.projects),
|
|
845
|
+
topModels: bucketTop(d.stats?.models),
|
|
846
|
+
topHarnesses: bucketTop(d.stats?.harnesses),
|
|
847
|
+
topRepos: bucketTop(d.stats?.repos),
|
|
848
|
+
topMembers: bucketTop(d.stats?.members),
|
|
849
|
+
distinctMembers: d.stats?.distinctMembers ?? 0,
|
|
850
|
+
distinctMachines: d.stats?.distinctMachines ?? 0,
|
|
851
|
+
firstSessionDate: d.stats?.firstSessionDate ?? null,
|
|
852
|
+
lastSessionDate: d.stats?.lastSessionDate ?? null
|
|
853
|
+
}, null, 2)
|
|
854
|
+
}]
|
|
855
|
+
};
|
|
856
|
+
}
|
|
857
|
+
case "agentistics_repos": {
|
|
858
|
+
const harness = args?.harness;
|
|
859
|
+
const data = await apiGet("/api/data");
|
|
860
|
+
const allSessions = filterSessions(data.sessions ?? [], harness);
|
|
861
|
+
const byRemote = {};
|
|
862
|
+
for (const s of allSessions) {
|
|
863
|
+
const key = s.git_remote || "";
|
|
864
|
+
if (!byRemote[key])
|
|
865
|
+
byRemote[key] = { sessions: 0, messages: 0, inputTokens: 0, outputTokens: 0, cacheRead: 0, cacheWrite: 0, costUSD: 0, lastActive: "" };
|
|
866
|
+
const agg = byRemote[key];
|
|
867
|
+
const { input, output, cacheRead, cacheWrite, cost } = sessionTokens(s);
|
|
868
|
+
agg.sessions += 1;
|
|
869
|
+
agg.messages += sessionMessages(s);
|
|
870
|
+
agg.inputTokens += input;
|
|
871
|
+
agg.outputTokens += output;
|
|
872
|
+
agg.cacheRead += cacheRead;
|
|
873
|
+
agg.cacheWrite += cacheWrite;
|
|
874
|
+
agg.costUSD += cost;
|
|
875
|
+
if (s.start_time && String(s.start_time) > agg.lastActive)
|
|
876
|
+
agg.lastActive = String(s.start_time);
|
|
877
|
+
}
|
|
878
|
+
const rows = Object.entries(byRemote).map(([remote, agg]) => ({
|
|
879
|
+
repo: remote || "unlinked",
|
|
880
|
+
remote: remote || null,
|
|
881
|
+
sessions: agg.sessions,
|
|
882
|
+
messages: agg.messages,
|
|
883
|
+
inputTokens: agg.inputTokens,
|
|
884
|
+
outputTokens: agg.outputTokens,
|
|
885
|
+
totalTokens: agg.inputTokens + agg.outputTokens + agg.cacheRead + agg.cacheWrite,
|
|
886
|
+
estimatedCostUSD: Math.round(agg.costUSD * 1e4) / 1e4,
|
|
887
|
+
lastActive: agg.lastActive || null
|
|
888
|
+
})).sort((a, b) => b.totalTokens - a.totalTokens);
|
|
889
|
+
return { content: [{ type: "text", text: JSON.stringify(rows, null, 2) }] };
|
|
890
|
+
}
|
|
891
|
+
case "agentistics_team_status": {
|
|
892
|
+
const prefs = await getPrefs();
|
|
893
|
+
const team2 = prefs.team;
|
|
894
|
+
const mode = team2?.mode ?? "solo";
|
|
895
|
+
const connections = (team2?.connections ?? []).map((c) => ({
|
|
896
|
+
id: c.id,
|
|
897
|
+
label: c.label ?? null,
|
|
898
|
+
endpoint: c.endpoint
|
|
899
|
+
}));
|
|
900
|
+
return {
|
|
901
|
+
content: [{
|
|
902
|
+
type: "text",
|
|
903
|
+
text: JSON.stringify({ mode, connections }, null, 2)
|
|
904
|
+
}]
|
|
905
|
+
};
|
|
906
|
+
}
|
|
907
|
+
case "agentistics_team_members": {
|
|
908
|
+
const res = await fetch(`${API}/api/team/members`);
|
|
909
|
+
if (res.status === 404) {
|
|
910
|
+
return { content: [{ type: "text", text: "This machine is not running as a central (solo or member mode) — there is no member roster here. Use agentistics_team_status to see the current mode." }] };
|
|
911
|
+
}
|
|
912
|
+
if (!res.ok)
|
|
913
|
+
throw new Error(`GET /api/team/members → HTTP ${res.status}`);
|
|
914
|
+
const data = await res.json();
|
|
915
|
+
const members = data.members ?? [];
|
|
916
|
+
const rows = members.map((m) => ({
|
|
917
|
+
id: m.id,
|
|
918
|
+
name: m.user,
|
|
919
|
+
label: m.label,
|
|
920
|
+
online: m.online ?? false,
|
|
921
|
+
latencyMs: m.latencyMs ?? null,
|
|
922
|
+
lastSeenAt: m.lastSeenAt ?? null
|
|
923
|
+
}));
|
|
924
|
+
return { content: [{ type: "text", text: JSON.stringify(rows, null, 2) }] };
|
|
925
|
+
}
|
|
613
926
|
default:
|
|
614
927
|
throw new Error(`Unknown tool: ${name}`);
|
|
615
928
|
}
|