claude-inspect 0.5.0 → 0.5.1

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.de.md CHANGED
@@ -65,6 +65,7 @@ Aus den Quellen: `git clone …`, `npm install`, `npm run build`, `npm start`
65
65
  MCP-Server …) als gestapelter Verlauf je Programmname, dazu die Prozesse mit der meisten CPU-Zeit
66
66
  - *Cost*: geschätzte Kosten (API-Listenpreis) der Session samt aller Subagenten und per
67
67
  `claude` gestarteten Sitzungen, je Agent und je Modell mit Tokens (Input, Output, Cache)
68
+ und als Zeitleiste mit den teuersten Momenten
68
69
  - Beim Hochscrollen blendet sich eine kompakte Kopfzeile mit Sprung nach oben/unten ein
69
70
  - **CPU**: alle 3 s aus der Prozesstabelle gemessen (100 % = ein Kern), Verlaufslinie auf der
70
71
  Dashboard-Karte. Pro Session eine CSV-Datei im App-Datenverzeichnis (macOS
package/README.md CHANGED
@@ -50,7 +50,7 @@ becomes hard to tell *who is doing what*. claude-inspect answers:
50
50
  | **Agent tree & call graph** | Main agent → subagents (nested) → `claude` sessions started via Bash, including fork/resume relations. Finished agents are greyed out. |
51
51
  | **Timeline** | One lane per agent, tool calls as bars, spawn links between lanes, idle gaps > 10 min collapsed, zoom. Click a bar to jump to the call. |
52
52
  | **Spawn linking** | Links sessions started by `claude -p …` to the calling tool use via the process tree (exact), `--session-id`, prompt text, `--agent`, time window and working directory – with a confidence label. |
53
- | **Cost per agent tree** | Cost tab per session: API list-price estimate for the main agent plus all its subagents and spawned `claude` sessions, per agent and per model with input, output and cache tokens. |
53
+ | **Cost per agent tree** | Cost tab per session: API list-price estimate for the main agent plus all its subagents and spawned `claude` sessions, per agent and per model with input, output and cache tokens, plus a cost timeline with the most expensive moments. |
54
54
  | **Statistics** | API list-price estimate, tokens per model/day/project, cache share, activity heatmap, tool statistics with error rate and p95 duration. |
55
55
  | **CPU usage** | CPU of each Claude Code process plus all its subprocesses (shells, builds, test runners, MCP servers …), measured every 3 s from the process table. Sparkline on the dashboard card, a *CPU* tab per session with a stacked chart by process name and the processes that used the most CPU time. Recorded as one CSV per session in the app data directory (`~/Library/Application Support/claude-inspect/cpu` on macOS, `~/.local/share/claude-inspect/cpu` on Linux, `%LOCALAPPDATA%\claude-inspect\cpu` on Windows), so it stays available after the session ended; files older than 30 days are deleted. Only measured while claude-inspect runs; processes that live less than 3 s are missed. `--no-cpu-log` turns recording off. |
56
56
  | **Tool explorer & files** | Every tool call across all sessions, filterable; which files agents read, edited or wrote. |
@@ -42,27 +42,7 @@ export class HistoryIndex {
42
42
  seen.add(p);
43
43
  continue;
44
44
  }
45
- const dir = subagentDir(f.path);
46
- let names = [];
47
- try {
48
- names = await readdir(dir);
49
- }
50
- catch {
51
- /* keine Subagenten */
52
- }
53
- for (const n of names) {
54
- const m = /^agent-(.+)\.jsonl$/.exec(n);
55
- if (!m)
56
- continue;
57
- const path = join(dir, n);
58
- seen.add(path);
59
- try {
60
- await this.scan(path, (await stat(path)).size, f.sessionId, m[1], project);
61
- }
62
- catch {
63
- /* gelöscht */
64
- }
65
- }
45
+ await this.scanSubagents(f, project, seen);
66
46
  }
