A desktop app that parses Claude Code .jsonl session logs and provides a rich UI for exploring conversations, tool usage, subagents, and token consumption. Features include project browser with auto-discovery of ~/.claude/projects, conversation timeline with inline subagent expansion, agents overview, toolbelt chart, and token usage dashboard. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
101 lines
2.6 KiB
Dart
101 lines
2.6 KiB
Dart
import 'content_block.dart';
|
|
import 'log_entry.dart';
|
|
import 'token_usage.dart';
|
|
|
|
class AgentInfo {
|
|
final String id;
|
|
final String name;
|
|
final String? subagentType;
|
|
final String? description;
|
|
final String? prompt;
|
|
final String? model;
|
|
final List<LogEntry> messages;
|
|
final List<ToolUseBlock> toolsUsed;
|
|
final TokenUsage aggregatedUsage;
|
|
|
|
AgentInfo({
|
|
required this.id,
|
|
required this.name,
|
|
this.subagentType,
|
|
this.description,
|
|
this.prompt,
|
|
this.model,
|
|
required this.messages,
|
|
required this.toolsUsed,
|
|
required this.aggregatedUsage,
|
|
});
|
|
|
|
Set<String> get uniqueToolNames => toolsUsed.map((t) => t.name).toSet();
|
|
|
|
Map<String, int> get toolUsageCounts {
|
|
final counts = <String, int>{};
|
|
for (final tool in toolsUsed) {
|
|
counts[tool.name] = (counts[tool.name] ?? 0) + 1;
|
|
}
|
|
return counts;
|
|
}
|
|
|
|
int get messageCount => messages.length;
|
|
int get userMessageCount =>
|
|
messages.where((m) => m.type == 'user').length;
|
|
int get assistantMessageCount =>
|
|
messages.where((m) => m.type == 'assistant').length;
|
|
}
|
|
|
|
class SessionLog {
|
|
final String? sessionId;
|
|
final String? cwd;
|
|
final String? version;
|
|
final String? gitBranch;
|
|
final List<LogEntry> allEntries;
|
|
final List<LogEntry> mainConversation;
|
|
final List<AgentInfo> agents;
|
|
final AgentInfo mainAgent;
|
|
final TokenUsage totalUsage;
|
|
final Map<String, List<ToolUseBlock>> toolsByName;
|
|
final DateTime? startTime;
|
|
final DateTime? endTime;
|
|
|
|
/// Lookup subagent by ID (matches both full id and partial)
|
|
late final Map<String, AgentInfo> agentById = {
|
|
for (final a in agents) a.id: a,
|
|
};
|
|
|
|
SessionLog({
|
|
this.sessionId,
|
|
this.cwd,
|
|
this.version,
|
|
this.gitBranch,
|
|
required this.allEntries,
|
|
required this.mainConversation,
|
|
required this.agents,
|
|
required this.mainAgent,
|
|
required this.totalUsage,
|
|
required this.toolsByName,
|
|
this.startTime,
|
|
this.endTime,
|
|
});
|
|
|
|
/// Find an agent that matches a tool_use id (Agent tool calls use
|
|
/// the tool_use block id, subagent files use a shortened agentId)
|
|
AgentInfo? findAgentForToolUse(String toolUseId) {
|
|
// Direct match
|
|
if (agentById.containsKey(toolUseId)) return agentById[toolUseId];
|
|
// Partial match: subagent file ids are shortened (e.g. "a014e30b71de602bb")
|
|
for (final agent in agents) {
|
|
if (agent.id == 'main') continue;
|
|
if (toolUseId.contains(agent.id) || agent.id.contains(toolUseId)) {
|
|
return agent;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
Duration? get duration {
|
|
if (startTime != null && endTime != null) {
|
|
return endTime!.difference(startTime!);
|
|
}
|
|
return null;
|
|
}
|
|
}
|