claude-code-session-manager 0.14.0 → 0.15.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/assets/{TiptapBody-CzNkD0kT.js → TiptapBody-D0tfDVZb.js} +1 -1
- package/dist/assets/{cssMode-DUMBBgBO.js → cssMode-C3tZkaJ9.js} +1 -1
- package/dist/assets/{freemarker2-BjnoO8uS.js → freemarker2-DEh8tC5X.js} +1 -1
- package/dist/assets/{handlebars-CggfdTjZ.js → handlebars-D_6KmsOK.js} +1 -1
- package/dist/assets/{html-Bcnp_pNA.js → html-DF1KVjzv.js} +1 -1
- package/dist/assets/{htmlMode-CFws8rf5.js → htmlMode-DEakPokt.js} +1 -1
- package/dist/assets/{index-DOo1Vtox.css → index-D-kX3T0V.css} +1 -1
- package/dist/assets/{index-0geebEag.js → index-Zg61GP50.js} +511 -511
- package/dist/assets/{javascript-DI4e0TwT.js → javascript-Du7a359D.js} +1 -1
- package/dist/assets/{jsonMode-Ca0fgEAF.js → jsonMode-W3BJwbUD.js} +1 -1
- package/dist/assets/{liquid-E7hk4hx9.js → liquid-DDj1fqca.js} +1 -1
- package/dist/assets/{lspLanguageFeatures-Wh7EFavn.js → lspLanguageFeatures-uyEbiR-d.js} +1 -1
- package/dist/assets/{mdx-iXIhpMBJ.js → mdx-DUqSETvC.js} +1 -1
- package/dist/assets/{python-laxy9eG1.js → python-D7S2lUAn.js} +1 -1
- package/dist/assets/{razor-CatPL_eG.js → razor-19nfZNaZ.js} +1 -1
- package/dist/assets/{tsMode-D2WL55Cf.js → tsMode-CQke5zpL.js} +1 -1
- package/dist/assets/{typescript-B5DFbVoi.js → typescript-D4ge0PUF.js} +1 -1
- package/dist/assets/{xml-BsStStQW.js → xml-D42QDS7q.js} +1 -1
- package/dist/assets/{yaml-rg2IIDpu.js → yaml-CEiz9NyU.js} +1 -1
- package/dist/index.html +2 -2
- package/package.json +1 -1
- package/src/main/index.cjs +4 -0
- package/src/main/transcripts.cjs +12 -0
- package/src/main/usageMatrix.cjs +336 -0
- package/src/preload/api.d.ts +52 -0
- package/src/preload/index.cjs +8 -0
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* usageMatrix.cjs — per-tab agentic-ops aggregator.
|
|
3
|
+
*
|
|
4
|
+
* Watches the same classified transcript events that transcripts.cjs streams
|
|
5
|
+
* to the renderer and maintains a small rolling state per tab: turn count,
|
|
6
|
+
* token velocity (5-min window), cache age, subagent fan-out, and behavioral
|
|
7
|
+
* flags (geometric context growth, cache cold-start risk, long session).
|
|
8
|
+
*
|
|
9
|
+
* Lives in main rather than the renderer so a renderer reload does not lose
|
|
10
|
+
* the rolling counters — multi-hour sessions are the primary use case and
|
|
11
|
+
* losing the velocity series at a reload would make the dashboard useless
|
|
12
|
+
* exactly when it matters most.
|
|
13
|
+
*
|
|
14
|
+
* Broadcasts `usage:matrix:tick` on a 5-second cadence (and once on demand
|
|
15
|
+
* via the `usage:matrix:snapshot` handler at mount).
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
'use strict';
|
|
19
|
+
|
|
20
|
+
const path = require('node:path');
|
|
21
|
+
const { ipcMain } = require('electron');
|
|
22
|
+
const { sendIfAlive } = require('./lib/sendToRenderer.cjs');
|
|
23
|
+
|
|
24
|
+
const TICK_MS = 5_000;
|
|
25
|
+
const TOKEN_WINDOW_MS = 5 * 60_000; // 5 min rolling window for tokens/min
|
|
26
|
+
const TURN_RING = 20; // per-turn input-token series cap
|
|
27
|
+
const TOOL_RING = 100; // recent tool-name series cap
|
|
28
|
+
const CACHE_TTL_MS = 60 * 60_000; // Anthropic prefix cache ~60 min
|
|
29
|
+
const CACHE_WARN_MS = 50 * 60_000; // start warning at 50 min
|
|
30
|
+
|
|
31
|
+
const BURN_LOW = 5_000; // tokens/min
|
|
32
|
+
const BURN_CRITICAL = 30_000; // tokens/min
|
|
33
|
+
const LONG_SESSION_TURNS = 70;
|
|
34
|
+
const GEOMETRIC_MIN_TOKENS = 50_000; // don't flag tiny series
|
|
35
|
+
|
|
36
|
+
/** Map<tabId, TabState> */
|
|
37
|
+
const tabs = new Map();
|
|
38
|
+
let window = null;
|
|
39
|
+
let tickTimer = null;
|
|
40
|
+
let dirty = false;
|
|
41
|
+
|
|
42
|
+
function attachWindow(w) {
|
|
43
|
+
window = w;
|
|
44
|
+
if (!tickTimer) {
|
|
45
|
+
tickTimer = setInterval(broadcastIfChanged, TICK_MS);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function workspaceName(cwd) {
|
|
50
|
+
if (!cwd) return '(unknown)';
|
|
51
|
+
return path.basename(cwd) || cwd;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function blankTab(tabId, cwd, sessionUuid) {
|
|
55
|
+
return {
|
|
56
|
+
tabId,
|
|
57
|
+
cwd,
|
|
58
|
+
sessionUuid,
|
|
59
|
+
workspace: workspaceName(cwd),
|
|
60
|
+
startedAt: Date.now(),
|
|
61
|
+
lastEventAt: 0,
|
|
62
|
+
lastAssistantAt: 0,
|
|
63
|
+
lastUserAt: 0,
|
|
64
|
+
lastPlanAt: 0,
|
|
65
|
+
turns: 0,
|
|
66
|
+
cumulativeUsage: { input: 0, output: 0, cacheRead: 0, cacheCreate: 0 },
|
|
67
|
+
perTurnInputTokens: [],
|
|
68
|
+
tokenWindow: [], // [{ts, tokens}]
|
|
69
|
+
toolSeries: [], // [{ts, name}]
|
|
70
|
+
agentSpawns: 0,
|
|
71
|
+
agentsActive: new Map(), // toolUseId -> {at}
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function ensureTab(tabId, cwd, sessionUuid) {
|
|
76
|
+
let t = tabs.get(tabId);
|
|
77
|
+
if (!t) {
|
|
78
|
+
t = blankTab(tabId, cwd, sessionUuid);
|
|
79
|
+
tabs.set(tabId, t);
|
|
80
|
+
}
|
|
81
|
+
return t;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Feed one classified transcript event into the per-tab aggregator. Called
|
|
86
|
+
* from transcripts.cjs for every event, both during replay and live.
|
|
87
|
+
*/
|
|
88
|
+
function recordEvent({ tabId, cwd, sessionUuid, ev }) {
|
|
89
|
+
if (!tabId || !ev) return;
|
|
90
|
+
const t = ensureTab(tabId, cwd, sessionUuid);
|
|
91
|
+
const now = Date.now();
|
|
92
|
+
t.lastEventAt = now;
|
|
93
|
+
|
|
94
|
+
switch (ev.kind) {
|
|
95
|
+
case 'usage': {
|
|
96
|
+
// Each usage rollup = one assistant turn in Claude Code's transcript.
|
|
97
|
+
const u = ev.data || {};
|
|
98
|
+
const inTok = (u.input_tokens || 0)
|
|
99
|
+
+ (u.cache_creation_input_tokens || 0)
|
|
100
|
+
+ (u.cache_read_input_tokens || 0);
|
|
101
|
+
const outTok = u.output_tokens || 0;
|
|
102
|
+
t.cumulativeUsage.input += u.input_tokens || 0;
|
|
103
|
+
t.cumulativeUsage.output += outTok;
|
|
104
|
+
t.cumulativeUsage.cacheRead += u.cache_read_input_tokens || 0;
|
|
105
|
+
t.cumulativeUsage.cacheCreate += u.cache_creation_input_tokens || 0;
|
|
106
|
+
t.turns += 1;
|
|
107
|
+
t.lastAssistantAt = now;
|
|
108
|
+
|
|
109
|
+
t.perTurnInputTokens.push(inTok);
|
|
110
|
+
if (t.perTurnInputTokens.length > TURN_RING) t.perTurnInputTokens.shift();
|
|
111
|
+
|
|
112
|
+
t.tokenWindow.push({ ts: now, tokens: inTok + outTok });
|
|
113
|
+
pruneWindow(t.tokenWindow, now);
|
|
114
|
+
dirty = true;
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
case 'tool_use': {
|
|
118
|
+
const name = ev.data?.name;
|
|
119
|
+
if (name) {
|
|
120
|
+
t.toolSeries.push({ ts: now, name });
|
|
121
|
+
if (t.toolSeries.length > TOOL_RING) t.toolSeries.shift();
|
|
122
|
+
}
|
|
123
|
+
dirty = true;
|
|
124
|
+
break;
|
|
125
|
+
}
|
|
126
|
+
case 'agent_spawn': {
|
|
127
|
+
t.agentSpawns += 1;
|
|
128
|
+
const id = ev.data?.toolUseId;
|
|
129
|
+
if (id) t.agentsActive.set(id, { at: now });
|
|
130
|
+
dirty = true;
|
|
131
|
+
break;
|
|
132
|
+
}
|
|
133
|
+
case 'tool_result': {
|
|
134
|
+
const id = ev.data?.toolUseId;
|
|
135
|
+
if (id && t.agentsActive.has(id)) {
|
|
136
|
+
t.agentsActive.delete(id);
|
|
137
|
+
dirty = true;
|
|
138
|
+
}
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
141
|
+
case 'plan': {
|
|
142
|
+
t.lastPlanAt = now;
|
|
143
|
+
dirty = true;
|
|
144
|
+
break;
|
|
145
|
+
}
|
|
146
|
+
default:
|
|
147
|
+
break;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function pruneWindow(arr, now) {
|
|
152
|
+
const cutoff = now - TOKEN_WINDOW_MS;
|
|
153
|
+
while (arr.length && arr[0].ts < cutoff) arr.shift();
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function removeTab(tabId) {
|
|
157
|
+
if (tabs.delete(tabId)) dirty = true;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Build a derived snapshot. Heavy work happens here, not on the hot
|
|
162
|
+
* recordEvent path — recordEvent only appends to ring buffers and flips a
|
|
163
|
+
* dirty bit.
|
|
164
|
+
*/
|
|
165
|
+
function buildSnapshot() {
|
|
166
|
+
const now = Date.now();
|
|
167
|
+
const out = [];
|
|
168
|
+
|
|
169
|
+
let totalSubagentsActive = 0;
|
|
170
|
+
let totalSubagentsSpawned = 0;
|
|
171
|
+
let totalTokensToday = 0;
|
|
172
|
+
let combinedTokensPerMin = 0;
|
|
173
|
+
|
|
174
|
+
for (const t of tabs.values()) {
|
|
175
|
+
pruneWindow(t.tokenWindow, now);
|
|
176
|
+
const windowTokens = t.tokenWindow.reduce((s, e) => s + e.tokens, 0);
|
|
177
|
+
const windowMin = Math.min(TOKEN_WINDOW_MS, now - (t.tokenWindow[0]?.ts ?? now)) / 60_000;
|
|
178
|
+
const tokensPerMin = windowMin > 0.1 ? Math.round(windowTokens / windowMin) : 0;
|
|
179
|
+
combinedTokensPerMin += tokensPerMin;
|
|
180
|
+
|
|
181
|
+
const intensity = tokensPerMin >= BURN_CRITICAL
|
|
182
|
+
? 'critical'
|
|
183
|
+
: tokensPerMin >= BURN_LOW
|
|
184
|
+
? 'medium'
|
|
185
|
+
: tokensPerMin > 0
|
|
186
|
+
? 'low'
|
|
187
|
+
: 'idle';
|
|
188
|
+
|
|
189
|
+
const cacheAgeMs = t.lastAssistantAt ? now - t.lastAssistantAt : null;
|
|
190
|
+
const cacheState = cacheAgeMs == null
|
|
191
|
+
? 'cold'
|
|
192
|
+
: cacheAgeMs > CACHE_TTL_MS
|
|
193
|
+
? 'cold'
|
|
194
|
+
: cacheAgeMs > CACHE_WARN_MS
|
|
195
|
+
? 'expiring'
|
|
196
|
+
: 'warm';
|
|
197
|
+
|
|
198
|
+
const avgPerTurn = t.perTurnInputTokens.length > 0
|
|
199
|
+
? Math.round(t.perTurnInputTokens.reduce((s, n) => s + n, 0) / t.perTurnInputTokens.length)
|
|
200
|
+
: 0;
|
|
201
|
+
|
|
202
|
+
const geometricGrowth = detectGeometricGrowth(t.perTurnInputTokens);
|
|
203
|
+
|
|
204
|
+
// State derivation. Order matters — earlier branches win.
|
|
205
|
+
let state;
|
|
206
|
+
if (t.lastPlanAt > 0 && (now - t.lastPlanAt) < 60_000 && t.lastPlanAt >= t.lastAssistantAt) {
|
|
207
|
+
state = 'plan';
|
|
208
|
+
} else if (t.agentsActive.size > 0 && (now - t.lastAssistantAt) > 10_000) {
|
|
209
|
+
state = 'background';
|
|
210
|
+
} else if (t.lastEventAt > 0 && (now - t.lastEventAt) < 30_000) {
|
|
211
|
+
state = 'executing';
|
|
212
|
+
} else {
|
|
213
|
+
state = 'idle';
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const subagentsActive = t.agentsActive.size;
|
|
217
|
+
totalSubagentsActive += subagentsActive;
|
|
218
|
+
totalSubagentsSpawned += t.agentSpawns;
|
|
219
|
+
totalTokensToday += t.cumulativeUsage.input + t.cumulativeUsage.output;
|
|
220
|
+
|
|
221
|
+
const alerts = [];
|
|
222
|
+
if (geometricGrowth) {
|
|
223
|
+
alerts.push({
|
|
224
|
+
level: 'warn',
|
|
225
|
+
code: 'geometric-growth',
|
|
226
|
+
message: `Per-turn input grew geometrically (avg ${(avgPerTurn / 1000).toFixed(0)}k). Run /compact.`,
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
if (t.turns >= LONG_SESSION_TURNS) {
|
|
230
|
+
alerts.push({
|
|
231
|
+
level: 'warn',
|
|
232
|
+
code: 'long-session',
|
|
233
|
+
message: `Session is at turn ${t.turns}. Context resubmissions cost ${(avgPerTurn / 1000).toFixed(0)}k tokens each.`,
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
if (cacheState === 'expiring' && state === 'idle') {
|
|
237
|
+
const minLeft = Math.max(0, Math.round((CACHE_TTL_MS - (cacheAgeMs ?? 0)) / 60_000));
|
|
238
|
+
alerts.push({
|
|
239
|
+
level: 'info',
|
|
240
|
+
code: 'cache-cold-start',
|
|
241
|
+
message: `Cache cold-start in ${minLeft}m. Next command will incur full cache_creation cost.`,
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
if (cacheState === 'cold' && t.lastAssistantAt > 0) {
|
|
245
|
+
alerts.push({
|
|
246
|
+
level: 'info',
|
|
247
|
+
code: 'cache-cold',
|
|
248
|
+
message: 'Cache expired. Next command pays full cache_creation pricing.',
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
if (subagentsActive >= 4) {
|
|
252
|
+
alerts.push({
|
|
253
|
+
level: 'info',
|
|
254
|
+
code: 'subagent-fanout',
|
|
255
|
+
message: `${subagentsActive} subagents in flight. Consider waiting before fanning out more.`,
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
out.push({
|
|
260
|
+
tabId: t.tabId,
|
|
261
|
+
workspace: t.workspace,
|
|
262
|
+
cwd: t.cwd,
|
|
263
|
+
state,
|
|
264
|
+
turns: t.turns,
|
|
265
|
+
lastEventAt: t.lastEventAt,
|
|
266
|
+
lastAssistantAt: t.lastAssistantAt,
|
|
267
|
+
cacheAgeMs,
|
|
268
|
+
cacheState,
|
|
269
|
+
tokensPerMin,
|
|
270
|
+
intensity,
|
|
271
|
+
avgTokensPerTurn: avgPerTurn,
|
|
272
|
+
cumulativeUsage: { ...t.cumulativeUsage },
|
|
273
|
+
subagentsActive,
|
|
274
|
+
subagentsSpawned: t.agentSpawns,
|
|
275
|
+
geometricGrowth,
|
|
276
|
+
alerts,
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Most active first.
|
|
281
|
+
out.sort((a, b) => b.tokensPerMin - a.tokensPerMin || b.lastEventAt - a.lastEventAt);
|
|
282
|
+
|
|
283
|
+
return {
|
|
284
|
+
generatedAt: now,
|
|
285
|
+
tabs: out,
|
|
286
|
+
totals: {
|
|
287
|
+
activeSessions: out.length,
|
|
288
|
+
subagentsActive: totalSubagentsActive,
|
|
289
|
+
subagentsSpawned: totalSubagentsSpawned,
|
|
290
|
+
tokensTotal: totalTokensToday,
|
|
291
|
+
combinedTokensPerMin,
|
|
292
|
+
},
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Geometric growth: avg of last 3 turns > 1.8 × avg of prior 3 turns,
|
|
298
|
+
* and last-3 avg above GEOMETRIC_MIN_TOKENS so trivial sessions don't flag.
|
|
299
|
+
*/
|
|
300
|
+
function detectGeometricGrowth(series) {
|
|
301
|
+
if (series.length < 6) return false;
|
|
302
|
+
const tail = series.slice(-3);
|
|
303
|
+
const prev = series.slice(-6, -3);
|
|
304
|
+
const tailAvg = tail.reduce((s, n) => s + n, 0) / 3;
|
|
305
|
+
const prevAvg = prev.reduce((s, n) => s + n, 0) / 3;
|
|
306
|
+
if (tailAvg < GEOMETRIC_MIN_TOKENS) return false;
|
|
307
|
+
if (prevAvg <= 0) return false;
|
|
308
|
+
return tailAvg / prevAvg >= 1.8;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function broadcastIfChanged() {
|
|
312
|
+
if (!dirty) return;
|
|
313
|
+
dirty = false;
|
|
314
|
+
if (!window) return;
|
|
315
|
+
sendIfAlive(window, 'usage:matrix:tick', buildSnapshot());
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function registerHandlers() {
|
|
319
|
+
ipcMain.handle('usage:matrix:snapshot', () => buildSnapshot());
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function closeAll() {
|
|
323
|
+
if (tickTimer) {
|
|
324
|
+
clearInterval(tickTimer);
|
|
325
|
+
tickTimer = null;
|
|
326
|
+
}
|
|
327
|
+
tabs.clear();
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
module.exports = {
|
|
331
|
+
attachWindow,
|
|
332
|
+
recordEvent,
|
|
333
|
+
removeTab,
|
|
334
|
+
registerHandlers,
|
|
335
|
+
closeAll,
|
|
336
|
+
};
|
package/src/preload/api.d.ts
CHANGED
|
@@ -130,6 +130,54 @@ export type BillingFetchResult =
|
|
|
130
130
|
| { kind: 'transient'; message: string; httpStatus: number | null }
|
|
131
131
|
| { kind: 'config'; message: string };
|
|
132
132
|
|
|
133
|
+
// ── Usage matrix (AgOps dashboard, main-side aggregator)
|
|
134
|
+
export type UsageMatrixState = 'idle' | 'executing' | 'plan' | 'background';
|
|
135
|
+
export type UsageMatrixIntensity = 'idle' | 'low' | 'medium' | 'critical';
|
|
136
|
+
export type UsageMatrixCacheState = 'warm' | 'expiring' | 'cold';
|
|
137
|
+
|
|
138
|
+
export interface UsageMatrixAlert {
|
|
139
|
+
level: 'info' | 'warn';
|
|
140
|
+
code:
|
|
141
|
+
| 'geometric-growth'
|
|
142
|
+
| 'long-session'
|
|
143
|
+
| 'cache-cold-start'
|
|
144
|
+
| 'cache-cold'
|
|
145
|
+
| 'subagent-fanout';
|
|
146
|
+
message: string;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export interface UsageMatrixTab {
|
|
150
|
+
tabId: string;
|
|
151
|
+
workspace: string;
|
|
152
|
+
cwd: string;
|
|
153
|
+
state: UsageMatrixState;
|
|
154
|
+
turns: number;
|
|
155
|
+
lastEventAt: number;
|
|
156
|
+
lastAssistantAt: number;
|
|
157
|
+
cacheAgeMs: number | null;
|
|
158
|
+
cacheState: UsageMatrixCacheState;
|
|
159
|
+
tokensPerMin: number;
|
|
160
|
+
intensity: UsageMatrixIntensity;
|
|
161
|
+
avgTokensPerTurn: number;
|
|
162
|
+
cumulativeUsage: { input: number; output: number; cacheRead: number; cacheCreate: number };
|
|
163
|
+
subagentsActive: number;
|
|
164
|
+
subagentsSpawned: number;
|
|
165
|
+
geometricGrowth: boolean;
|
|
166
|
+
alerts: UsageMatrixAlert[];
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export interface UsageMatrixSnapshot {
|
|
170
|
+
generatedAt: number;
|
|
171
|
+
tabs: UsageMatrixTab[];
|
|
172
|
+
totals: {
|
|
173
|
+
activeSessions: number;
|
|
174
|
+
subagentsActive: number;
|
|
175
|
+
subagentsSpawned: number;
|
|
176
|
+
tokensTotal: number;
|
|
177
|
+
combinedTokensPerMin: number;
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
133
181
|
export interface VoiceHotkeyConfig {
|
|
134
182
|
accelerator: string;
|
|
135
183
|
mode: 'hold' | 'toggle';
|
|
@@ -787,6 +835,10 @@ export interface SessionManagerAPI {
|
|
|
787
835
|
billing: {
|
|
788
836
|
fetch: () => Promise<BillingFetchResult>;
|
|
789
837
|
};
|
|
838
|
+
usageMatrix: {
|
|
839
|
+
snapshot: () => Promise<UsageMatrixSnapshot>;
|
|
840
|
+
onTick: (handler: (snap: UsageMatrixSnapshot) => void) => () => void;
|
|
841
|
+
};
|
|
790
842
|
logs: {
|
|
791
843
|
write: (scope: string, level: 'debug' | 'info' | 'warn' | 'error', message: string, meta?: unknown) => void;
|
|
792
844
|
dir: () => Promise<string>;
|
package/src/preload/index.cjs
CHANGED
|
@@ -76,6 +76,14 @@ contextBridge.exposeInMainWorld('api', {
|
|
|
76
76
|
billing: {
|
|
77
77
|
fetch: () => ipcRenderer.invoke('billing:fetch'),
|
|
78
78
|
},
|
|
79
|
+
usageMatrix: {
|
|
80
|
+
snapshot: () => ipcRenderer.invoke('usage:matrix:snapshot'),
|
|
81
|
+
onTick: (handler) => {
|
|
82
|
+
const listener = (_e, payload) => handler(payload);
|
|
83
|
+
ipcRenderer.on('usage:matrix:tick', listener);
|
|
84
|
+
return () => ipcRenderer.removeListener('usage:matrix:tick', listener);
|
|
85
|
+
},
|
|
86
|
+
},
|
|
79
87
|
logs: {
|
|
80
88
|
write: (scope, level, message, meta) =>
|
|
81
89
|
ipcRenderer.send('log:write', { scope, level, message, meta }),
|