Skip to content

Commit 6d37bbc

Browse files
fix: stop plan regex from bleeding across TUI lines (#1636)
* fix: stop plan regex from bleeding across TUI lines * fix: keep provider plans on rendered lines * docs: note provider plan parsing fix --------- Co-authored-by: Peter Steinberger <steipete@gmail.com>
1 parent e3ae6f2 commit 6d37bbc

4 files changed

Lines changed: 81 additions & 4 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
### Fixed
2323
- Memory: release idle OpenAI WebViews under system pressure without blocking the main thread. Thanks @ProspectOre!
2424
- Memory: trim rebuildable menu and OpenAI debug caches under system pressure. Thanks @ProspectOre!
25+
- Provider plans: keep Claude and Kiro plan matching on one rendered line to avoid bogus labels from adjacent usage hints. Thanks @elijahfriedman!
2526
- Codex web: keep cookie-import deadlines responsive when browser cookie work blocks the shared worker pool.
2627
- Codex pace: extrapolate historically exhausted weeks for run-out forecasts and avoid contradictory reset headlines. Thanks @Yuxin-Qiao!
2728
- Localization: correct the German in-progress refresh label. Thanks @ChrisLauinger77!

Sources/CodexBarCore/Providers/Claude/ClaudeStatusProbe.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -697,7 +697,10 @@ public struct ClaudeStatusProbe: Sendable {
697697
}
698698
// Capture any "Claude <...>" phrase (e.g., Max/Pro/Ultra/Team) to avoid future plan-name churn.
699699
// Strip any leading ANSI that may have survived (rare) before matching.
700-
let planPattern = #"(?i)(claude\s+[a-z0-9][a-z0-9\s._-]{0,24})"#
700+
// Use horizontal whitespace ([ \t]) rather than \s so the match stays on a single rendered line:
701+
// \s spans newlines, which let "…use Claude" bridge into the /usage panel's "d → today" hint and
702+
// produced a bogus "Dtoday" plan label after ANSI stripping glued the lines together.
703+
let planPattern = #"(?i)(claude[ \t]+[a-z0-9][a-z0-9 \t._-]{0,24})"#
701704
var candidates: [String] = []
702705
if let regex = try? NSRegularExpression(pattern: planPattern, options: []) {
703706
let nsrange = NSRange(text.startIndex..<text.endIndex, in: text)

Sources/CodexBarCore/Providers/Kiro/KiroStatusProbe.swift

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -686,14 +686,15 @@ public struct KiroStatusProbe: Sendable {
686686
var matchedNewFormat = false
687687

688688
// Parse plan name from "| KIRO FREE" or similar (legacy format)
689-
if let planMatch = text.range(of: #"\|\s*(KIRO\s+\w+)"#, options: .regularExpression) {
689+
// Horizontal whitespace only ([ \t]) so the match cannot bridge a newline into the next line.
690+
if let planMatch = text.range(of: #"\|[ \t]*(KIRO[ \t]+\w+)"#, options: .regularExpression) {
690691
let raw = String(text[planMatch]).replacingOccurrences(of: "|", with: "")
691692
planName = raw.trimmingCharacters(in: .whitespaces)
692693
}
693694

694695
// Parse plan name from "Estimated Usage | resets on 2026-06-01 | KIRO FREE" (kiro-cli 2.x)
695696
if let estimatedMatch = text.range(
696-
of: #"Estimated Usage\s*\|[^\n|]*\|\s*([A-Z][A-Z0-9 ]+)"#,
697+
of: #"Estimated Usage[ \t]*\|[^\n|]*\|[ \t]*([A-Z][A-Z0-9 ]+)"#,
697698
options: .regularExpression)
698699
{
699700
let line = String(text[estimatedMatch])
@@ -705,7 +706,7 @@ public struct KiroStatusProbe: Sendable {
705706
}
706707

707708
// Parse plan name from "Plan: Q Developer Pro" (new format, kiro-cli 1.24+)
708-
if let newPlanMatch = text.range(of: #"Plan:\s*(.+)"#, options: .regularExpression) {
709+
if let newPlanMatch = text.range(of: #"Plan:[ \t]*(.+)"#, options: .regularExpression) {
709710
let line = String(text[newPlanMatch])
710711
let planLine = line.replacingOccurrences(of: "Plan:", with: "").trimmingCharacters(in: .whitespaces)
711712
if let firstLine = planLine.split(separator: "\n").first {
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import Testing
2+
@testable import CodexBarCore
3+
4+
struct ProviderPlanLineParsingTests {
5+
@Test
6+
func `Claude plan matching does not bridge usage lines`() {
7+
let usageText = """
8+
Skills, subagents, plugins, and MCP servers
9+
Noattributiondatayet·accumulatesasyouuseClaude
10+
11+
dtoday·wtoweek
12+
13+
Usagecredits
14+
Usagecreditsareoff·/usage-creditstoturnthemon
15+
"""
16+
17+
let identity = ClaudeStatusProbe.parseIdentity(usageText: usageText, statusText: nil)
18+
19+
#expect(identity.loginMethod == nil)
20+
}
21+
22+
@Test
23+
func `Claude plan matching keeps single line phrases`() {
24+
let identity = ClaudeStatusProbe.parseIdentity(
25+
usageText: nil,
26+
statusText: "Sonnet 4.6 · Claude Max · you@example.com")
27+
28+
#expect(identity.loginMethod == "Max")
29+
}
30+
31+
@Test
32+
func `Kiro legacy plan matching does not bridge lines`() throws {
33+
let output = """
34+
|
35+
KIRO FREE
36+
████████████████████████████████████████████████████ 25%
37+
(12.50 of 50 covered in plan), resets on 01/15
38+
"""
39+
40+
let snapshot = try KiroStatusProbe().parse(output: output)
41+
42+
#expect(snapshot.planName == "Kiro")
43+
}
44+
45+
@Test
46+
func `Kiro estimated usage plan matching does not bridge lines`() throws {
47+
let output = """
48+
Estimated Usage | resets on 2026-06-01 |
49+
KIRO FREE
50+
████████████████████████████████████████████████████ 25%
51+
(12.50 of 50 covered in plan), resets on 01/15
52+
"""
53+
54+
let snapshot = try KiroStatusProbe().parse(output: output)
55+
56+
#expect(snapshot.planName == "Kiro")
57+
}
58+
59+
@Test
60+
func `Kiro labeled plan matching does not bridge lines`() throws {
61+
let output = """
62+
Plan:
63+
Q Developer Pro
64+
████████████████████████████████████████████████████ 25%
65+
(12.50 of 50 covered in plan), resets on 01/15
66+
"""
67+
68+
let snapshot = try KiroStatusProbe().parse(output: output)
69+
70+
#expect(snapshot.planName == "Kiro")
71+
}
72+
}

0 commit comments

Comments
 (0)