@cryptiklemur/lattice 5.10.0 → 5.11.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/client/assets/{angular-html-BDIcxkJq.js → angular-html-BoFzmWT8.js} +1 -1
- package/dist/client/assets/{angular-ts-Bt22ouNH.js → angular-ts-DZnI8rKE.js} +1 -1
- package/dist/client/assets/{apl-p8qkxzEK.js → apl-DstVmncE.js} +1 -1
- package/dist/client/assets/{astro-CIaMc49M.js → astro-DTPCjzEx.js} +1 -1
- package/dist/client/assets/{blade-BR56EAMD.js → blade-6q42Ss3F.js} +1 -1
- package/dist/client/assets/{c-Dli0HzAh.js → c-BQDGJ-nQ.js} +1 -1
- package/dist/client/assets/{cobol-Cad15ECy.js → cobol-Dlh0WvsZ.js} +1 -1
- package/dist/client/assets/{coffee-DpyATEbF.js → coffee-DdQv129j.js} +1 -1
- package/dist/client/assets/{cpp-KN8_NFsf.js → cpp-DhbQJIv4.js} +1 -1
- package/dist/client/assets/{crystal-CuyGv0kh.js → crystal-C22kERUB.js} +1 -1
- package/dist/client/assets/{css-Cm3q4bxn.js → css-n31O5kHj.js} +1 -1
- package/dist/client/assets/{dist-BjxsMc4u.js → dist-D8okl7lw.js} +2 -2
- package/dist/client/assets/{edge-B6S7CSbx.js → edge-Cgwx-o_7.js} +1 -1
- package/dist/client/assets/{elixir-CNUy9H8T.js → elixir-DAGM2WKD.js} +1 -1
- package/dist/client/assets/{elm-CNfcWmb9.js → elm-BLw_7oO9.js} +1 -1
- package/dist/client/assets/{erb-DWebzDaI.js → erb-DCaNhYa7.js} +1 -1
- package/dist/client/assets/{git-rebase-B_Pt2ZBK.js → git-rebase-CNNhb8-g.js} +1 -1
- package/dist/client/assets/{glimmer-js-CVwoOd72.js → glimmer-js-BnZd88Wi.js} +1 -1
- package/dist/client/assets/{glimmer-ts-CjtFSxjz.js → glimmer-ts-DvFNbZu-.js} +1 -1
- package/dist/client/assets/{glsl-CP4rggAA.js → glsl-Dnrk_Jnx.js} +1 -1
- package/dist/client/assets/{graphql-Dbm6sAtp.js → graphql-DlWTPvCG.js} +1 -1
- package/dist/client/assets/{hack-Bj9y3SGf.js → hack-DQg1Ek33.js} +1 -1
- package/dist/client/assets/{haml-DRGrdf3f.js → haml-DSk45qIE.js} +1 -1
- package/dist/client/assets/{handlebars-CFKjcBMg.js → handlebars-DuLvATB2.js} +1 -1
- package/dist/client/assets/{html-Vcd4eHHg.js → html-D4DiUnLg.js} +1 -1
- package/dist/client/assets/{html-derivative-BF0YbD4L.js → html-derivative-CS5MZ6d9.js} +1 -1
- package/dist/client/assets/{http-CGVTa2NT.js → http-CkDncfer.js} +1 -1
- package/dist/client/assets/{hurl-B0GrsGqd.js → hurl-DU39oO3U.js} +1 -1
- package/dist/client/assets/{index-CX1tudsF.js → index-CHPfE1Zl.js} +129 -129
- package/dist/client/assets/index-DHUKmLLC.css +2 -0
- package/dist/client/assets/{java-BJHQqHsm.js → java-lntACKEu.js} +1 -1
- package/dist/client/assets/{javascript-CmuMsKrc.js → javascript-CxkFc6nV.js} +1 -1
- package/dist/client/assets/{jinja-JxCLeq1j.js → jinja-DolO2zO7.js} +1 -1
- package/dist/client/assets/{jison-BdgAUhei.js → jison-Cok5FPev.js} +1 -1
- package/dist/client/assets/{json-DtPissHL.js → json-BebuQPrq.js} +1 -1
- package/dist/client/assets/{jsx-DUAxxDkP.js → jsx-iLBaUyXr.js} +1 -1
- package/dist/client/assets/{julia-DxDlbL6e.js → julia-C5Dsc7cH.js} +1 -1
- package/dist/client/assets/{just-CVmAAx2R.js → just-DJYqq_9R.js} +1 -1
- package/dist/client/assets/{latex-uwxggTWA.js → latex-BTTYiKj1.js} +1 -1
- package/dist/client/assets/{liquid-xsETAJJy.js → liquid-DpAKCrOB.js} +1 -1
- package/dist/client/assets/{lua-B2Hh8PgD.js → lua-BZ6b1hko.js} +1 -1
- package/dist/client/assets/{marko-yDeGxD87.js → marko-D8VK6iGt.js} +1 -1
- package/dist/client/assets/{mdc-QMp4ieYR.js → mdc-Paa3XzwY.js} +1 -1
- package/dist/client/assets/{nginx-7gmRmcqz.js → nginx-C5k9mWtJ.js} +1 -1
- package/dist/client/assets/{nim-CA8SNY_7.js → nim-Dst6YSnE.js} +1 -1
- package/dist/client/assets/{perl-lx5nW4VC.js → perl-XhiCjgBp.js} +1 -1
- package/dist/client/assets/{php-DgHiW953.js → php-BcsPLnLU.js} +1 -1
- package/dist/client/assets/{pug-CbbB1vwb.js → pug-GLH9-eAJ.js} +1 -1
- package/dist/client/assets/{qml-COrzwCIh.js → qml-Cj_lJioE.js} +1 -1
- package/dist/client/assets/{r-Dv7pZJDH.js → r-B70aGYK5.js} +1 -1
- package/dist/client/assets/{razor-D2m8EDP5.js → razor-R3gub_zy.js} +1 -1
- package/dist/client/assets/{regexp-BXLT-jPc.js → regexp-itC0dIUJ.js} +1 -1
- package/dist/client/assets/{rst-_S6rrUYh.js → rst-DdyoV8E2.js} +1 -1
- package/dist/client/assets/{ruby-C3XO7tYY.js → ruby-BYBZsv66.js} +1 -1
- package/dist/client/assets/{sas-DP2k4iuN.js → sas-fqfqXqj1.js} +1 -1
- package/dist/client/assets/{scss-lhLFMXGn.js → scss-B-ELv6mu.js} +1 -1
- package/dist/client/assets/{shellscript-BYlBPHen.js → shellscript-BgB8TNw6.js} +1 -1
- package/dist/client/assets/{shellsession-CbVyQKWZ.js → shellsession-BLK2Dgkm.js} +1 -1
- package/dist/client/assets/{soy-Be8a0lHq.js → soy-C7_RmNrp.js} +1 -1
- package/dist/client/assets/{sql-2KxvU9YS.js → sql-AUgbUJq4.js} +1 -1
- package/dist/client/assets/{stata-BxlWftTS.js → stata-CIVqSIOr.js} +1 -1
- package/dist/client/assets/{surrealql-CJ-q86nR.js → surrealql-BzRQzc5S.js} +1 -1
- package/dist/client/assets/{svelte-Q1ml0OiY.js → svelte-BCIwEwtb.js} +1 -1
- package/dist/client/assets/{templ-BbfPZhtu.js → templ-C1hbwe4u.js} +1 -1
- package/dist/client/assets/{tex-Dcth4Gi6.js → tex-CI4tIsaP.js} +1 -1
- package/dist/client/assets/{ts-tags-BKhSOXI3.js → ts-tags-SUeikhEp.js} +1 -1
- package/dist/client/assets/{tsx-CS6iQ0XH.js → tsx-xkp7aIZs.js} +1 -1
- package/dist/client/assets/{twig-BHp31ZxS.js → twig-CGgBSAyc.js} +1 -1
- package/dist/client/assets/{typescript-16YJBTaO.js → typescript-O2YMTl_s.js} +1 -1
- package/dist/client/assets/{vue-CMKwTi4r.js → vue-DsNRxos1.js} +1 -1
- package/dist/client/assets/{vue-html-Dr8VUA2G.js → vue-html-CuY3t7bs.js} +1 -1
- package/dist/client/assets/{vue-vine-DZUqDerl.js → vue-vine-C6kSCKwY.js} +1 -1
- package/dist/client/assets/{xml-CBbBKKDC.js → xml-DafwzOLY.js} +1 -1
- package/dist/client/assets/{xsl-DWEX6PKX.js → xsl-1SGGZibr.js} +1 -1
- package/dist/client/assets/{yaml-DvKvvh3X.js → yaml-DSVhzmhr.js} +1 -1
- package/dist/client/index.html +2 -2
- package/dist/client/sw.js +1 -1
- package/dist/server/analytics/engine.js +241 -241
- package/dist/server/assets.js +4 -4
- package/dist/server/auth/passphrase.js +13 -13
- package/dist/server/config.js +7 -7
- package/dist/server/daemon.js +93 -93
- package/dist/server/features/brainstorm.js +42 -42
- package/dist/server/features/ralph-loop.js +33 -33
- package/dist/server/features/scheduler.js +53 -53
- package/dist/server/features/specs.js +54 -54
- package/dist/server/features/sticky-notes.js +17 -17
- package/dist/server/features/superpowers.js +24 -24
- package/dist/server/handlers/analytics.js +1 -1
- package/dist/server/handlers/attachment.js +32 -32
- package/dist/server/handlers/bookmarks.js +4 -4
- package/dist/server/handlers/brainstorm.js +4 -4
- package/dist/server/handlers/chat.js +54 -54
- package/dist/server/handlers/editor.js +13 -13
- package/dist/server/handlers/fs.js +51 -51
- package/dist/server/handlers/hooks.js +20 -20
- package/dist/server/handlers/loop.js +6 -6
- package/dist/server/handlers/memory.js +44 -44
- package/dist/server/handlers/mesh.js +60 -60
- package/dist/server/handlers/notes.js +7 -7
- package/dist/server/handlers/plugins.js +174 -174
- package/dist/server/handlers/project-settings.js +26 -26
- package/dist/server/handlers/scheduler.js +6 -6
- package/dist/server/handlers/session.js +24 -24
- package/dist/server/handlers/settings.js +21 -21
- package/dist/server/handlers/skills.js +91 -91
- package/dist/server/handlers/specs.js +51 -28
- package/dist/server/handlers/terminal.js +13 -13
- package/dist/server/handlers/themes.js +21 -21
- package/dist/server/handlers/update.js +17 -17
- package/dist/server/hooks/event_forward.sh +34 -0
- package/dist/server/hooks/post_tool_use.sh +26 -0
- package/dist/server/hooks/statusline.sh +26 -0
- package/dist/server/identity.js +6 -6
- package/dist/server/index.js +111 -111
- package/dist/server/logger.js +1 -1
- package/dist/server/mesh/connector.js +78 -78
- package/dist/server/mesh/crypto.js +20 -20
- package/dist/server/mesh/discovery.js +14 -14
- package/dist/server/mesh/pairing.js +30 -30
- package/dist/server/mesh/peers.js +10 -10
- package/dist/server/mesh/proxy.js +14 -14
- package/dist/server/mesh/session-sync.js +23 -23
- package/dist/server/project/bookmarks.js +11 -11
- package/dist/server/project/context-breakdown.js +70 -70
- package/dist/server/project/file-browser.js +17 -17
- package/dist/server/project/project-files.js +68 -68
- package/dist/server/project/registry.js +10 -10
- package/dist/server/project/sdk-bridge.js +157 -157
- package/dist/server/project/session.js +201 -199
- package/dist/server/project/terminal.js +15 -15
- package/dist/server/project/warmup.js +37 -37
- package/dist/server/push.js +11 -11
- package/dist/server/runtime.js +1 -1
- package/dist/server/tls.js +15 -15
- package/dist/server/tui.js +15 -15
- package/dist/server/update-checker.js +21 -21
- package/dist/server/ws/broadcast.js +18 -18
- package/dist/server/ws/router.js +17 -17
- package/dist/shared/constants.js +8 -8
- package/package.json +2 -2
- package/dist/client/assets/index-DlfI20Gn.css +0 -2
|
@@ -4,10 +4,10 @@ import { homedir } from "node:os";
|
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { estimateCost, projectPathToHash } from "../project/session.js";
|
|
6
6
|
import { loadConfig } from "../config.js";
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
export
|
|
7
|
+
const cache = new Map();
|
|
8
|
+
const CACHE_TTL = 5 * 60 * 1000;
|
|
9
|
+
const inflight = new Map();
|
|
10
|
+
export const SECTION_KEYS = {
|
|
11
11
|
summary: ["totalCost", "totalSessions", "totalTokens", "cacheHitRate", "avgSessionCost", "avgSessionDuration", "costOverTime", "cumulativeCost", "sessionsOverTime", "tokensOverTime", "cacheHitRateOverTime"],
|
|
12
12
|
spending: ["costDistribution", "sessionBubbles", "modelUsage", "projectBreakdown"],
|
|
13
13
|
usage: ["toolUsage", "responseTimeData", "contextUtilization", "tokenFlowSankey"],
|
|
@@ -26,15 +26,15 @@ function bucketModel(model) {
|
|
|
26
26
|
function getPeriodCutoff(period) {
|
|
27
27
|
if (period === "all")
|
|
28
28
|
return 0;
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
const now = Date.now();
|
|
30
|
+
const hours = { "24h": 24, "7d": 168, "30d": 720, "90d": 2160 };
|
|
31
31
|
return now - (hours[period] || 0) * 60 * 60 * 1000;
|
|
32
32
|
}
|
|
33
33
|
function formatDate(ts) {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
34
|
+
const d = new Date(ts);
|
|
35
|
+
const year = d.getFullYear();
|
|
36
|
+
const month = String(d.getMonth() + 1).padStart(2, "0");
|
|
37
|
+
const day = String(d.getDate()).padStart(2, "0");
|
|
38
38
|
return year + "-" + month + "-" + day;
|
|
39
39
|
}
|
|
40
40
|
function getCostBucket(cost) {
|
|
@@ -56,8 +56,8 @@ function getCostBucket(cost) {
|
|
|
56
56
|
}
|
|
57
57
|
function parseSessionText(text, sessionId, projectSlug) {
|
|
58
58
|
try {
|
|
59
|
-
|
|
60
|
-
|
|
59
|
+
const lines = text.split("\n");
|
|
60
|
+
const data = {
|
|
61
61
|
id: sessionId,
|
|
62
62
|
title: "",
|
|
63
63
|
project: projectSlug,
|
|
@@ -73,22 +73,22 @@ function parseSessionText(text, sessionId, projectSlug) {
|
|
|
73
73
|
responseTimePoints: [],
|
|
74
74
|
contextMessages: [],
|
|
75
75
|
};
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
for (
|
|
79
|
-
|
|
76
|
+
let lastUserTimestamp = 0;
|
|
77
|
+
let assistantIndex = 0;
|
|
78
|
+
for (let i = 0; i < lines.length; i++) {
|
|
79
|
+
const line = lines[i].trim();
|
|
80
80
|
if (!line)
|
|
81
81
|
continue;
|
|
82
|
-
|
|
82
|
+
let parsed;
|
|
83
83
|
try {
|
|
84
84
|
parsed = JSON.parse(line);
|
|
85
85
|
}
|
|
86
86
|
catch {
|
|
87
87
|
continue;
|
|
88
88
|
}
|
|
89
|
-
|
|
89
|
+
let timestamp = 0;
|
|
90
90
|
if (typeof parsed.timestamp === "string") {
|
|
91
|
-
|
|
91
|
+
const ts = new Date(parsed.timestamp).getTime();
|
|
92
92
|
if (!isNaN(ts))
|
|
93
93
|
timestamp = ts;
|
|
94
94
|
}
|
|
@@ -99,24 +99,24 @@ function parseSessionText(text, sessionId, projectSlug) {
|
|
|
99
99
|
data.endTime = timestamp;
|
|
100
100
|
}
|
|
101
101
|
if (parsed.type === "assistant") {
|
|
102
|
-
|
|
102
|
+
const message = parsed.message;
|
|
103
103
|
if (!message)
|
|
104
104
|
continue;
|
|
105
|
-
|
|
106
|
-
|
|
105
|
+
const usage = message.usage;
|
|
106
|
+
const model = message.model || "";
|
|
107
107
|
if (usage) {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
108
|
+
const inTok = usage.input_tokens || 0;
|
|
109
|
+
const outTok = usage.output_tokens || 0;
|
|
110
|
+
const cacheRead = usage.cache_read_input_tokens || 0;
|
|
111
|
+
const cacheCreation = usage.cache_creation_input_tokens || 0;
|
|
112
112
|
data.inputTokens += inTok;
|
|
113
113
|
data.outputTokens += outTok;
|
|
114
114
|
data.cacheReadTokens += cacheRead;
|
|
115
115
|
data.cacheCreationTokens += cacheCreation;
|
|
116
|
-
|
|
116
|
+
const cost = estimateCost(model, inTok, outTok, cacheRead, cacheCreation);
|
|
117
117
|
data.cost += cost;
|
|
118
|
-
|
|
119
|
-
|
|
118
|
+
const bucket = bucketModel(model);
|
|
119
|
+
const existing = data.models.get(bucket);
|
|
120
120
|
if (existing) {
|
|
121
121
|
existing.cost += cost;
|
|
122
122
|
existing.tokens += inTok + outTok;
|
|
@@ -125,7 +125,7 @@ function parseSessionText(text, sessionId, projectSlug) {
|
|
|
125
125
|
data.models.set(bucket, { cost: cost, tokens: inTok + outTok });
|
|
126
126
|
}
|
|
127
127
|
if (outTok > 0 && timestamp > 0 && lastUserTimestamp > 0) {
|
|
128
|
-
|
|
128
|
+
const dur = timestamp - lastUserTimestamp;
|
|
129
129
|
if (dur > 0 && dur < 600000) {
|
|
130
130
|
data.responseTimePoints.push({ tokens: outTok, duration: dur, model: bucket });
|
|
131
131
|
}
|
|
@@ -142,19 +142,19 @@ function parseSessionText(text, sessionId, projectSlug) {
|
|
|
142
142
|
else if (parsed.type === "user") {
|
|
143
143
|
if (timestamp > 0)
|
|
144
144
|
lastUserTimestamp = timestamp;
|
|
145
|
-
|
|
145
|
+
const userMsg = parsed.message;
|
|
146
146
|
if (!userMsg || !Array.isArray(userMsg.content))
|
|
147
147
|
continue;
|
|
148
|
-
|
|
149
|
-
for (
|
|
150
|
-
|
|
148
|
+
const contentArr = userMsg.content;
|
|
149
|
+
for (let j = 0; j < contentArr.length; j++) {
|
|
150
|
+
const block = contentArr[j];
|
|
151
151
|
if (block.type === "tool_result" && typeof block.tool_use_id === "string") {
|
|
152
|
-
|
|
152
|
+
const toolName = block.name || "unknown";
|
|
153
153
|
data.tools.set(toolName, (data.tools.get(toolName) || 0) + 1);
|
|
154
154
|
}
|
|
155
155
|
}
|
|
156
156
|
if (!data.title && Array.isArray(userMsg.content)) {
|
|
157
|
-
for (
|
|
157
|
+
for (let k = 0; k < contentArr.length; k++) {
|
|
158
158
|
if (contentArr[k].type === "text" && typeof contentArr[k].text === "string") {
|
|
159
159
|
data.title = contentArr[k].text.slice(0, 80);
|
|
160
160
|
break;
|
|
@@ -173,7 +173,7 @@ function parseSessionText(text, sessionId, projectSlug) {
|
|
|
173
173
|
}
|
|
174
174
|
function parseSessionFile(filePath, sessionId, projectSlug) {
|
|
175
175
|
try {
|
|
176
|
-
|
|
176
|
+
const text = readFileSync(filePath, "utf-8");
|
|
177
177
|
return parseSessionText(text, sessionId, projectSlug);
|
|
178
178
|
}
|
|
179
179
|
catch {
|
|
@@ -182,7 +182,7 @@ function parseSessionFile(filePath, sessionId, projectSlug) {
|
|
|
182
182
|
}
|
|
183
183
|
async function parseSessionFileAsync(filePath, sessionId, projectSlug) {
|
|
184
184
|
try {
|
|
185
|
-
|
|
185
|
+
const text = await readFile(filePath, "utf-8");
|
|
186
186
|
return parseSessionText(text, sessionId, projectSlug);
|
|
187
187
|
}
|
|
188
188
|
catch {
|
|
@@ -190,20 +190,20 @@ async function parseSessionFileAsync(filePath, sessionId, projectSlug) {
|
|
|
190
190
|
}
|
|
191
191
|
}
|
|
192
192
|
function getSessionFilesForProject(projectPath, cutoff) {
|
|
193
|
-
|
|
194
|
-
|
|
193
|
+
const hash = projectPathToHash(projectPath);
|
|
194
|
+
const dir = join(homedir(), ".claude", "projects", hash);
|
|
195
195
|
if (!existsSync(dir))
|
|
196
196
|
return [];
|
|
197
|
-
|
|
197
|
+
const files = [];
|
|
198
198
|
try {
|
|
199
|
-
|
|
200
|
-
for (
|
|
199
|
+
const entries = readdirSync(dir);
|
|
200
|
+
for (let i = 0; i < entries.length; i++) {
|
|
201
201
|
if (!entries[i].endsWith(".jsonl"))
|
|
202
202
|
continue;
|
|
203
|
-
|
|
203
|
+
const filePath = join(dir, entries[i]);
|
|
204
204
|
if (cutoff && cutoff > 0) {
|
|
205
205
|
try {
|
|
206
|
-
|
|
206
|
+
const mtime = statSync(filePath).mtimeMs;
|
|
207
207
|
if (mtime < cutoff)
|
|
208
208
|
continue;
|
|
209
209
|
}
|
|
@@ -220,35 +220,35 @@ function getSessionFilesForProject(projectPath, cutoff) {
|
|
|
220
220
|
return files;
|
|
221
221
|
}
|
|
222
222
|
function aggregate(sessions, period) {
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
for (
|
|
226
|
-
|
|
227
|
-
|
|
223
|
+
const cutoff = getPeriodCutoff(period);
|
|
224
|
+
const filtered = [];
|
|
225
|
+
for (let i = 0; i < sessions.length; i++) {
|
|
226
|
+
const s = sessions[i];
|
|
227
|
+
const sessionTime = s.endTime > 0 ? s.endTime : s.startTime;
|
|
228
228
|
if (sessionTime >= cutoff)
|
|
229
229
|
filtered.push(s);
|
|
230
230
|
}
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
for (
|
|
231
|
+
let totalCost = 0;
|
|
232
|
+
let totalInput = 0;
|
|
233
|
+
let totalOutput = 0;
|
|
234
|
+
let totalCacheRead = 0;
|
|
235
|
+
let totalCacheCreation = 0;
|
|
236
|
+
let totalDuration = 0;
|
|
237
|
+
let durationCount = 0;
|
|
238
|
+
const dailyCost = new Map();
|
|
239
|
+
const dailySessions = new Map();
|
|
240
|
+
const dailyTokens = new Map();
|
|
241
|
+
const dailyCacheHit = new Map();
|
|
242
|
+
const modelStats = new Map();
|
|
243
|
+
const projectStats = new Map();
|
|
244
|
+
const toolStats = new Map();
|
|
245
|
+
const costBuckets = new Map();
|
|
246
|
+
const bucketOrder = ["<$0.01", "$0.01-0.05", "$0.05-0.10", "$0.10-0.50", "$0.50-1.00", "$1.00-5.00", "$5.00+"];
|
|
247
|
+
for (let b = 0; b < bucketOrder.length; b++) {
|
|
248
248
|
costBuckets.set(bucketOrder[b], 0);
|
|
249
249
|
}
|
|
250
|
-
for (
|
|
251
|
-
|
|
250
|
+
for (let si = 0; si < filtered.length; si++) {
|
|
251
|
+
const sess = filtered[si];
|
|
252
252
|
totalCost += sess.cost;
|
|
253
253
|
totalInput += sess.inputTokens;
|
|
254
254
|
totalOutput += sess.outputTokens;
|
|
@@ -258,8 +258,8 @@ function aggregate(sessions, period) {
|
|
|
258
258
|
totalDuration += sess.endTime - sess.startTime;
|
|
259
259
|
durationCount++;
|
|
260
260
|
}
|
|
261
|
-
|
|
262
|
-
|
|
261
|
+
const date = formatDate(sess.endTime > 0 ? sess.endTime : sess.startTime);
|
|
262
|
+
let dc = dailyCost.get(date);
|
|
263
263
|
if (!dc) {
|
|
264
264
|
dc = { total: 0, opus: 0, sonnet: 0, haiku: 0, other: 0 };
|
|
265
265
|
dailyCost.set(date, dc);
|
|
@@ -269,7 +269,7 @@ function aggregate(sessions, period) {
|
|
|
269
269
|
dc[key] += val.cost;
|
|
270
270
|
});
|
|
271
271
|
dailySessions.set(date, (dailySessions.get(date) || 0) + 1);
|
|
272
|
-
|
|
272
|
+
let dt = dailyTokens.get(date);
|
|
273
273
|
if (!dt) {
|
|
274
274
|
dt = { input: 0, output: 0, cacheRead: 0 };
|
|
275
275
|
dailyTokens.set(date, dt);
|
|
@@ -277,7 +277,7 @@ function aggregate(sessions, period) {
|
|
|
277
277
|
dt.input += sess.inputTokens;
|
|
278
278
|
dt.output += sess.outputTokens;
|
|
279
279
|
dt.cacheRead += sess.cacheReadTokens;
|
|
280
|
-
|
|
280
|
+
let dch = dailyCacheHit.get(date);
|
|
281
281
|
if (!dch) {
|
|
282
282
|
dch = { cacheRead: 0, totalInput: 0 };
|
|
283
283
|
dailyCacheHit.set(date, dch);
|
|
@@ -285,7 +285,7 @@ function aggregate(sessions, period) {
|
|
|
285
285
|
dch.cacheRead += sess.cacheReadTokens;
|
|
286
286
|
dch.totalInput += sess.inputTokens;
|
|
287
287
|
sess.models.forEach(function (val, key) {
|
|
288
|
-
|
|
288
|
+
let ms = modelStats.get(key);
|
|
289
289
|
if (!ms) {
|
|
290
290
|
ms = { sessions: 0, cost: 0, tokens: 0 };
|
|
291
291
|
modelStats.set(key, ms);
|
|
@@ -294,7 +294,7 @@ function aggregate(sessions, period) {
|
|
|
294
294
|
ms.cost += val.cost;
|
|
295
295
|
ms.tokens += val.tokens;
|
|
296
296
|
});
|
|
297
|
-
|
|
297
|
+
let ps = projectStats.get(sess.project);
|
|
298
298
|
if (!ps) {
|
|
299
299
|
ps = { cost: 0, sessions: 0, tokens: 0 };
|
|
300
300
|
projectStats.set(sess.project, ps);
|
|
@@ -303,7 +303,7 @@ function aggregate(sessions, period) {
|
|
|
303
303
|
ps.sessions++;
|
|
304
304
|
ps.tokens += sess.inputTokens + sess.outputTokens;
|
|
305
305
|
sess.tools.forEach(function (count, tool) {
|
|
306
|
-
|
|
306
|
+
let ts = toolStats.get(tool);
|
|
307
307
|
if (!ts) {
|
|
308
308
|
ts = { count: 0, totalCost: 0, sessions: 0 };
|
|
309
309
|
toolStats.set(tool, ts);
|
|
@@ -312,23 +312,23 @@ function aggregate(sessions, period) {
|
|
|
312
312
|
ts.totalCost += sess.cost;
|
|
313
313
|
ts.sessions++;
|
|
314
314
|
});
|
|
315
|
-
|
|
315
|
+
const bucket = getCostBucket(sess.cost);
|
|
316
316
|
if (bucket) {
|
|
317
317
|
costBuckets.set(bucket, (costBuckets.get(bucket) || 0) + 1);
|
|
318
318
|
}
|
|
319
319
|
}
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
for (
|
|
330
|
-
|
|
331
|
-
|
|
320
|
+
const totalTokensAll = totalInput + totalOutput + totalCacheRead + totalCacheCreation;
|
|
321
|
+
const cacheHitRate = (totalInput + totalCacheRead) > 0 ? totalCacheRead / (totalInput + totalCacheRead) : 0;
|
|
322
|
+
const dates = Array.from(dailyCost.keys()).sort();
|
|
323
|
+
const costOverTime = [];
|
|
324
|
+
const cumulativeCost = [];
|
|
325
|
+
const sessionsOverTime = [];
|
|
326
|
+
const tokensOverTime = [];
|
|
327
|
+
const cacheHitRateOverTime = [];
|
|
328
|
+
let cumTotal = 0;
|
|
329
|
+
for (let di = 0; di < dates.length; di++) {
|
|
330
|
+
const d = dates[di];
|
|
331
|
+
const dcEntry = dailyCost.get(d);
|
|
332
332
|
cumTotal += dcEntry.total;
|
|
333
333
|
costOverTime.push({
|
|
334
334
|
date: d,
|
|
@@ -340,32 +340,32 @@ function aggregate(sessions, period) {
|
|
|
340
340
|
});
|
|
341
341
|
cumulativeCost.push({ date: d, total: cumTotal });
|
|
342
342
|
sessionsOverTime.push({ date: d, count: dailySessions.get(d) || 0 });
|
|
343
|
-
|
|
343
|
+
const dtEntry = dailyTokens.get(d);
|
|
344
344
|
tokensOverTime.push({
|
|
345
345
|
date: d,
|
|
346
346
|
input: dtEntry ? dtEntry.input : 0,
|
|
347
347
|
output: dtEntry ? dtEntry.output : 0,
|
|
348
348
|
cacheRead: dtEntry ? dtEntry.cacheRead : 0,
|
|
349
349
|
});
|
|
350
|
-
|
|
351
|
-
|
|
350
|
+
const dchEntry = dailyCacheHit.get(d);
|
|
351
|
+
const rate = dchEntry && (dchEntry.totalInput + dchEntry.cacheRead) > 0 ? dchEntry.cacheRead / (dchEntry.totalInput + dchEntry.cacheRead) : 0;
|
|
352
352
|
cacheHitRateOverTime.push({ date: d, rate: rate });
|
|
353
353
|
}
|
|
354
|
-
|
|
355
|
-
for (
|
|
354
|
+
const costDistribution = [];
|
|
355
|
+
for (let bi = 0; bi < bucketOrder.length; bi++) {
|
|
356
356
|
costDistribution.push({
|
|
357
357
|
bucket: bucketOrder[bi],
|
|
358
358
|
count: costBuckets.get(bucketOrder[bi]) || 0,
|
|
359
359
|
});
|
|
360
360
|
}
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
361
|
+
const sessionBubbles = [];
|
|
362
|
+
const nonZeroCost = filtered.filter(function (s) { return s.cost > 0; });
|
|
363
|
+
const sorted = nonZeroCost.slice().sort(function (a, b) {
|
|
364
364
|
return (b.endTime || b.startTime) - (a.endTime || a.startTime);
|
|
365
365
|
});
|
|
366
|
-
|
|
367
|
-
for (
|
|
368
|
-
|
|
366
|
+
const bubbleCap = Math.min(sorted.length, 200);
|
|
367
|
+
for (let sbi = 0; sbi < bubbleCap; sbi++) {
|
|
368
|
+
const sb = sorted[sbi];
|
|
369
369
|
sessionBubbles.push({
|
|
370
370
|
id: sb.id,
|
|
371
371
|
title: sb.title,
|
|
@@ -375,8 +375,8 @@ function aggregate(sessions, period) {
|
|
|
375
375
|
project: sb.project,
|
|
376
376
|
});
|
|
377
377
|
}
|
|
378
|
-
|
|
379
|
-
|
|
378
|
+
const modelUsage = [];
|
|
379
|
+
const totalModelCost = totalCost || 1;
|
|
380
380
|
modelStats.forEach(function (val, key) {
|
|
381
381
|
modelUsage.push({
|
|
382
382
|
model: key,
|
|
@@ -387,7 +387,7 @@ function aggregate(sessions, period) {
|
|
|
387
387
|
});
|
|
388
388
|
});
|
|
389
389
|
modelUsage.sort(function (a, b) { return b.cost - a.cost; });
|
|
390
|
-
|
|
390
|
+
const projectBreakdown = [];
|
|
391
391
|
projectStats.forEach(function (val, key) {
|
|
392
392
|
projectBreakdown.push({
|
|
393
393
|
project: key,
|
|
@@ -397,7 +397,7 @@ function aggregate(sessions, period) {
|
|
|
397
397
|
});
|
|
398
398
|
});
|
|
399
399
|
projectBreakdown.sort(function (a, b) { return b.cost - a.cost; });
|
|
400
|
-
|
|
400
|
+
const toolUsage = [];
|
|
401
401
|
toolStats.forEach(function (val, key) {
|
|
402
402
|
toolUsage.push({
|
|
403
403
|
tool: key,
|
|
@@ -406,11 +406,11 @@ function aggregate(sessions, period) {
|
|
|
406
406
|
});
|
|
407
407
|
});
|
|
408
408
|
toolUsage.sort(function (a, b) { return b.count - a.count; });
|
|
409
|
-
|
|
410
|
-
for (
|
|
411
|
-
|
|
412
|
-
for (
|
|
413
|
-
|
|
409
|
+
const responseTimeData = [];
|
|
410
|
+
for (let rti = 0; rti < filtered.length; rti++) {
|
|
411
|
+
const rtSess = filtered[rti];
|
|
412
|
+
for (let rtj = 0; rtj < rtSess.responseTimePoints.length; rtj++) {
|
|
413
|
+
const rtp = rtSess.responseTimePoints[rtj];
|
|
414
414
|
responseTimeData.push({ tokens: rtp.tokens, duration: rtp.duration, model: rtp.model, sessionId: rtSess.id });
|
|
415
415
|
}
|
|
416
416
|
if (responseTimeData.length >= 200)
|
|
@@ -418,23 +418,23 @@ function aggregate(sessions, period) {
|
|
|
418
418
|
}
|
|
419
419
|
if (responseTimeData.length > 200)
|
|
420
420
|
responseTimeData.length = 200;
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
for (
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
421
|
+
const contextWindowSizes = { opus: 200000, sonnet: 200000, haiku: 200000, other: 200000 };
|
|
422
|
+
const contextUtilization = [];
|
|
423
|
+
const recentSessions = sorted.slice(0, 5);
|
|
424
|
+
for (let cui = 0; cui < recentSessions.length; cui++) {
|
|
425
|
+
const cuSess = recentSessions[cui];
|
|
426
|
+
let runningTokens = 0;
|
|
427
|
+
let primaryModel = "other";
|
|
428
|
+
let maxModelTokens = 0;
|
|
429
429
|
cuSess.models.forEach(function (val, key) {
|
|
430
430
|
if (val.tokens > maxModelTokens) {
|
|
431
431
|
maxModelTokens = val.tokens;
|
|
432
432
|
primaryModel = key;
|
|
433
433
|
}
|
|
434
434
|
});
|
|
435
|
-
|
|
436
|
-
for (
|
|
437
|
-
|
|
435
|
+
const windowSize = contextWindowSizes[primaryModel] || 200000;
|
|
436
|
+
for (let cmj = 0; cmj < cuSess.contextMessages.length; cmj++) {
|
|
437
|
+
const cm = cuSess.contextMessages[cmj];
|
|
438
438
|
runningTokens += cm.inputTokens;
|
|
439
439
|
contextUtilization.push({
|
|
440
440
|
messageIndex: cm.messageIndex,
|
|
@@ -444,7 +444,7 @@ function aggregate(sessions, period) {
|
|
|
444
444
|
});
|
|
445
445
|
}
|
|
446
446
|
}
|
|
447
|
-
|
|
447
|
+
const sankeyNodes = [
|
|
448
448
|
{ name: "Input Tokens" },
|
|
449
449
|
{ name: "Cache Read" },
|
|
450
450
|
{ name: "Cache Creation" },
|
|
@@ -454,19 +454,19 @@ function aggregate(sessions, period) {
|
|
|
454
454
|
{ name: "Other" },
|
|
455
455
|
{ name: "Output Tokens" },
|
|
456
456
|
];
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
for (
|
|
464
|
-
|
|
465
|
-
|
|
457
|
+
const modelNodeMap = { opus: 3, sonnet: 4, haiku: 5, other: 6 };
|
|
458
|
+
const sankeyLinks = [];
|
|
459
|
+
const modelInputTotals = new Map();
|
|
460
|
+
const modelCacheTotals = new Map();
|
|
461
|
+
const modelCacheCreationTotals = new Map();
|
|
462
|
+
const modelOutputTotals = new Map();
|
|
463
|
+
for (let ski = 0; ski < filtered.length; ski++) {
|
|
464
|
+
const skSess = filtered[ski];
|
|
465
|
+
const skTotal = skSess.inputTokens + skSess.cacheReadTokens + skSess.cacheCreationTokens;
|
|
466
466
|
if (skTotal === 0)
|
|
467
467
|
continue;
|
|
468
468
|
skSess.models.forEach(function (val, key) {
|
|
469
|
-
|
|
469
|
+
const proportion = val.tokens / (skTotal + skSess.outputTokens || 1);
|
|
470
470
|
modelInputTotals.set(key, (modelInputTotals.get(key) || 0) + skSess.inputTokens * proportion);
|
|
471
471
|
modelCacheTotals.set(key, (modelCacheTotals.get(key) || 0) + skSess.cacheReadTokens * proportion);
|
|
472
472
|
modelCacheCreationTotals.set(key, (modelCacheCreationTotals.get(key) || 0) + skSess.cacheCreationTokens * proportion);
|
|
@@ -474,11 +474,11 @@ function aggregate(sessions, period) {
|
|
|
474
474
|
});
|
|
475
475
|
}
|
|
476
476
|
["opus", "sonnet", "haiku", "other"].forEach(function (model) {
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
477
|
+
const nodeIdx = modelNodeMap[model];
|
|
478
|
+
const inputVal = Math.round(modelInputTotals.get(model) || 0);
|
|
479
|
+
const cacheVal = Math.round(modelCacheTotals.get(model) || 0);
|
|
480
|
+
const cacheCreationVal = Math.round(modelCacheCreationTotals.get(model) || 0);
|
|
481
|
+
const outputVal = Math.round(modelOutputTotals.get(model) || 0);
|
|
482
482
|
if (inputVal > 0)
|
|
483
483
|
sankeyLinks.push({ source: 0, target: nodeIdx, value: inputVal });
|
|
484
484
|
if (cacheVal > 0)
|
|
@@ -488,12 +488,12 @@ function aggregate(sessions, period) {
|
|
|
488
488
|
if (outputVal > 0)
|
|
489
489
|
sankeyLinks.push({ source: nodeIdx, target: 7, value: outputVal });
|
|
490
490
|
});
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
for (
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
491
|
+
const tokenFlowSankey = { nodes: sankeyNodes, links: sankeyLinks };
|
|
492
|
+
const activityCalendarMap = new Map();
|
|
493
|
+
for (let aci = 0; aci < filtered.length; aci++) {
|
|
494
|
+
const acSess = filtered[aci];
|
|
495
|
+
const acDate = formatDate(acSess.endTime > 0 ? acSess.endTime : acSess.startTime);
|
|
496
|
+
let acEntry = activityCalendarMap.get(acDate);
|
|
497
497
|
if (!acEntry) {
|
|
498
498
|
acEntry = { count: 0, tokens: 0, cost: 0 };
|
|
499
499
|
activityCalendarMap.set(acDate, acEntry);
|
|
@@ -502,14 +502,14 @@ function aggregate(sessions, period) {
|
|
|
502
502
|
acEntry.tokens += acSess.inputTokens + acSess.outputTokens;
|
|
503
503
|
acEntry.cost += acSess.cost;
|
|
504
504
|
}
|
|
505
|
-
|
|
505
|
+
const activityCalendar = [];
|
|
506
506
|
if (dates.length > 0) {
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
507
|
+
const calStart = new Date(dates[0]);
|
|
508
|
+
const calEnd = new Date(dates[dates.length - 1]);
|
|
509
|
+
const calCursor = new Date(calStart);
|
|
510
510
|
while (calCursor <= calEnd) {
|
|
511
|
-
|
|
512
|
-
|
|
511
|
+
const calKey = formatDate(calCursor.getTime());
|
|
512
|
+
const calData = activityCalendarMap.get(calKey);
|
|
513
513
|
activityCalendar.push({
|
|
514
514
|
date: calKey,
|
|
515
515
|
count: calData ? calData.count : 0,
|
|
@@ -519,31 +519,31 @@ function aggregate(sessions, period) {
|
|
|
519
519
|
calCursor.setDate(calCursor.getDate() + 1);
|
|
520
520
|
}
|
|
521
521
|
}
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
for (
|
|
525
|
-
|
|
522
|
+
const hourlyHeatmap = [];
|
|
523
|
+
const heatmapGrid = new Map();
|
|
524
|
+
for (let hmi = 0; hmi < filtered.length; hmi++) {
|
|
525
|
+
const hmSess = filtered[hmi];
|
|
526
526
|
if (hmSess.startTime <= 0)
|
|
527
527
|
continue;
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
528
|
+
const hmDate = new Date(hmSess.startTime);
|
|
529
|
+
const hmDay = hmDate.getDay();
|
|
530
|
+
const hmHour = hmDate.getHours();
|
|
531
|
+
const hmKey = hmDay + ":" + hmHour;
|
|
532
532
|
heatmapGrid.set(hmKey, (heatmapGrid.get(hmKey) || 0) + 1);
|
|
533
533
|
}
|
|
534
|
-
for (
|
|
535
|
-
for (
|
|
536
|
-
|
|
534
|
+
for (let hd = 0; hd < 7; hd++) {
|
|
535
|
+
for (let hh = 0; hh < 24; hh++) {
|
|
536
|
+
const hhKey = hd + ":" + hh;
|
|
537
537
|
hourlyHeatmap.push({ day: hd, hour: hh, count: heatmapGrid.get(hhKey) || 0 });
|
|
538
538
|
}
|
|
539
539
|
}
|
|
540
|
-
|
|
541
|
-
|
|
540
|
+
const sessionTimeline = [];
|
|
541
|
+
const tlSorted = filtered
|
|
542
542
|
.filter(function (s) { return s.startTime > 0 && s.endTime > 0 && s.cost > 0; })
|
|
543
543
|
.sort(function (a, b) { return b.startTime - a.startTime; });
|
|
544
|
-
|
|
545
|
-
for (
|
|
546
|
-
|
|
544
|
+
const tlCap = Math.min(tlSorted.length, 50);
|
|
545
|
+
for (let tli = 0; tli < tlCap; tli++) {
|
|
546
|
+
const tlSess = tlSorted[tli];
|
|
547
547
|
sessionTimeline.push({
|
|
548
548
|
id: tlSess.id,
|
|
549
549
|
title: tlSess.title,
|
|
@@ -553,11 +553,11 @@ function aggregate(sessions, period) {
|
|
|
553
553
|
cost: tlSess.cost,
|
|
554
554
|
});
|
|
555
555
|
}
|
|
556
|
-
|
|
557
|
-
for (
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
556
|
+
const dailySummaryMap = new Map();
|
|
557
|
+
for (let dsi = 0; dsi < filtered.length; dsi++) {
|
|
558
|
+
const dsSess = filtered[dsi];
|
|
559
|
+
const dsDate = formatDate(dsSess.endTime > 0 ? dsSess.endTime : dsSess.startTime);
|
|
560
|
+
let dsEntry = dailySummaryMap.get(dsDate);
|
|
561
561
|
if (!dsEntry) {
|
|
562
562
|
dsEntry = { sessions: 0, cost: 0, tokens: 0, tools: new Map(), models: new Map() };
|
|
563
563
|
dailySummaryMap.set(dsDate, dsEntry);
|
|
@@ -572,21 +572,21 @@ function aggregate(sessions, period) {
|
|
|
572
572
|
dsEntry.models.set(key, (dsEntry.models.get(key) || 0) + val.cost);
|
|
573
573
|
});
|
|
574
574
|
}
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
for (
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
575
|
+
const dailySummaries = [];
|
|
576
|
+
const dsSortedDates = Array.from(dailySummaryMap.keys()).sort();
|
|
577
|
+
for (let dsdi = 0; dsdi < dsSortedDates.length; dsdi++) {
|
|
578
|
+
const dsd = dsSortedDates[dsdi];
|
|
579
|
+
const dsData = dailySummaryMap.get(dsd);
|
|
580
|
+
let topTool = "";
|
|
581
|
+
let topToolCount = 0;
|
|
582
582
|
dsData.tools.forEach(function (count, tool) {
|
|
583
583
|
if (count > topToolCount) {
|
|
584
584
|
topToolCount = count;
|
|
585
585
|
topTool = tool;
|
|
586
586
|
}
|
|
587
587
|
});
|
|
588
|
-
|
|
589
|
-
|
|
588
|
+
const modelMix = {};
|
|
589
|
+
let modelTotal = 0;
|
|
590
590
|
dsData.models.forEach(function (cost) { modelTotal += cost; });
|
|
591
591
|
if (modelTotal > 0) {
|
|
592
592
|
dsData.models.forEach(function (cost, model) {
|
|
@@ -602,7 +602,7 @@ function aggregate(sessions, period) {
|
|
|
602
602
|
modelMix: modelMix,
|
|
603
603
|
});
|
|
604
604
|
}
|
|
605
|
-
|
|
605
|
+
const toolTreemap = [];
|
|
606
606
|
toolStats.forEach(function (val, key) {
|
|
607
607
|
toolTreemap.push({
|
|
608
608
|
name: key,
|
|
@@ -611,29 +611,29 @@ function aggregate(sessions, period) {
|
|
|
611
611
|
});
|
|
612
612
|
});
|
|
613
613
|
toolTreemap.sort(function (a, b) { return b.count - a.count; });
|
|
614
|
-
|
|
614
|
+
const toolCategoryMap = {
|
|
615
615
|
Read: "Read", Glob: "Read", Grep: "Read", LS: "Read",
|
|
616
616
|
Edit: "Write", Write: "Write", MultiEdit: "Write",
|
|
617
617
|
Bash: "Execute",
|
|
618
618
|
Agent: "AI", Skill: "AI",
|
|
619
619
|
};
|
|
620
|
-
|
|
620
|
+
const toolSunburst = [];
|
|
621
621
|
toolStats.forEach(function (val, key) {
|
|
622
|
-
|
|
622
|
+
const category = toolCategoryMap[key] || "Other";
|
|
623
623
|
toolSunburst.push({ name: key, category: category, count: val.count });
|
|
624
624
|
});
|
|
625
625
|
toolSunburst.sort(function (a, b) { return b.count - a.count; });
|
|
626
|
-
|
|
626
|
+
let totalToolCalls = 0;
|
|
627
627
|
toolStats.forEach(function (val) { totalToolCalls += val.count; });
|
|
628
|
-
|
|
628
|
+
const permissionStats = {
|
|
629
629
|
allowed: totalToolCalls,
|
|
630
630
|
denied: 0,
|
|
631
631
|
alwaysAllowed: 0,
|
|
632
632
|
};
|
|
633
|
-
|
|
634
|
-
for (
|
|
635
|
-
|
|
636
|
-
|
|
633
|
+
const projectRadarMap = new Map();
|
|
634
|
+
for (let pri = 0; pri < filtered.length; pri++) {
|
|
635
|
+
const prSess = filtered[pri];
|
|
636
|
+
let prEntry = projectRadarMap.get(prSess.project);
|
|
637
637
|
if (!prEntry) {
|
|
638
638
|
prEntry = { cost: 0, sessions: 0, totalDuration: 0, durationCount: 0, tools: new Set(), totalTokens: 0 };
|
|
639
639
|
projectRadarMap.set(prSess.project, prEntry);
|
|
@@ -647,7 +647,7 @@ function aggregate(sessions, period) {
|
|
|
647
647
|
}
|
|
648
648
|
prSess.tools.forEach(function (_count, tool) { prEntry.tools.add(tool); });
|
|
649
649
|
}
|
|
650
|
-
|
|
650
|
+
const projectRadar = [];
|
|
651
651
|
projectRadarMap.forEach(function (val, key) {
|
|
652
652
|
projectRadar.push({
|
|
653
653
|
project: key,
|
|
@@ -661,27 +661,27 @@ function aggregate(sessions, period) {
|
|
|
661
661
|
projectRadar.sort(function (a, b) { return b.cost - a.cost; });
|
|
662
662
|
if (projectRadar.length > 5)
|
|
663
663
|
projectRadar.length = 5;
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
for (
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
664
|
+
const contextWindowSizesForComplexity = { opus: 200000, sonnet: 200000, haiku: 200000, other: 200000 };
|
|
665
|
+
const sessionComplexity = [];
|
|
666
|
+
for (let sci = 0; sci < filtered.length; sci++) {
|
|
667
|
+
const scSess = filtered[sci];
|
|
668
|
+
const scUniqueTools = scSess.tools.size;
|
|
669
|
+
const scMessages = scSess.contextMessages.length;
|
|
670
|
+
let scRunning = 0;
|
|
671
|
+
let scPrimaryModel = "other";
|
|
672
|
+
let scMaxTokens = 0;
|
|
673
673
|
scSess.models.forEach(function (val, key) {
|
|
674
674
|
if (val.tokens > scMaxTokens) {
|
|
675
675
|
scMaxTokens = val.tokens;
|
|
676
676
|
scPrimaryModel = key;
|
|
677
677
|
}
|
|
678
678
|
});
|
|
679
|
-
|
|
680
|
-
for (
|
|
679
|
+
const scWindowSize = contextWindowSizesForComplexity[scPrimaryModel] || 200000;
|
|
680
|
+
for (let scmi = 0; scmi < scSess.contextMessages.length; scmi++) {
|
|
681
681
|
scRunning += scSess.contextMessages[scmi].inputTokens;
|
|
682
682
|
}
|
|
683
|
-
|
|
684
|
-
|
|
683
|
+
const scContextPercent = Math.min((scRunning / scWindowSize) * 100, 100);
|
|
684
|
+
const scScore = (scMessages * 1) + (scUniqueTools * 5) + (scContextPercent * 0.5);
|
|
685
685
|
sessionComplexity.push({
|
|
686
686
|
id: scSess.id,
|
|
687
687
|
title: scSess.title,
|
|
@@ -731,54 +731,54 @@ function aggregate(sessions, period) {
|
|
|
731
731
|
};
|
|
732
732
|
}
|
|
733
733
|
export async function getAnalytics(scope, period, projectSlug, sessionId, forceRefresh) {
|
|
734
|
-
|
|
734
|
+
let cacheKey = scope + ":" + period + ":" + (projectSlug || "all");
|
|
735
735
|
if (sessionId)
|
|
736
736
|
cacheKey += ":" + sessionId;
|
|
737
737
|
if (!forceRefresh) {
|
|
738
|
-
|
|
738
|
+
const cached = cache.get(cacheKey);
|
|
739
739
|
if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
|
|
740
740
|
return Promise.resolve(cached.data);
|
|
741
741
|
}
|
|
742
|
-
|
|
742
|
+
const existing = inflight.get(cacheKey);
|
|
743
743
|
if (existing)
|
|
744
744
|
return existing;
|
|
745
745
|
}
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
746
|
+
const config = loadConfig();
|
|
747
|
+
const cutoff = getPeriodCutoff(period);
|
|
748
|
+
const fileRefs = [];
|
|
749
749
|
if (scope === "global") {
|
|
750
|
-
for (
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
for (
|
|
750
|
+
for (let i = 0; i < config.projects.length; i++) {
|
|
751
|
+
const proj = config.projects[i];
|
|
752
|
+
const files = getSessionFilesForProject(proj.path, cutoff);
|
|
753
|
+
for (let j = 0; j < files.length; j++) {
|
|
754
754
|
fileRefs.push({ path: files[j].path, id: files[j].id, slug: proj.slug });
|
|
755
755
|
}
|
|
756
756
|
}
|
|
757
757
|
}
|
|
758
758
|
else if (scope === "project" && projectSlug) {
|
|
759
|
-
|
|
759
|
+
const project = config.projects.find(function (p) { return p.slug === projectSlug; });
|
|
760
760
|
if (project) {
|
|
761
|
-
|
|
762
|
-
for (
|
|
761
|
+
const projFiles = getSessionFilesForProject(project.path, cutoff);
|
|
762
|
+
for (let pf = 0; pf < projFiles.length; pf++) {
|
|
763
763
|
fileRefs.push({ path: projFiles[pf].path, id: projFiles[pf].id, slug: projectSlug });
|
|
764
764
|
}
|
|
765
765
|
}
|
|
766
766
|
}
|
|
767
767
|
else if (scope === "session" && projectSlug && sessionId) {
|
|
768
|
-
|
|
768
|
+
const sessProject = config.projects.find(function (p) { return p.slug === projectSlug; });
|
|
769
769
|
if (sessProject) {
|
|
770
|
-
|
|
771
|
-
|
|
770
|
+
const hash = projectPathToHash(sessProject.path);
|
|
771
|
+
const filePath = join(homedir(), ".claude", "projects", hash, sessionId + ".jsonl");
|
|
772
772
|
if (existsSync(filePath)) {
|
|
773
773
|
fileRefs.push({ path: filePath, id: sessionId, slug: projectSlug });
|
|
774
774
|
}
|
|
775
775
|
}
|
|
776
776
|
}
|
|
777
|
-
|
|
777
|
+
const promise = Promise.all(fileRefs.map(function (ref) {
|
|
778
778
|
return parseSessionFileAsync(ref.path, ref.id, ref.slug);
|
|
779
779
|
})).then(function (results) {
|
|
780
|
-
|
|
781
|
-
|
|
780
|
+
const sessions = results.filter(function (s) { return s !== null; });
|
|
781
|
+
const result = aggregate(sessions, period);
|
|
782
782
|
cache.set(cacheKey, { data: result, timestamp: Date.now() });
|
|
783
783
|
inflight.delete(cacheKey);
|
|
784
784
|
return result;
|
|
@@ -786,24 +786,24 @@ export async function getAnalytics(scope, period, projectSlug, sessionId, forceR
|
|
|
786
786
|
inflight.set(cacheKey, promise);
|
|
787
787
|
return promise;
|
|
788
788
|
}
|
|
789
|
-
|
|
790
|
-
|
|
789
|
+
let dailySpendCache = null;
|
|
790
|
+
const DAILY_SPEND_CACHE_TTL = 30 * 1000;
|
|
791
791
|
export function getDailySpend() {
|
|
792
792
|
if (dailySpendCache && Date.now() - dailySpendCache.timestamp < DAILY_SPEND_CACHE_TTL) {
|
|
793
793
|
return dailySpendCache.value;
|
|
794
794
|
}
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
for (
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
for (
|
|
803
|
-
|
|
795
|
+
const config = loadConfig();
|
|
796
|
+
const now = new Date();
|
|
797
|
+
const todayStart = new Date(now.getFullYear(), now.getMonth(), now.getDate()).getTime();
|
|
798
|
+
let totalCost = 0;
|
|
799
|
+
for (let i = 0; i < config.projects.length; i++) {
|
|
800
|
+
const proj = config.projects[i];
|
|
801
|
+
const files = getSessionFilesForProject(proj.path);
|
|
802
|
+
for (let j = 0; j < files.length; j++) {
|
|
803
|
+
const data = parseSessionFile(files[j].path, files[j].id, proj.slug);
|
|
804
804
|
if (!data)
|
|
805
805
|
continue;
|
|
806
|
-
|
|
806
|
+
const sessionTime = data.endTime > 0 ? data.endTime : data.startTime;
|
|
807
807
|
if (sessionTime >= todayStart) {
|
|
808
808
|
totalCost += data.cost;
|
|
809
809
|
}
|
|
@@ -816,14 +816,14 @@ export function invalidateDailySpendCache() {
|
|
|
816
816
|
dailySpendCache = null;
|
|
817
817
|
}
|
|
818
818
|
export async function streamAnalyticsSections(scope, period, projectSlug, sessionId, forceRefresh, onSection) {
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
for (
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
for (
|
|
826
|
-
|
|
819
|
+
const payload = await getAnalytics(scope, period, projectSlug, sessionId, forceRefresh);
|
|
820
|
+
const sectionNames = ["summary", "spending", "usage", "activity", "projects"];
|
|
821
|
+
for (let si = 0; si < sectionNames.length; si++) {
|
|
822
|
+
const name = sectionNames[si];
|
|
823
|
+
const keys = SECTION_KEYS[name];
|
|
824
|
+
const sectionData = {};
|
|
825
|
+
for (let ki = 0; ki < keys.length; ki++) {
|
|
826
|
+
const key = keys[ki];
|
|
827
827
|
sectionData[key] = payload[key];
|
|
828
828
|
}
|
|
829
829
|
onSection(name, sectionData);
|