@mbeato/contextscope 0.1.4 → 0.1.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/.next/standalone/.next/BUILD_ID +1 -1
- package/.next/standalone/.next/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/_global-error.html +1 -1
- package/.next/standalone/.next/server/app/_global-error.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_not-found.html +1 -1
- package/.next/standalone/.next/server/app/_not-found.rsc +3 -3
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/context/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/items/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/page.js +2 -2
- package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/sessions/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0j40w4k._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0p2sxww._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0q3rzj9._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/app_page_tsx_0fwe3kl._.js +3 -0
- package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
- package/.next/standalone/.next/server/pages/404.html +1 -1
- package/.next/standalone/.next/server/pages/500.html +1 -1
- package/.next/standalone/app/page.tsx +291 -258
- package/.next/standalone/bin/cli.js +22 -6
- package/.next/standalone/lib/transcripts.ts +103 -41
- package/.next/standalone/package.json +1 -1
- package/.next/standalone/tsconfig.tsbuildinfo +1 -1
- package/.next/static/chunks/0pb14~6.l8.f9.css +1 -0
- package/bin/cli.js +22 -6
- package/package.json +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0lbywin._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_0j0avc7._.js +0 -3
- package/.next/static/chunks/118uk9v3812u1.css +0 -1
- /package/.next/static/{kaNzMZNco9qjeyEFA-gjr → uI28k7hBjH7vKGE3jdIaM}/_buildManifest.js +0 -0
- /package/.next/static/{kaNzMZNco9qjeyEFA-gjr → uI28k7hBjH7vKGE3jdIaM}/_clientMiddlewareManifest.js +0 -0
- /package/.next/static/{kaNzMZNco9qjeyEFA-gjr → uI28k7hBjH7vKGE3jdIaM}/_ssgManifest.js +0 -0
|
@@ -78,8 +78,18 @@ function inferProjectPath(dirName: string): string {
|
|
|
78
78
|
|
|
79
79
|
async function parseFile(filePath: string, mtimeMs: number): Promise<TranscriptResult> {
|
|
80
80
|
const parts = filePath.split("/");
|
|
81
|
-
|
|
82
|
-
|
|
81
|
+
// Two shapes:
|
|
82
|
+
// <project>/<session-uuid>.jsonl → main session file
|
|
83
|
+
// <project>/<session-uuid>/subagents/agent-<id>.jsonl → subagent file
|
|
84
|
+
// For subagents we want the parent session UUID + parent project, so the
|
|
85
|
+
// tokens roll up to the same Session as the main file.
|
|
86
|
+
const isSubagent = parts[parts.length - 2] === "subagents";
|
|
87
|
+
const sessionId = isSubagent
|
|
88
|
+
? (parts[parts.length - 3] ?? "")
|
|
89
|
+
: parts[parts.length - 1].replace(/\.jsonl$/, "");
|
|
90
|
+
const project = isSubagent
|
|
91
|
+
? (parts[parts.length - 4] ?? "")
|
|
92
|
+
: (parts[parts.length - 2] ?? "");
|
|
83
93
|
const result: TranscriptResult = {
|
|
84
94
|
filePath,
|
|
85
95
|
mtimeMs,
|
|
@@ -227,30 +237,62 @@ async function pMapLimit<T, R>(items: T[], limit: number, fn: (x: T) => Promise<
|
|
|
227
237
|
}
|
|
228
238
|
|
|
229
239
|
/**
|
|
230
|
-
* Walk raw per-file results oldest first
|
|
231
|
-
*
|
|
232
|
-
*
|
|
240
|
+
* Walk raw per-file results oldest first, dedup messages globally by
|
|
241
|
+
* (msg.id, requestId), and merge multiple files belonging to the same logical
|
|
242
|
+
* session (main + subagents/*) into one TranscriptResult per session.
|
|
233
243
|
*
|
|
234
|
-
* Important: do NOT mutate the cached `raws` objects —
|
|
244
|
+
* Important: do NOT mutate the cached `raws` objects — they're shared across
|
|
245
|
+
* calls.
|
|
235
246
|
*/
|
|
236
247
|
function dedupAndSum(raws: TranscriptResult[]): TranscriptResult[] {
|
|
237
248
|
const sorted = [...raws].sort((a, b) => a.mtimeMs - b.mtimeMs);
|
|
238
249
|
const seen = new Set<string>();
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
250
|
+
const merged = new Map<string, TranscriptResult>();
|
|
251
|
+
|
|
252
|
+
for (const r of sorted) {
|
|
253
|
+
const key = `${r.project}\x00${r.sessionId}`;
|
|
254
|
+
let t = merged.get(key);
|
|
255
|
+
if (!t) {
|
|
256
|
+
t = {
|
|
257
|
+
filePath: r.filePath,
|
|
258
|
+
mtimeMs: r.mtimeMs,
|
|
259
|
+
sessionId: r.sessionId,
|
|
260
|
+
project: r.project,
|
|
261
|
+
projectPath: r.projectPath,
|
|
262
|
+
startTime: 0,
|
|
263
|
+
endTime: 0,
|
|
264
|
+
turnCount: 0,
|
|
265
|
+
models: [],
|
|
266
|
+
inputTokens: 0,
|
|
267
|
+
cacheReadTokens: 0,
|
|
268
|
+
cacheCreationTokens: 0,
|
|
269
|
+
outputTokens: 0,
|
|
270
|
+
byModel: {},
|
|
271
|
+
usageRecords: [],
|
|
272
|
+
invocations: [],
|
|
273
|
+
toolCalls: {},
|
|
274
|
+
toolErrors: 0,
|
|
275
|
+
sidechainTurns: 0,
|
|
276
|
+
};
|
|
277
|
+
merged.set(key, t);
|
|
278
|
+
} else {
|
|
279
|
+
// Prefer the main session file's path/mtime as the canonical reference;
|
|
280
|
+
// a subagent file landed first only if no main yet, which is rare.
|
|
281
|
+
const incomingIsMain = !r.filePath.includes("/subagents/");
|
|
282
|
+
if (incomingIsMain) {
|
|
283
|
+
t.filePath = r.filePath;
|
|
284
|
+
t.mtimeMs = Math.max(t.mtimeMs, r.mtimeMs);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Accumulate ancillary fields
|
|
289
|
+
for (const inv of r.invocations) t.invocations.push(inv);
|
|
290
|
+
for (const [name, n] of Object.entries(r.toolCalls)) {
|
|
291
|
+
t.toolCalls[name] = (t.toolCalls[name] ?? 0) + n;
|
|
292
|
+
}
|
|
293
|
+
t.toolErrors += r.toolErrors;
|
|
294
|
+
|
|
295
|
+
const modelSet = new Set<string>(t.models);
|
|
254
296
|
for (const u of r.usageRecords) {
|
|
255
297
|
if (u.dedupKey) {
|
|
256
298
|
if (seen.has(u.dedupKey)) continue;
|
|
@@ -282,11 +324,47 @@ function dedupAndSum(raws: TranscriptResult[]): TranscriptResult[] {
|
|
|
282
324
|
}
|
|
283
325
|
}
|
|
284
326
|
t.models = [...modelSet];
|
|
285
|
-
|
|
286
|
-
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
return [...merged.values()];
|
|
287
330
|
}
|
|
288
331
|
|
|
289
|
-
|
|
332
|
+
async function collectJsonlFiles(
|
|
333
|
+
dir: string,
|
|
334
|
+
cutoff: number,
|
|
335
|
+
out: { filePath: string; mtimeMs: number }[],
|
|
336
|
+
depth: number = 0
|
|
337
|
+
): Promise<void> {
|
|
338
|
+
if (depth > 3) return; // <project>/<session>/subagents/<file> is the deepest expected shape
|
|
339
|
+
let entries;
|
|
340
|
+
try {
|
|
341
|
+
entries = await readdir(dir, { withFileTypes: true });
|
|
342
|
+
} catch {
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
for (const e of entries) {
|
|
346
|
+
const fp = join(dir, e.name);
|
|
347
|
+
if (e.isDirectory()) {
|
|
348
|
+
await collectJsonlFiles(fp, cutoff, out, depth + 1);
|
|
349
|
+
continue;
|
|
350
|
+
}
|
|
351
|
+
if (!e.isFile() || !e.name.endsWith(".jsonl")) continue;
|
|
352
|
+
try {
|
|
353
|
+
const st = await stat(fp);
|
|
354
|
+
if (st.mtimeMs >= cutoff) out.push({ filePath: fp, mtimeMs: st.mtimeMs });
|
|
355
|
+
} catch {
|
|
356
|
+
// skip
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Scan all transcripts modified in the last N days. Scans both main session
|
|
363
|
+
* files (<project>/<session>.jsonl) and subagent files
|
|
364
|
+
* (<project>/<session>/subagents/agent-*.jsonl), then attributes subagent
|
|
365
|
+
* tokens to their parent session via shared sessionId. Deduped globally by
|
|
366
|
+
* (msg.id, requestId).
|
|
367
|
+
*/
|
|
290
368
|
export async function getAllTranscripts(daysBack: number = 30): Promise<TranscriptResult[]> {
|
|
291
369
|
const cutoff = Date.now() - daysBack * 24 * 60 * 60 * 1000;
|
|
292
370
|
let projDirs: import("node:fs").Dirent[];
|
|
@@ -299,23 +377,7 @@ export async function getAllTranscripts(daysBack: number = 30): Promise<Transcri
|
|
|
299
377
|
await Promise.all(
|
|
300
378
|
projDirs.map(async (d) => {
|
|
301
379
|
if (!d.isDirectory()) return;
|
|
302
|
-
|
|
303
|
-
let entries;
|
|
304
|
-
try {
|
|
305
|
-
entries = await readdir(dir, { withFileTypes: true });
|
|
306
|
-
} catch {
|
|
307
|
-
return;
|
|
308
|
-
}
|
|
309
|
-
for (const e of entries) {
|
|
310
|
-
if (!e.isFile() || !e.name.endsWith(".jsonl")) continue;
|
|
311
|
-
const fp = join(dir, e.name);
|
|
312
|
-
try {
|
|
313
|
-
const st = await stat(fp);
|
|
314
|
-
if (st.mtimeMs >= cutoff) candidates.push({ filePath: fp, mtimeMs: st.mtimeMs });
|
|
315
|
-
} catch {
|
|
316
|
-
// skip
|
|
317
|
-
}
|
|
318
|
-
}
|
|
380
|
+
await collectJsonlFiles(join(PROJECTS_DIR, String(d.name)), cutoff, candidates);
|
|
319
381
|
})
|
|
320
382
|
);
|
|
321
383
|
const raws = await pMapLimit(candidates, 16, (c) => getFileResult(c.filePath, c.mtimeMs));
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mbeato/contextscope",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"description": "Local dashboard auditing Claude Code's per-turn token context (skills, agents, commands, CLAUDE.md, MEMORY.md, hooks, MCP) with toggle-based disable and session analytics.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|