mirror of
https://github.com/AgentSeal/codeburn.git
synced 2026-07-19 14:04:26 +00:00
Some checks are pending
CI / semgrep (push) Waiting to run
* Fix Antigravity provider detection, Codex fork double-counting, and tab ordering Antigravity: - Handle ephemeral port (--https_server_port 0) via lsof fallback - Force reparse when cached turns are 0 (server may have been unavailable) - Persist precomputed costUSD in session cache for correct pricing - Map gemini-pro-agent to gemini-3.1-pro pricing - Add display names for gemini-pro-agent and gemini-3.5-flash-low Codex: - Detect forked sessions via forked_from_id in session_meta - Skip replayed parent events within 5s of fork creation time - Use parent session ID in dedup key so parent+fork don't double-count Menubar: - Sort provider tabs by cost descending instead of enum declaration order * Add tooling breakdowns to CLI dashboard and menubar Surface skills, subagents, tools, and MCP server usage in both the text dashboard and menubar app. Parse subagent types from Agent/Task tool calls, aggregate with skills into a merged "Skills & Agents" panel. Remove Top Sessions panel from CLI dashboard.
137 lines
4.7 KiB
Swift
137 lines
4.7 KiB
Swift
import SwiftUI
|
|
|
|
struct ToolingSection: View {
|
|
@Environment(AppStore.self) private var store
|
|
@State private var isExpanded: Bool = false
|
|
|
|
private var skillsAndAgents: [CostRowData] {
|
|
let current = store.payload.current
|
|
var merged: [String: (uses: Int, cost: Double)] = [:]
|
|
for s in current.skills {
|
|
let e = merged[s.name, default: (0, 0)]
|
|
merged[s.name] = (e.uses + s.turns, e.cost + s.cost)
|
|
}
|
|
for a in current.subagents {
|
|
let e = merged[a.name, default: (0, 0)]
|
|
merged[a.name] = (e.uses + a.calls, e.cost + a.cost)
|
|
}
|
|
return merged
|
|
.map { CostRowData(name: $0.key, uses: $0.value.uses, cost: $0.value.cost) }
|
|
.sorted { $0.cost > $1.cost }
|
|
}
|
|
|
|
var body: some View {
|
|
let current = store.payload.current
|
|
let combined = skillsAndAgents
|
|
let hasAny = !current.tools.isEmpty || !combined.isEmpty || !current.mcpServers.isEmpty
|
|
if hasAny {
|
|
CollapsibleSection(caption: "Tooling", isExpanded: $isExpanded) {
|
|
VStack(alignment: .leading, spacing: 12) {
|
|
if !current.tools.isEmpty {
|
|
ToolingSubsection(title: "Tools") {
|
|
let maxCalls = current.tools.map(\.calls).max() ?? 1
|
|
ForEach(current.tools, id: \.name) { t in
|
|
CallsRow(name: t.name, calls: t.calls, maxCalls: maxCalls)
|
|
}
|
|
}
|
|
}
|
|
if !combined.isEmpty {
|
|
ToolingSubsection(title: "Skills & Agents") {
|
|
let maxCost = max(combined.map(\.cost).max() ?? 0.01, 0.01)
|
|
ForEach(combined, id: \.name) { d in
|
|
CostRow(name: d.name, cost: d.cost, count: d.uses, countLabel: "uses", maxCost: maxCost)
|
|
}
|
|
}
|
|
}
|
|
if !current.mcpServers.isEmpty {
|
|
ToolingSubsection(title: "MCP Servers") {
|
|
let maxCalls = current.mcpServers.map(\.calls).max() ?? 1
|
|
ForEach(current.mcpServers, id: \.name) { m in
|
|
CallsRow(name: m.name, calls: m.calls, maxCalls: maxCalls)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private struct CostRowData {
|
|
let name: String
|
|
let uses: Int
|
|
let cost: Double
|
|
}
|
|
|
|
private struct ToolingSubsection<Content: View>: View {
|
|
let title: String
|
|
let content: Content
|
|
|
|
init(title: String, @ViewBuilder content: () -> Content) {
|
|
self.title = title
|
|
self.content = content()
|
|
}
|
|
|
|
var body: some View {
|
|
VStack(alignment: .leading, spacing: 5) {
|
|
Text(title)
|
|
.font(.system(size: 10.5, weight: .semibold))
|
|
.foregroundStyle(.tertiary)
|
|
.textCase(.uppercase)
|
|
.tracking(0.5)
|
|
content
|
|
}
|
|
}
|
|
}
|
|
|
|
private struct CallsRow: View {
|
|
let name: String
|
|
let calls: Int
|
|
let maxCalls: Int
|
|
|
|
var body: some View {
|
|
HStack(spacing: 8) {
|
|
FixedBar(fraction: Double(calls) / Double(max(maxCalls, 1)))
|
|
.frame(width: 40, height: 5)
|
|
Text(name)
|
|
.font(.system(size: 12, weight: .medium))
|
|
.lineLimit(1)
|
|
.frame(maxWidth: .infinity, alignment: .leading)
|
|
Text("\(calls)")
|
|
.font(.system(size: 11))
|
|
.monospacedDigit()
|
|
.foregroundStyle(.secondary)
|
|
.frame(minWidth: 36, alignment: .trailing)
|
|
}
|
|
.padding(.vertical, 1)
|
|
}
|
|
}
|
|
|
|
private struct CostRow: View {
|
|
let name: String
|
|
let cost: Double
|
|
let count: Int
|
|
let countLabel: String
|
|
let maxCost: Double
|
|
|
|
var body: some View {
|
|
HStack(spacing: 8) {
|
|
FixedBar(fraction: cost / max(maxCost, 0.01))
|
|
.frame(width: 40, height: 5)
|
|
Text(name)
|
|
.font(.system(size: 12, weight: .medium))
|
|
.lineLimit(1)
|
|
.frame(maxWidth: .infinity, alignment: .leading)
|
|
Text("\(count)")
|
|
.font(.system(size: 11))
|
|
.monospacedDigit()
|
|
.foregroundStyle(.secondary)
|
|
.frame(minWidth: 30, alignment: .trailing)
|
|
Text(cost.asCompactCurrency())
|
|
.font(.codeMono(size: 11, weight: .medium))
|
|
.tracking(-0.2)
|
|
.frame(minWidth: 46, alignment: .trailing)
|
|
}
|
|
.padding(.vertical, 1)
|
|
}
|
|
}
|