Skip to content

Commit 95adbae

Browse files
authored
Merge pull request #288 from kilhyeonjun/fix/kiro-cli-1.24-format
fix(kiro): support kiro-cli 1.24+ Q Developer format
2 parents 565ddca + 7559522 commit 95adbae

2 files changed

Lines changed: 87 additions & 2 deletions

File tree

Sources/CodexBarCore/Providers/Kiro/KiroStatusProbe.swift

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -359,14 +359,31 @@ public struct KiroStatusProbe: Sendable {
359359
// Track which key patterns matched to detect format changes
360360
var matchedPercent = false
361361
var matchedCredits = false
362+
var matchedNewFormat = false
362363

363-
// Parse plan name from "| KIRO FREE" or similar
364+
// Parse plan name from "| KIRO FREE" or similar (legacy format)
364365
var planName = "Kiro"
365366
if let planMatch = stripped.range(of: #"\|\s*(KIRO\s+\w+)"#, options: .regularExpression) {
366367
let raw = String(stripped[planMatch]).replacingOccurrences(of: "|", with: "")
367368
planName = raw.trimmingCharacters(in: .whitespaces)
368369
}
369370

371+
// Parse plan name from "Plan: Q Developer Pro" (new format, kiro-cli 1.24+)
372+
if let newPlanMatch = stripped.range(of: #"Plan:\s*(.+)"#, options: .regularExpression) {
373+
let line = String(stripped[newPlanMatch])
374+
// Extract just the plan name, stopping at newline
375+
let planLine = line.replacingOccurrences(of: "Plan:", with: "").trimmingCharacters(in: .whitespaces)
376+
if let firstLine = planLine.split(separator: "\n").first {
377+
planName = String(firstLine).trimmingCharacters(in: .whitespaces)
378+
matchedNewFormat = true
379+
}
380+
}
381+
382+
// Check if this is a managed/enterprise plan with no usage data
383+
let isManagedPlan = lowered.contains("managed by admin")
384+
|| lowered.contains("managed by organization")
385+
|| lowered.contains("enterprise")
386+
370387
// Parse reset date from "resets on 01/01"
371388
var resetsAt: Date?
372389
if let resetMatch = stripped.range(of: #"resets on (\d{2}/\d{2})"#, options: .regularExpression) {
@@ -423,8 +440,25 @@ public struct KiroStatusProbe: Sendable {
423440
}
424441
}
425442

443+
// For managed/enterprise plans in new format, we may not have usage data
444+
// but we should still show the plan name without error
445+
if matchedNewFormat, isManagedPlan {
446+
// Managed plans don't expose credits; return snapshot with plan name only
447+
return KiroUsageSnapshot(
448+
planName: planName,
449+
creditsUsed: 0,
450+
creditsTotal: 0,
451+
creditsPercent: 0,
452+
bonusCreditsUsed: nil,
453+
bonusCreditsTotal: nil,
454+
bonusExpiryDays: nil,
455+
resetsAt: nil,
456+
updatedAt: Date())
457+
}
458+
426459
// Require at least one key pattern to match to avoid silent failures
427-
if !matchedPercent, !matchedCredits {
460+
// Only bypass error for managed plans in new format (they don't expose usage data)
461+
if !matchedPercent, !matchedCredits, !(matchedNewFormat && isManagedPlan) {
428462
throw KiroStatusProbeError.parseError(
429463
"No recognizable usage patterns found. Kiro CLI output format may have changed.")
430464
}
@@ -482,5 +516,7 @@ public struct KiroStatusProbe: Sendable {
482516
return stripped.contains("covered in plan")
483517
|| stripped.contains("resets on")
484518
|| stripped.contains("bonus credits")
519+
|| stripped.contains("plan:")
520+
|| stripped.contains("managed by admin")
485521
}
486522
}

Tests/CodexBarTests/KiroStatusProbeTests.swift

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,55 @@ struct KiroStatusProbeTests {
122122
}
123123
}
124124

125+
// MARK: - New Format (kiro-cli 1.24+, Q Developer)
126+
127+
@Test
128+
func parsesQDeveloperManagedPlan() throws {
129+
let output = """
130+
Plan: Q Developer Pro
131+
Your plan is managed by admin
132+
133+
Tip: to see context window usage, run /context
134+
"""
135+
136+
let probe = KiroStatusProbe()
137+
let snapshot = try probe.parse(output: output)
138+
139+
#expect(snapshot.planName == "Q Developer Pro")
140+
#expect(snapshot.creditsPercent == 0)
141+
#expect(snapshot.creditsUsed == 0)
142+
#expect(snapshot.creditsTotal == 0)
143+
#expect(snapshot.bonusCreditsUsed == nil)
144+
#expect(snapshot.resetsAt == nil)
145+
}
146+
147+
@Test
148+
func parsesQDeveloperFreePlan() throws {
149+
let output = """
150+
Plan: Q Developer Free
151+
Your plan is managed by admin
152+
"""
153+
154+
let probe = KiroStatusProbe()
155+
let snapshot = try probe.parse(output: output)
156+
157+
#expect(snapshot.planName == "Q Developer Free")
158+
#expect(snapshot.creditsPercent == 0)
159+
}
160+
161+
@Test
162+
func parsesNewFormatWithANSICodes() throws {
163+
let output = """
164+
\u{001B}[38;5;141mPlan: Q Developer Pro\u{001B}[0m
165+
Your plan is managed by admin
166+
"""
167+
168+
let probe = KiroStatusProbe()
169+
let snapshot = try probe.parse(output: output)
170+
171+
#expect(snapshot.planName == "Q Developer Pro")
172+
}
173+
125174
// MARK: - Snapshot Conversion
126175

127176
@Test

0 commit comments

Comments
 (0)