67
47
  // Von Claude Code aufgeräumte Dateien vergessen
68
48
  for (const p of [...this.files.keys()])
@@ -71,7 +51,52 @@ export class HistoryIndex {
71
51
  this.totalFiles = this.files.size;
72
52
  this.ready = true;
73
53
  }
54
+ async scanSubagents(f, project, seen) {
55
+ const dir = subagentDir(f.path);
56
+ let names = [];
57
+ try {
58
+ names = await readdir(dir);
59
+ }
60
+ catch {
61
+ /* keine Subagenten */
62
+ }
63
+ for (const n of names) {
64
+ const m = /^agent-(.+)\.jsonl$/.exec(n);
65
+ if (!m)
66
+ continue;
67
+ const path = join(dir, n);
68
+ seen?.add(path);
69
+ try {
70
+ await this.scan(path, (await stat(path)).size, f.sessionId, m[1], project);
71
+ }
72
+ catch {
73
+ /* gelöscht */
74
+ }
75
+ }
76
+ }
77
+ /**
78
+ * Eine Session samt Subagenten sofort auf Stand bringen, ohne auf den ersten
79
+ * vollständigen Scan zu warten (für die Kostenansicht direkt nach dem Start).
80
+ */
81
+ async ensureSession(f, project) {
82
+ try {
83
+ await this.scan(f.path, (await stat(f.path)).size, f.sessionId, undefined, project);
84
+ }
85
+ catch {
86
+ return; // gelöscht
87
+ }
88
+ await this.scanSubagents(f, project);
89
+ }
90
+ /** Läuft parallel zum Hintergrund-Scan: pro Datei nur ein Lesevorgang gleichzeitig. */
91
+ scanning = new Map();
74
92
  async scan(path, size, sessionId, agentId, project) {
93
+ for (let running = this.scanning.get(path); running; running = this.scanning.get(path))
94
+ await running;
95
+ const p = this.scanUnlocked(path, size, sessionId, agentId, project).finally(() => this.scanning.delete(path));
96
+ this.scanning.set(path, p);
97
+ return p;
98
+ }
99
+ async scanUnlocked(path, size, sessionId, agentId, project) {
75
100
  let agg = this.files.get(path);
76
101
  if (agg && agg.size === size)
77
102
  return;
@@ -509,7 +509,12 @@ export class Inspector extends EventEmitter {
509
509
  const flow = await this.flow(rootSessionId);
510
510
  if (!flow)
511
511
  return undefined;
512
- await this.historyLoaded;
512
+ // nur die Dateien dieses Baums einlesen, statt auf den ersten vollständigen History-Scan zu warten
513
+ for (const sid of new Set(flow.lanes.map((l) => l.sessionId))) {
514
+ const f = this.index.get(sid);
515
+ if (f)
516
+ await this.history.ensureSession(f, await this.projectOf(f));
517
+ }
513
518
  const seen = new Set();
514
519
  const total = emptyTotals();
515
520
  const byModel = new Map();
@@ -556,7 +561,6 @@ export class Inspector extends EventEmitter {
556
561
  const rootFile = this.index.get(rootSessionId);
557
562
  return {
558
563
  rootSessionId,
559
- ready: this.historyReady,
560
564
  rows,
561
565
  total,
562
566
  costUSD,
@@ -609,7 +613,7 @@ function costTimeline(points) {
609
613
  b.byModel[p.model] = (b.byModel[p.model] ?? 0) + p.cost;
610
614
  let l = b.lanes.get(p.lane.id);
611
615
  if (!l)
612
- b.lanes.set(p.lane.id, (l = { label: p.lane.label, costUSD: 0 }));
616
+ b.lanes.set(p.lane.id, (l = { label: p.lane.label, sublabel: p.lane.sublabel, costUSD: 0 }));
613
617
  l.costUSD += p.cost;
614
618
  }
615
619
  return {