Skip to content

Commit 32a12cf

Browse files
author
Peter Steinberger
committed
Merge remote-tracking branch 'origin/main' into prz-2628
2 parents 0eb1c06 + 47e1d04 commit 32a12cf

51 files changed

Lines changed: 3869 additions & 211 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,16 @@
44

55
### Added
66
- Kimi: enrich Code API and CLI usage with the monthly membership pool from a signed-in Kimi Desktop session, using WAL-safe read-only cookie access (#2351). Thanks @Leehow!
7+
- Sessions: discover live pi and OMP sessions through one Pi-family scanner, with dialect-aware metadata, PID-only startup rows, and mixed-version CLI/remote support (#2529). Thanks @wdmitchelluk!
78
- Kimi/GLM: distinguish Kimi Code from the regional Open Platform, bind China and international keys to their issuing hosts, and show GLM Coding Plan's 5-hour window as primary with MCP separate (#2351). Thanks @Leehow!
9+
- Provider plugins: declarative detail rows/charts plus bundled JavaScript conversions for OpenAI, z.ai, OpenRouter, Poe, and ClawRouter behind `CODEXBAR_JS_PROVIDERS=1`.
810

911
### Changed
1012
- Menu: move each usage window's used percentage and reset time into its title row, with all pace detail on one line (#2182). Thanks @jack24254029!
1113

1214
### Fixed
1315
- Usage & Spend: keep validated Codex totals visible while the local scanner catches up, with refresh indicators in the dashboard and menu cost rows (#2397). Thanks @hhh2210!
16+
- ZoomMate: preserve browser cookie scope so parent-domain sessions reach both API hosts without leaking host-only cookies (fixes #2507). Thanks @weddle!
1417
- Sync: propagate provider configuration edits made by the CLI or directly in `config.json` to the iCloud fleet without echoing remotely applied writes.
1518

1619
## 0.47.0 — 2026-08-03

Package.resolved

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Package.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ let useLocalSweetCookieKit =
88
let sweetCookieKitDependency: Package.Dependency =
99
useLocalSweetCookieKit && FileManager.default.fileExists(atPath: sweetCookieKitPath)
1010
? .package(path: sweetCookieKitPath)
11-
: .package(url: "https://github.com/steipete/SweetCookieKit", from: "0.5.1")
11+
: .package(url: "https://github.com/steipete/SweetCookieKit", from: "0.5.2")
1212

1313
let sqlite3LibDir = ProcessInfo.processInfo.environment["CODEXBAR_SQLITE3_LIB_DIR"]?
1414
.trimmingCharacters(in: .whitespacesAndNewlines)

Sources/CodexBar/MenuCardHeightFingerprint.swift

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import CodexBarCore
12
import Foundation
23

34
extension UsageMenuCardView.Model {
@@ -22,6 +23,7 @@ extension UsageMenuCardView.Model {
2223
"codexResetCredits=\(self.codexResetCredits?.heightFingerprint ?? "")",
2324
"metrics=\(MenuCardHeightFingerprint.join(self.metrics.map(\.heightFingerprint)))",
2425
"notes=\(notesFingerprint)",
26+
"providerDetails=\(self.providerDetails.heightFingerprint)",
2527
"dashboard=\(self.inlineUsageDashboard?.heightFingerprint ?? "")",
2628
"providerCost=\(self.providerCost?.heightFingerprint ?? "")",
2729
"tokenUsage=\(self.tokenUsage?.heightFingerprint ?? "")",
@@ -34,6 +36,31 @@ extension UsageMenuCardView.Model {
3436
}
3537
}
3638

39+
extension [ProviderDetailSection] {
40+
fileprivate var heightFingerprint: String {
41+
MenuCardHeightFingerprint.join(self.map { section in
42+
MenuCardHeightFingerprint.join([
43+
MenuCardHeightFingerprint.field("title", section.title),
44+
MenuCardHeightFingerprint.join(section.rows.map { row in
45+
MenuCardHeightFingerprint.join([
46+
MenuCardHeightFingerprint.field("label", row.label),
47+
MenuCardHeightFingerprint.field("value", row.value),
48+
MenuCardHeightFingerprint.field("secondary", row.secondaryValue),
49+
])
50+
}),
51+
section.chart.map { chart in
52+
MenuCardHeightFingerprint.join([
53+
chart.kind.rawValue,
54+
MenuCardHeightFingerprint.field("title", chart.title),
55+
MenuCardHeightFingerprint.field("unit", chart.unit),
56+
"points=\(chart.points.count)",
57+
])
58+
} ?? "chart=nil",
59+
])
60+
})
61+
}
62+
}
63+
3764
private enum MenuCardHeightFingerprint {
3865
private static let hashSalt = UUID()
3966

Sources/CodexBar/MenuCardView+ModelHelpers.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,7 @@ extension UsageMenuCardView.Model {
279279
self.subtitleStyle == .error &&
280280
self.metrics.isEmpty &&
281281
self.usageNotes.isEmpty &&
282+
self.providerDetails.isEmpty &&
282283
self.openAIAPIUsage == nil &&
283284
self.inlineUsageDashboard == nil &&
284285
self.creditsRemaining == nil &&
@@ -290,6 +291,7 @@ extension UsageMenuCardView.Model {
290291
var hasUsageContent: Bool {
291292
!self.metrics.isEmpty ||
292293
!self.usageNotes.isEmpty ||
294+
!self.providerDetails.isEmpty ||
293295
self.openAIAPIUsage != nil ||
294296
self.inlineUsageDashboard != nil ||
295297
self.codexResetCredits != nil ||
@@ -301,6 +303,7 @@ extension UsageMenuCardView.Model {
301303
self.inlineUsageDashboard != nil &&
302304
self.metrics.isEmpty &&
303305
self.usageNotes.isEmpty &&
306+
self.providerDetails.isEmpty &&
304307
self.openAIAPIUsage == nil &&
305308
self.codexResetCredits == nil &&
306309
self.placeholder == nil
@@ -337,6 +340,7 @@ extension UsageMenuCardView.Model {
337340
guard self.provider == candidate.provider,
338341
!includeMetrics || self.metrics.count == candidate.metrics.count,
339342
self.usageNotes == candidate.usageNotes,
343+
self.providerDetails == candidate.providerDetails,
340344
(self.openAIAPIUsage == nil) == (candidate.openAIAPIUsage == nil),
341345
Self.hasCompatibleCreditsLayout(
342346
currentText: self.creditsText,

Sources/CodexBar/MenuCardView.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,7 @@ struct UsageMenuCardView: View {
165165
let metrics: [Metric]
166166
let usageNotes: [String]
167167
var subscriptionNotes: [String] = []
168+
var providerDetails: [ProviderDetailSection] = []
168169
let openAIAPIUsage: OpenAIAPIUsageSnapshot?
169170
let inlineUsageDashboard: InlineUsageDashboardModel?
170171
let creditsText: String?
@@ -217,6 +218,11 @@ struct UsageMenuCardView: View {
217218
.foregroundStyle(MenuHighlightStyle.secondary(self.isHighlighted))
218219
.font(.subheadline)
219220
}
221+
if !liveModel.providerDetails.isEmpty {
222+
ProviderDetailSectionsContent(
223+
sections: liveModel.providerDetails,
224+
chartColor: liveModel.progressColor)
225+
}
220226
} else {
221227
let hasUsage = liveModel.hasUsageContent
222228
let hasCredits = liveModel.creditsText != nil
@@ -684,6 +690,11 @@ private struct UsageMenuCardUsageContentView: View {
684690
.foregroundStyle(MenuHighlightStyle.secondary(self.isHighlighted))
685691
.font(.subheadline)
686692
}
693+
if !self.model.providerDetails.isEmpty {
694+
ProviderDetailSectionsContent(
695+
sections: self.model.providerDetails,
696+
chartColor: self.model.progressColor)
697+
}
687698
if self.showBottomDivider {
688699
Divider()
689700
}
@@ -971,6 +982,7 @@ extension UsageMenuCardView.Model {
971982
metrics: metrics,
972983
usageNotes: usageNotes,
973984
subscriptionNotes: Self.subscriptionMetadataNotes(snapshot: input.snapshot, provider: input.provider),
985+
providerDetails: input.snapshot?.details ?? [],
974986
openAIAPIUsage: openAIAPIUsage,
975987
inlineUsageDashboard: inlineUsageDashboard,
976988
creditsText: creditsText,

Sources/CodexBar/MenuDescriptor.swift

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -193,9 +193,14 @@ struct MenuDescriptor {
193193
now: Date) -> String
194194
{
195195
let state = session.state == .active ? "" : ""
196-
let providerGlyph = session.provider == .codex ? "" : ""
196+
let providerGlyph = switch session.provider {
197+
case .codex: ""
198+
case .claude: ""
199+
case .pi: "π"
200+
}
197201
let label = labelStyle.label(for: session)
198-
return "\(state) \(providerGlyph) \(label)\(session.provider.rawValue) · " +
202+
let providerTag = session.dialect?.rawValue ?? session.provider.rawValue
203+
return "\(state) \(providerGlyph) \(label)\(providerTag) · " +
199204
"\(session.source.rawValue) · \(self.agentSessionAge(session, now: now))"
200205
}
201206

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
import CodexBarCore
2+
import SwiftUI
3+
4+
struct ProviderDetailSectionsContent: View {
5+
let sections: [ProviderDetailSection]
6+
let chartColor: Color
7+
@Environment(\.menuItemHighlighted) private var isHighlighted
8+
9+
var body: some View {
10+
VStack(alignment: .leading, spacing: 12) {
11+
ForEach(Array(self.sections.enumerated()), id: \.offset) { index, section in
12+
if index > 0 {
13+
Divider()
14+
}
15+
self.section(section)
16+
}
17+
}
18+
.frame(maxWidth: .infinity, alignment: .leading)
19+
}
20+
21+
private func section(_ section: ProviderDetailSection) -> some View {
22+
VStack(alignment: .leading, spacing: 6) {
23+
if let title = section.title {
24+
Text(title)
25+
.font(.caption.weight(.semibold))
26+
.foregroundStyle(MenuHighlightStyle.secondary(self.isHighlighted))
27+
.textCase(.uppercase)
28+
.lineLimit(1)
29+
}
30+
ForEach(Array(section.rows.enumerated()), id: \.offset) { _, row in
31+
HStack(alignment: .firstTextBaseline, spacing: 8) {
32+
Text(row.label)
33+
.foregroundStyle(MenuHighlightStyle.secondary(self.isHighlighted))
34+
Spacer(minLength: 8)
35+
VStack(alignment: .trailing, spacing: 1) {
36+
Text(row.value)
37+
.foregroundStyle(MenuHighlightStyle.primary(self.isHighlighted))
38+
.fontWeight(.medium)
39+
if let secondaryValue = row.secondaryValue {
40+
Text(secondaryValue)
41+
.font(.caption2)
42+
.foregroundStyle(MenuHighlightStyle.secondary(self.isHighlighted))
43+
}
44+
}
45+
}
46+
.font(.caption)
47+
.lineLimit(1)
48+
}
49+
if let chart = section.chart {
50+
ProviderDetailChartContent(chart: chart, color: self.chartColor)
51+
}
52+
}
53+
}
54+
}
55+
56+
private struct ProviderDetailChartContent: View {
57+
let chart: ProviderDetailSection.Chart
58+
let color: Color
59+
@Environment(\.menuItemHighlighted) private var isHighlighted
60+
61+
var body: some View {
62+
VStack(alignment: .leading, spacing: 3) {
63+
if self.chart.title != nil || self.chart.unit != nil {
64+
HStack(spacing: 6) {
65+
if let title = self.chart.title {
66+
Text(title)
67+
.font(.caption2.weight(.semibold))
68+
}
69+
Spacer(minLength: 0)
70+
if let unit = self.chart.unit {
71+
Text(unit)
72+
.font(.caption2)
73+
.monospacedDigit()
74+
}
75+
}
76+
.foregroundStyle(MenuHighlightStyle.secondary(self.isHighlighted))
77+
.lineLimit(1)
78+
}
79+
Group {
80+
switch self.chart.kind {
81+
case .bars:
82+
self.bars
83+
case .line:
84+
self.line
85+
}
86+
}
87+
.frame(height: 58)
88+
.accessibilityElement(children: .combine)
89+
.accessibilityLabel(self.accessibilityLabel)
90+
}
91+
}
92+
93+
private var bars: some View {
94+
GeometryReader { geometry in
95+
let scale = UsageChartScale(values: self.chart.points.map(\.value))
96+
HStack(alignment: .bottom, spacing: 2) {
97+
ForEach(Array(self.chart.points.enumerated()), id: \.offset) { _, point in
98+
RoundedRectangle(cornerRadius: 1.5, style: .continuous)
99+
.fill(self.fillColor(value: point.value, scale: scale))
100+
.frame(maxWidth: .infinity)
101+
.frame(height: Self.height(value: point.value, scale: scale, available: geometry.size.height))
102+
}
103+
}
104+
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .bottom)
105+
.overlay(alignment: .bottomLeading) {
106+
Rectangle()
107+
.fill(MenuHighlightStyle.secondary(self.isHighlighted).opacity(0.22))
108+
.frame(height: 1)
109+
}
110+
}
111+
}
112+
113+
private var line: some View {
114+
GeometryReader { _ in
115+
Canvas { context, size in
116+
let scale = UsageChartScale(values: self.chart.points.map(\.value))
117+
let points = self.chart.points.enumerated().map { index, point in
118+
let x = self.chart.points.count == 1
119+
? size.width / 2
120+
: size.width * CGFloat(index) / CGFloat(self.chart.points.count - 1)
121+
let y = size.height * (1 - CGFloat(scale.fraction(for: point.value)))
122+
return CGPoint(x: x, y: y)
123+
}
124+
guard let first = points.first else { return }
125+
var path = Path()
126+
path.move(to: first)
127+
for point in points.dropFirst() {
128+
path.addLine(to: point)
129+
}
130+
context.stroke(
131+
path,
132+
with: .color(self.isHighlighted ? .white.opacity(0.82) : self.color),
133+
style: StrokeStyle(lineWidth: 2, lineCap: .round, lineJoin: .round))
134+
for point in points {
135+
context.fill(
136+
Path(ellipseIn: CGRect(x: point.x - 1.5, y: point.y - 1.5, width: 3, height: 3)),
137+
with: .color(self.isHighlighted ? .white : self.color))
138+
}
139+
}
140+
.overlay(alignment: .bottomLeading) {
141+
Rectangle()
142+
.fill(MenuHighlightStyle.secondary(self.isHighlighted).opacity(0.22))
143+
.frame(height: 1)
144+
}
145+
}
146+
}
147+
148+
private var accessibilityLabel: String {
149+
let title = self.chart.title.map { "\($0): " } ?? ""
150+
let unit = self.chart.unit.map { " \($0)" } ?? ""
151+
let points = self.chart.points.map { "\($0.label) \($0.value)\(unit)" }.joined(separator: ", ")
152+
return title + points
153+
}
154+
155+
private func fillColor(value: Double, scale: UsageChartScale) -> Color {
156+
let ratio = max(0.18, scale.fraction(for: value))
157+
if self.isHighlighted {
158+
return Color.white.opacity(0.55 + ratio * 0.35)
159+
}
160+
return self.color.opacity(0.42 + ratio * 0.58)
161+
}
162+
163+
private static func height(value: Double, scale: UsageChartScale, available: CGFloat) -> CGFloat {
164+
let ratio = scale.fraction(for: value)
165+
guard ratio > 0 else { return 1 }
166+
return max(3, CGFloat(ratio) * available)
167+
}
168+
}

Sources/CodexBarCLI/CLIEntry.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -186,13 +186,13 @@ enum CodexBarCLI {
186186
signature: costSignature),
187187
CommandDescriptor(
188188
name: "sessions",
189-
abstract: "List live Codex and Claude Code sessions",
189+
abstract: "List live Codex, Claude Code, pi, and OMP sessions",
190190
discussion: nil,
191191
signature: CommandSignature(),
192192
subcommands: [
193193
CommandDescriptor(
194194
name: "list",
195-
abstract: "List live Codex and Claude Code sessions",
195+
abstract: "List live Codex, Claude Code, pi, and OMP sessions",
196196
discussion: nil,
197197
signature: sessionsSignature),
198198
CommandDescriptor(

Sources/CodexBarCLI/CLIHelp.swift

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -117,17 +117,20 @@ extension CodexBarCLI {
117117
CodexBar \(version)
118118
119119
Usage:
120-
codexbar sessions [--json] [--pretty]
120+
codexbar sessions [--json|--json-v2] [--pretty]
121121
codexbar sessions focus <id>
122122
123123
Description:
124-
List live local Codex and Claude Code agent sessions.
124+
List live local Codex, Claude Code, pi, and OMP agent sessions.
125+
--json emits the legacy v1 array with only Codex and Claude providers.
126+
--json-v2 emits the complete current array, including Pi-family sessions.
125127
JSON uses stable AgentSession field names and ISO-8601 dates.
126128
Focus activates the owning terminal or desktop app on macOS.
127129
128130
Examples:
129131
codexbar sessions
130132
codexbar sessions --json
133+
codexbar sessions --json-v2
131134
codexbar sessions focus 019f3497-73bf-7df3-a173-4f67d968914a
132135
"""
133136
}
@@ -426,7 +429,7 @@ extension CodexBarCLI {
426429
[--json-output] [--log-level <trace|verbose|debug|info|warning|error|critical>] [-v|--verbose]
427430
[--provider \(ProviderHelp.list)] [--no-color] [--pretty] [--refresh]
428431
[--days <days>] [--group-by project]
429-
codexbar sessions [--json] [--pretty]
432+
codexbar sessions [--json|--json-v2] [--pretty]
430433
codexbar sessions focus <id>
431434
codexbar dashboard [--pretty] [--timeout <seconds>]
432435
codexbar serve [--host <host>] [--port <port>] [--refresh-interval <seconds>]

0 commit comments

Comments
 (0)