Skip to content

Add Kiro provider#139

Merged
kargnas merged 9 commits into
opgginc:mainfrom
Daltonganger:feature/kiro-provider
May 22, 2026
Merged

Add Kiro provider#139
kargnas merged 9 commits into
opgginc:mainfrom
Daltonganger:feature/kiro-provider

Conversation

@Daltonganger
Copy link
Copy Markdown
Contributor

Overview

This PR adds Kiro as a first-class quota provider in OpenCode Bar.

Kiro exposes usage through the authenticated local kiro-cli experience rather than a stable public usage API. The provider therefore uses the officially available CLI flow, runs /usage through kiro-cli chat --classic, and parses the same billing summary that users can see in Kiro itself. This keeps the integration aligned with Kiro's current product surface while avoiding direct reads of local auth tokens or private account databases.

Changes

  • Add a new KiroProvider as a quota-based provider.
  • Discover kiro-cli through PATH, login shell PATH, common install locations, and the macOS app bundle.
  • Fetch Kiro billing usage via kiro-cli chat --classic with /usage.
  • Parse monthly credit usage, total credits, reset date, plan name, and overage status from Kiro CLI output.
  • Add Kiro to provider registration for the app and CLI.
  • Add Kiro to quota menu ordering, status bar usage prioritization, provider icon handling, and subscription presets.
  • Add Kiro-specific detail menu rows for monthly credits, credits used, credits left, plan, and subscription settings.
  • Add Kiro provider tests covering parser behavior, ANSI output cleanup, plan hint trimming, comma-formatted numbers, missing usage output, and quota/result conversion.

Validation

  • KiroProviderTests pass locally.
  • Full macOS test suite passed locally for the branch.
  • Debug app build passed locally with ENABLE_DEBUG_DYLIB=NO.
  • Runtime verification confirmed:
    • Kiro appears in the quota menu.
    • ProviderManager includes Kiro in the fetch cycle.
    • KiroProvider finds kiro-cli on this Mac.
    • Kiro usage fetch succeeds through /usage.
    • The menu displays Kiro quota usage.

Notes

The provider intentionally does not parse Kiro's local auth database. It uses kiro-cli as the integration boundary so authentication remains owned by Kiro.

@op-gg-ai-devops
Copy link
Copy Markdown
Contributor

op-gg-ai-devops Bot commented May 19, 2026

✅ AI Code Review Completed

Review finished. Check the PR for inline comments.


📋 View Logs | 🤖 Model: anthropic/claude-opus-4-7|high

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a347d84985

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +199 to +205
guard let result = try await group.next() else {
throw ProviderError.providerError("kiro-cli /usage task failed")
}

group.cancelAll()
if process.isRunning {
process.terminate()
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Ensure timeout path always terminates running kiro-cli process

When the timeout task wins, try await group.next() throws and execution skips the cleanup block that calls process.terminate(). In that path, the command task can remain suspended on its continuation while kiro-cli keeps running, so the fetch can hang past the timeout and leak subprocesses under slow/stuck CLI conditions. Move termination/cancellation into a defer (or equivalent error path) so process cleanup runs even when group.next() throws.

Useful? React with 👍 / 👎.

Comment on lines +141 to +143
process.executableURL = binaryPath
process.arguments = ["chat", "--classic"]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Use supported classic-mode invocation for kiro-cli

This launches Kiro as kiro-cli chat --classic, but Kiro’s CLI docs describe classic mode as a top-level flag (kiro-cli --classic) and list --classic in global UI-engine precedence. On CLI versions that enforce that contract, this argument order exits with an error, causing Kiro usage fetch to fail every time even when authentication is valid.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor

@op-gg-ai-devops op-gg-ai-devops Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

honestly this is a clean, well-scoped Kiro provider add. all the integration touch-points the codebase expects are wired up correctly — ProviderIdentifier, ProviderManager, CLIProviderManager, StatusBarController, both icon views, SubscriptionSettings, and ProviderMenuBuilder. test coverage is solid too — ANSI stripping, comma-separated numbers, plan-hint trimming, centicredit precision, and the "missing credits" error path are all exercised. nice work.

a few real things to look at though:

  1. SwiftLint is failing on duplicate_importsKiroProvider.swift has import Foundation twice on lines 1–2. CI is red on this exact violation, so it has to be fixed before merge.
  2. kiro-cli chat --classic + stdin piping is fragile. The cleaner non-interactive form is kiro-cli chat --no-interactive "/usage" (no stdin handshake, no /quit dance). This is what other Kiro-watching tools (e.g. CodexBar) use, and it removes a whole class of pipe-timing bugs.
  3. minimumFetchInterval = 60s spawns a full chat process every minute. Other CLI-spawning providers feel fine because HTTP is cheap, but spinning up kiro-cli chat ... every 60s is heavier and could trigger session-log spam on the Kiro side. Bumping to 5+ min would be friendlier.
  4. Plan-name regex captures KIRO PRO / KIRO POWER while presets are named Pro / Pro+ / Power — name-based subscription matching may miss. Worth verifying or normalizing.
  5. Output-format risk: the parser matches Credits (X of Y covered in plan) and resets on YYYY-MM-DD. Newer Kiro CLI output has progress-bar style with ███ X% and resets on MM/DD plus a Bonus credits block (per the upstream Kiro issue #7752 and CodexBar's kiro.md). Test against the latest kiro-cli to confirm the current format is still what we're parsing.
  6. Minor: k.circle SF symbol collides with Kimi (also k.circle). Indistinguishable in alerts. Maybe keyboard.badge.eye or another distinct symbol.
  7. Docs nit: docs/AI_USAGE_API_REFERENCE.md has a section per provider (Claude, Codex, Grok, ...). Kiro should get a short entry there too, and a scripts/query-kiro.sh would match the established pattern.

Nothing here is a correctness blocker — it all runs, parses, and ships data into the menu correctly. Just fix the lint failure and the rest can be follow-ups if you want.

CI Status

  • SwiftLint ❌ failed (duplicate_imports on KiroProvider.swift:1-2)
  • Build & Test
  • Build 🟡 in progress
  • GitHub Actions Lint

Comment on lines +1 to +2
import Foundation
import Foundation
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Major: Style duplicate import Foundation: SwiftLint CI fails
import Foundation appears twice. SwiftLint is failing on this exact line with duplicate_imports and that's blocking CI.

import Foundation
import os.log

Comment on lines +178 to +182
static let kiro: [SubscriptionPreset] = [
SubscriptionPreset(name: "Free", cost: 0),
SubscriptionPreset(name: "Pro", cost: 20),
SubscriptionPreset(name: "Pro+", cost: 40),
SubscriptionPreset(name: "Power", cost: 200)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: Bug preset names won't match parsed plan strings
The parser captures plan names like KIRO PRO, KIRO POWER (uppercase, with the KIRO prefix) from the CLI output, but the presets you added below name them Pro, Pro+, Power:

static let kiro: [SubscriptionPreset] = [
    SubscriptionPreset(name: "Free", cost: 0),
    SubscriptionPreset(name: "Pro", cost: 20),
    SubscriptionPreset(name: "Pro+", cost: 40),
    SubscriptionPreset(name: "Power", cost: 200)
]

Depending on how preset matching is done (looks like name-based in ProviderSubscriptionPresets.presets(for:) consumers), this likely won't auto-resolve the right preset. Two clean options:

  1. Normalize the parsed plan name (strip KIRO prefix, lowercase, title-case) in parsePlanName.
  2. Rename presets to match parsed output: KIRO FREE, KIRO PRO, KIRO PRO+, KIRO POWER.

Worth picking whichever matches how other providers (Grok, Kimi) handle the same translation.

let identifier: ProviderIdentifier = .kiro
let type: ProviderType = .quotaBased
let fetchTimeout: TimeInterval = 25
let minimumFetchInterval: TimeInterval = 60
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Major: Perf minimumFetchInterval=60s is too tight for a process-spawn provider
60s is fine for HTTP-based quota providers but here we're spawning a whole kiro-cli chat process (plus a which/login-shell discovery if cached path expires) every minute. That's heavier than the other providers in this manager, and depending on how Kiro counts "sessions" on its side, it can show up as suspicious churn in their logs.

Suggest bumping to something more relaxed (Kiro's plan window is monthly anyway):

let minimumFetchInterval: TimeInterval = 300   // 5 min

fetchTimeout = 25s is also pretty long — combined with the every-minute cadence, a hung CLI can keep a Process around for half the fetch interval. Worth trimming once the --no-interactive switch lands.

return try await withThrowingTaskGroup(of: String.self) { group in
let process = Process()
process.executableURL = binaryPath
process.arguments = ["chat", "--classic"]
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Major: Bug interactive chat + stdin pipe is fragile: use --no-interactive
Spawning kiro-cli chat --classic and piping /usage\n/quit\n through stdin works but is brittle — any change in the chat REPL's prompt or input handling will silently break this. Kiro CLI also ships a non-interactive form:

process.arguments = ["chat", "--no-interactive", "/usage"]

This is what CodexBar and similar tools use, and it removes the stdin pipe + /quit handshake entirely. The output is still ANSI-decorated so the existing stripANSI path keeps working.

Reference: Kiro issue kirodotdev/Kiro#7752 confirms there's no JSON usage endpoint yet, so a non-interactive chat invocation is the cleanest path.

Comment on lines +215 to +232
guard let creditsMatch = firstMatch(
in: normalized,
pattern: #"Credits\s*\(\s*([0-9][0-9,]*(?:\.[0-9]+)?)\s+of\s+([0-9][0-9,]*(?:\.[0-9]+)?)\s+covered\s+in\s+plan\s*\)"#
),
creditsMatch.count >= 3,
let usedCredits = parseNumber(creditsMatch[1]),
let totalCredits = parseNumber(creditsMatch[2]),
totalCredits > 0 else {
throw ProviderError.decodingError("Kiro usage output did not include monthly credit usage")
}

let resetDate = firstMatch(in: normalized, pattern: #"resets\s+on\s+(\d{4}-\d{2}-\d{2})"#).flatMap { match in
match.count > 1 ? parseDate(match[1]) : nil
}
let planName = parsePlanName(from: normalized)
let overageStatus = firstMatch(in: normalized, pattern: #"Overages:\s*([A-Za-z]+)"#).flatMap { match in
match.count > 1 ? match[1] : nil
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Major: Bug regex assumes old kiro-cli format; new TUI uses progress bar + MM/DD
The regex chain is locked to a specific output format:

  • Credits (X of Y covered in plan) for credits
  • resets on YYYY-MM-DD for reset date
  • Overages: <word> for overage status

Upstream Kiro (issue kirodotdev/Kiro#7752) and at least one third-party tool's docs describe a newer CLI output that uses a progress bar (██████ X%), a resets on MM/DD short date, and adds a Bonus credits: A/B credits used, expires in N days section. If you're on the older format the tests pass — but it'd be worth re-running kiro-cli chat --no-interactive "/usage" against the latest CLI build and:

  1. Confirming the regexes still hit.
  2. Adding a regression fixture for the newer format (with progress bar + MM/DD reset + bonus credits) so this provider doesn't silently zero out when Kiro updates the TUI.

If the new format is the only one in the wild, you'll also want to add a Bonus credits parse path so the secondary window shows up.

return "moon.stars"
case .openCodeGo:
return "chevron.left.forwardslash.chevron.right"
case .kiro:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: Style k.circle collides with Kimi icon
Kimi already uses k.circle (see the .kimi case right below in MultiProviderStatusBarIconView). Two providers with the same SF symbol means a user running both can't visually tell their status-bar alerts apart. Pick a Kiro-distinct symbol — e.g. keyboard.badge.eye, cpu, or a custom asset — and apply it consistently across ProviderIdentifier.iconName, MultiProviderStatusBarIconView, and ModernStatusBarIconView.

@op-gg-ai-devops
Copy link
Copy Markdown
Contributor

op-gg-ai-devops Bot commented May 19, 2026

✅ AI Code Review Completed

Review finished. Check the PR for inline comments.


📋 View Logs | 🤖 Model: anthropic/claude-opus-4-7|high

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: cbab362072

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +36 to +37
var remainingCredits: Double {
max(totalCredits - usedCredits, 0)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve overage when deriving remaining credits

When Kiro overages are enabled, usedCredits can exceed totalCredits, but this getter clamps remainingCredits to zero. That loses the over-limit amount before makeResult builds ProviderUsage.quotaBased, so the app reports at most 100% usage and cannot reflect how far over quota the account is. This underreports real usage specifically for paid plans with overage enabled.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor

@op-gg-ai-devops op-gg-ai-devops Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ngl, this is a really clean follow-up pass. you actually went through every single piece of feedback from the last round and fixed it — that's rare and worth calling out:

  • duplicate_imports lint failure → fixed, SwiftLint CI is green now.
  • ✅ Stdin pipe + /quit handshake → replaced with the clean kiro-cli chat --no-interactive "/usage" invocation.
  • minimumFetchInterval bumped from 60s → 300s, way friendlier to Kiro's side.
  • ✅ Plan name normalization (KIRO PRO+Pro+, etc.) now actually matches the SubscriptionPreset names. Good catch on the precedence order in normalizePlanNamePRO+ checked before PRO so it doesn't get swallowed.
  • ✅ New TUI format coverage: progress bar (███ X%), MM/DD short reset date, and the Bonus credits: block all parsed. The table-style regression test (testUsageParserReadsTableOutputWithSlashReset) is exactly the kind of fixture I asked for.
  • k.circle collision with Kimi resolved by picking keyboard.badge.eye, consistently applied across ProviderIdentifier, both icon views, and StatusBarController.
  • ✅ Timeout path: defer { group.cancelAll(); if process.isRunning { process.terminate() } } now handles the codex P1 about leaking subprocesses on the timeout branch.
  • ✅ Docs: docs/AI_USAGE_API_REFERENCE.md got a proper Kiro section, and scripts/query-kiro.sh matches the established query-*.sh pattern.

Test coverage is solid for the parser surface — ANSI stripping, comma numbers, plan-hint trimming, centicredit scaling, the missing-credits error path, and the new TUI fallback are all exercised.

CI is fully green on cbab362 (Build, Build & Test, SwiftLint, GitHub Actions Lint).

A couple of small things still worth a look:

  1. scripts/query-kiro.sh is missing the shebang line. Every other query-*.sh starts with #!/usr/bin/env bash or #!/bin/bash. Without one, the kernel returns ENOEXEC and the script falls through to /bin/sh (dash on Linux CI), which immediately dies on set -euo pipefail with Illegal option -o pipefail. Reproduced locally:

    $ /bin/sh scripts/query-kiro.sh --help
    scripts/query-kiro.sh: 4: set: Illegal option -o pipefail
    

    One-line fix, but it does block the script from being used outside of macOS bash.

  2. Process.waitUntilExit() in the binary-discovery helpers. Both findBinaryViaWhich and findBinaryViaLoginShell block the actor synchronously. The login-shell variant in particular can sit on a 100–500ms shell startup. Other providers (Antigravity, OpenCode Zen) moved to the withCheckedThrowingContinuation + terminationHandler pattern for exactly this reason — see the AGENTS.md "Process.waitUntilExit() Blocking Issue" note. Not a blocker since this only runs on cold fetch, but worth aligning eventually.

  3. Heads up on the open Codex P2 (KiroUsageSnapshot.remainingCredits): clamping to >= 0 discards overage information when the Kiro plan allows overages. The shared TableFormatter.formatQuotaMetrics already understands a negative remaining as overage (e.g., "3 overage (allowed)"), so passing entitlement - used through unclamped would give a more honest read for paid plans. Not raising as a separate inline — Codex already has the right line on it.

None of these are correctness blockers for the feature, and the integration touch-points (ProviderManager, CLIProviderManager, StatusBarController, both icon views, SubscriptionSettings, ProviderMenuBuilder) are all wired up correctly. Nice work — happy to approve.


do {
try process.run()
process.waitUntilExit()
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: Perf Process.waitUntilExit blocks the actor: align with async pattern
both findBinaryViaWhich (line 115) and findBinaryViaLoginShell (line 143) use process.waitUntilExit() synchronously inside fetch(). per the AGENTS.md note on Process.waitUntilExit() Blocking Issue, this is a known anti-pattern in this codebase — it blocks the calling actor and can prevent other parallel provider fetches from completing while a slow shell startup (login-shell which can run 100–500ms easily) drags on.

other CLI-spawning providers (Antigravity, OpenCodeZen, and your own runKiroUsage below) already use the async withCheckedThrowingContinuation + terminationHandler pattern. consider migrating these helpers to the same style:

private func findBinaryViaWhich() async -> URL? {
    try? await runProcessAsync(executable: "/usr/bin/which", args: ["kiro-cli"])
        .flatMap { ... }
}

not a blocker since cold-fetch discovery only happens once before the result gets cached upstream — just flagging for consistency with the established pattern.

Comment thread scripts/query-kiro.sh
@@ -0,0 +1,203 @@
# Query Kiro billing usage through the authenticated Kiro CLI.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Major: Bug missing shebang: script fails under /bin/sh
this file is missing a shebang. without one, executing it falls through to /bin/sh (dash on Linux CI) which doesn't support set -o pipefail:

$ /bin/sh scripts/query-kiro.sh --help
scripts/query-kiro.sh: 4: set: Illegal option -o pipefail

every other scripts/query-*.sh starts with #!/usr/bin/env bash (e.g. query-grok.sh, query-opencode-go.sh). add the same line at the top so set -euo pipefail works regardless of how the script is invoked, and so scripts/query-all.sh (which exec's it directly) doesn't silently break in non-macOS bash environments.

#!/usr/bin/env bash
# Query Kiro billing usage through the authenticated Kiro CLI.

@op-gg-ai-devops
Copy link
Copy Markdown
Contributor

🤖 AI Code Review Status

Review started with anthropic/claude-opus-4-7|high...

@op-gg-ai-devops
Copy link
Copy Markdown
Contributor

op-gg-ai-devops Bot commented May 19, 2026

✅ AI Code Review Completed

Review finished. Check the PR for inline comments.


📋 View Logs | 🤖 Model: anthropic/claude-opus-4-7|high

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 03fb2442b4

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread scripts/query-kiro.sh Outdated
Comment on lines +1 to +2
# Query Kiro billing usage through the authenticated Kiro CLI.
#!/usr/bin/env bash
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Move shebang to the first line

Putting a regular comment before #!/usr/bin/env bash prevents the kernel from recognizing the script interpreter, so direct invocations can run under /bin/sh (or fail with ENOEXEC) instead of Bash. In this script that breaks execution in non-Bash contexts because set -euo pipefail is not POSIX-sh compatible (e.g., dash errors on pipefail), which can make query-kiro.sh fail in automation even though it appears executable.

Useful? React with 👍 / 👎.

Comment thread scripts/query-kiro.sh Outdated
"plan": plan,
"used_credits": used,
"total_credits": total,
"remaining_credits": max(total - used, 0),
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve negative remaining credits for overage accounts

Clamping remaining_credits with max(total - used, 0) hides overage when usage exceeds plan credits, so JSON/text output underreports real usage for paid plans with overages enabled. This makes the new Kiro query script report "0 left" instead of how far over quota the account is, which can mislead manual checks and any tooling consuming --json output.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor

@op-gg-ai-devops op-gg-ai-devops Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ngl this third pass is genuinely satisfying to review. you came back, hit every single point from the last round, and the diff actually shows it — overage preserved (remainingCredits = totalCredits - usedCredits, no clamp), shebang attempted, test fixture for the overage path, the works. that's rare and worth saying.

What landed cleanly this pass:

  • KiroUsageSnapshot.remainingCredits no longer clamps to >= 0. Negative remaining now flows into ProviderUsage.quotaBased, and testMakeResultPreservesOverageAmount proves overage is reported as usagePercentage = 105, remainingQuota = -5_000. Exactly what the shared TableFormatter.formatQuotaMetrics overage path expects.
  • ✅ Integration touch-points are still all wired correctly: ProviderIdentifier, ProviderManager, CLIProviderManager, StatusBarController (both the icon switch and the usagePercentCandidates switch), MultiProviderStatusBarIconView, ModernStatusBarIconView, SubscriptionSettings, ProviderMenuBuilder. Docs section 7 was added, sections 8–9 renumbered correctly.
  • ✅ SwiftLint and Build are green on 03fb244. Build & Test is still pending at the time of review but didn't regress.

One thing actually broke though:

The Fix Kiro query script shebang commit (03fb244) put #!/usr/bin/env bash on line 2 of scripts/query-kiro.sh, with a # Query Kiro... comment on line 1. The kernel only treats the file as a shebang script if #! is the very first two bytes — line 2 doesn't count, so this is still the same ENOEXEC fallthrough the previous review flagged. Reproduced locally just now:

$ /bin/sh scripts/query-kiro.sh --help
scripts/query-kiro.sh: 6: set: Illegal option -o pipefail

Every other scripts/query-*.sh (e.g. query-grok.sh, query-opencode-go.sh) starts with the shebang on line 1. Swap the two lines and this is gone.

Smaller follow-up:

  • scripts/query-kiro.sh still clamps remaining_credits to max(total - used, 0) (line 156), which contradicts the Swift change you just made on the same PR. Worth letting it go negative so the debug script matches what the app actually reports.

A few previously-raised items remain intentionally not re-raised: the Process.waitUntilExit() synchronous calls in findBinaryViaWhich / findBinaryViaLoginShell are a known not-a-blocker per the last review (cold-fetch only, cached upstream), and everything from rounds 1–2 (duplicate imports, --no-interactive, minimumFetchInterval=300, plan-name normalization, new TUI parser, keyboard.badge.eye, timeout-path cleanup, docs, query script existence) is fixed.

Approving once the shebang lands on line 1. Nice work overall.

Comment thread scripts/query-kiro.sh Outdated
"plan": plan,
"used_credits": used,
"total_credits": total,
"remaining_credits": max(total - used, 0),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: Bug remaining_credits clamp hides overage that the Swift provider now preserves
This still clamps remaining_credits to >= 0, but the Swift side in this same PR (KiroUsageSnapshot.remainingCredits, KiroProvider.makeResult) was specifically changed to let it go negative so overage on paid plans is reported honestly (e.g., usagePercentage = 105, remainingQuota = -5_000).

Either drop the clamp here too so the debug script matches the app, or at least surface the raw total - used alongside the clamped value:

result = {
    ...
    "remaining_credits": total - used,
    ...
}

Not a blocker — just so this script doesn't quietly mask the same overage information the menu now exposes.

Comment thread scripts/query-kiro.sh Outdated
@@ -0,0 +1,205 @@
# Query Kiro billing usage through the authenticated Kiro CLI.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Major: Bug shebang on line 2 = ENOEXEC: script still fails under /bin/sh
The shebang line MUST be the first line of the file. Here it's on line 2 with a # Query Kiro billing usage... comment on line 1, so the kernel never sees #! at offset 0 — it returns ENOEXEC and the shell falls back to /bin/sh (dash on Linux CI), which immediately blows up on set -o pipefail:

$ /bin/sh scripts/query-kiro.sh --help
scripts/query-kiro.sh: 6: set: Illegal option -o pipefail

This is the exact failure mode the previous review flagged. Commit 03fb244 did add the shebang text, just placed it on the wrong line. Other scripts/query-*.sh files (grok, opencode-go) put it on line 1.

Fix:

#!/usr/bin/env bash
# Query Kiro billing usage through the authenticated Kiro CLI.

Once that lands, scripts/query-all.sh (which exec's it directly) and any non-macOS bash invocation will stop silently dying.

@op-gg-ai-devops
Copy link
Copy Markdown
Contributor

op-gg-ai-devops Bot commented May 19, 2026

✅ AI Code Review Completed

Review finished. Check the PR for inline comments.


📋 View Logs | 🤖 Model: anthropic/claude-opus-4-7|high

Copy link
Copy Markdown
Contributor

@op-gg-ai-devops op-gg-ai-devops Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ngl this 4th pass is so close. you've got every Swift integration touch-point landing cleanly — KiroProvider itself reads fine, the parser fixture set actually covers both the classic line format and the new table-style TUI with bonus credits + MM/DD reset, and testMakeResultPreservesOverageAmount proves the overage path now flows through to ProviderUsage.quotaBased as usagePercentage = 105, remainingQuota = -5_000. integration touch-points still wired correctly: ProviderIdentifier, ProviderManager, CLIProviderManager, StatusBarController (icon switch + usagePercentCandidates weekly priority), MultiProviderStatusBarIconView, ModernStatusBarIconView, SubscriptionSettings, ProviderMenuBuilder. docs section 7 reads well, scripts/query-all.sh updated, and the keyboard.badge.eye icon picked specifically to avoid the Kimi k.circle collision is applied consistently across all 4 places.

also worth calling out: you actually went ahead and dropped the max(total - used, 0) clamp in scripts/query-kiro.sh line 156 between the last review and this one (commit 7b786a1), so the debug script now matches what the Swift provider reports. that's exactly what the previous round asked for, no nagging needed. good.

but one thing is genuinely still broken though:

scripts/query-kiro.sh STILL has the shebang on line 2, not line 1. this is now the third commit (4efebc2 had no shebang at all, 03fb244 put it on line 2, 7b786a1 left it on line 2) that's tried to fix this exact bug and the shebang is still in the wrong place. reproduced just now on this exact HEAD:

$ head -2 scripts/query-kiro.sh
# Query Kiro billing usage through the authenticated Kiro CLI.
#!/usr/bin/env bash

$ /bin/sh scripts/query-kiro.sh --help
scripts/query-kiro.sh: 6: set: Illegal option -o pipefail

the kernel only treats a file as a shebang script if #! is the very first two bytes of the file. a # comment on line 1 means the kernel returns ENOEXEC and the parent shell falls back to /bin/sh (dash on Linux CI / GitHub Actions), which then dies on set -o pipefail because that's bash-only. on macOS this happens to work because /bin/sh is bash3 in compat mode, but it breaks on every Linux invocation and breaks scripts/query-all.sh (which exec's it directly).

the fix is a one-line swap — just put #!/usr/bin/env bash as the very first line and move the comment below. every other scripts/query-*.sh does it this way. one shot, done.

everything else is approved as-is — Process.waitUntilExit() in findBinaryViaWhich / findBinaryViaLoginShell stays intentionally not re-raised per the last two reviews (cold-fetch only, cached upstream), and rounds 1–3 items (duplicate_imports, --no-interactive, minimumFetchInterval=300, plan-name normalization, new TUI parser, overage preservation in both Swift and the script, icon de-collision, timeout cleanup, docs section 7 + query-all.sh entry) are all properly landed.

approving on the strength of the Swift side. shebang swap is the only line that has to change to close out the round.

CI Status

  • SwiftLint ✅ pass
  • GitHub Actions Lint ✅ pass
  • Build 🟡 pending (no regression vs 03fb244)
  • Build & Test 🟡 pending

Comment thread scripts/query-kiro.sh Outdated
Comment on lines +1 to +2
# Query Kiro billing usage through the authenticated Kiro CLI.
#!/usr/bin/env bash
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Major: Bug shebang on line 2 = ENOEXEC: still falls through to /bin/sh on Linux
this is the third commit aimed at fixing the shebang and it's still on line 2 instead of line 1. the kernel only recognizes #! as a shebang when it's at offset 0 — a regular # comment on line 1 makes the kernel return ENOEXEC, the parent shell falls back to /bin/sh (dash on Linux CI / GitHub Actions), and set -o pipefail on line 6 immediately blows up because that's not POSIX-sh:

$ head -2 scripts/query-kiro.sh
# Query Kiro billing usage through the authenticated Kiro CLI.
#!/usr/bin/env bash

$ /bin/sh scripts/query-kiro.sh --help
scripts/query-kiro.sh: 6: set: Illegal option -o pipefail

on macOS this happens to work because /bin/sh is bash3 in compat mode, but it breaks on every Linux exec path and breaks scripts/query-all.sh which exec's this file directly. every other scripts/query-*.sh (grok, opencode-go, ...) has the shebang as the literal first line — just swap line 1 and line 2:

#!/usr/bin/env bash
# Query Kiro billing usage through the authenticated Kiro CLI.

not a Swift-side blocker so not failing the review on it, but this is the same line that's been called out twice already in rounds 2 and 3 — please get it on line 1 this time.

@op-gg-ai-devops
Copy link
Copy Markdown
Contributor

op-gg-ai-devops Bot commented May 19, 2026

✅ AI Code Review Completed

Review finished. Check the PR for inline comments.


📋 View Logs | 🤖 Model: anthropic/claude-opus-4-7|high

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 801c3b1342

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".


do {
try process.run()
process.waitUntilExit()
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Remove blocking waitUntilExit from login-shell lookup

findBinaryViaLoginShell() uses process.waitUntilExit(), which blocks the task thread and does not cooperate with cancellation. Because ProviderManager.fetchWithTimeout relies on task-group cancellation for timeouts, a hung login shell startup (e.g., slow/blocked shell init scripts) can keep KiroProvider.fetch() stuck indefinitely and prevent the timeout path from completing, stalling provider refreshes.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor

@op-gg-ai-devops op-gg-ai-devops Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ngl this is the round where everything actually lands. 5th pass and the one outstanding blocker from rounds 3 and 4 — the scripts/query-kiro.sh shebang on line 2 — is finally on line 1. verified on this exact HEAD (801c3b1):

$ head -2 scripts/query-kiro.sh
#!/usr/bin/env bash

$ scripts/query-kiro.sh --help
Usage: scripts/query-kiro.sh [options]
...

kernel no longer falls back to dash, set -o pipefail works, scripts/query-all.sh can exec it cleanly on Linux CI too. done.

What's still landed correctly across all 5 rounds:

  • KiroProvider itself reads cleanly — withThrowingTaskGroup with timeout + defer { group.cancelAll(); if process.isRunning { process.terminate() } } handles the subprocess leak path, nonisolated(unsafe) var outputData = Data() matches the repo's documented async Process pattern, and kiro-cli chat --no-interactive "/usage" is the clean invocation (no stdin handshake).
  • ✅ Overage preservation in both directions: Swift KiroUsageSnapshot.remainingCredits = totalCredits - usedCredits (no clamp), makeResult passes the negative remaining straight into ProviderUsage.quotaBased, and testMakeResultPreservesOverageAmount proves usagePercentage = 105, remainingQuota = -5_000. Python script also matches now (remaining_credits: total - used, no clamp on line 156).
  • ✅ Parser fixture set actually covers both the classic line format (Credits (X of Y covered in plan)) and the new TUI table format with progress bar (███ X%), MM/DD short reset date, and the Bonus credits: block. testUsageParserReadsTableOutputWithSlashReset is exactly the regression coverage the bonus credits path needed.
  • ✅ Plan name normalization order (PRO+ checked before PRO so it doesn't get swallowed), keyboard.badge.eye chosen specifically to avoid the Kimi k.circle collision, minimumFetchInterval = 300 friendly to Kiro's side.
  • ✅ Every integration touch-point this codebase expects: ProviderIdentifier (rawValue/displayName/shortName/iconName), ProviderManager, CLIProviderManager, both icon views (MultiProviderStatusBarIconView + ModernStatusBarIconView), StatusBarController (icon switch + usagePercentCandidates monthly priority), ProviderMenuBuilder Kiro submenu with Monthly Credits + Bonus Credits + Plan rows, SubscriptionSettings presets matching the normalized plan names.
  • ✅ Docs: docs/AI_USAGE_API_REFERENCE.md got a proper Kiro section 7 with renumbering of Grok→8 and Antigravity→9, plus the test script table entry.

Two tiny FYI items, neither blocking:

  1. The Free 50 / Pro 1000 / Pro+ 2000 / Power 10000 plan-totals fallback is duplicated across KiroProvider.planCreditTotal (Swift) and PLAN_TOTALS in scripts/query-kiro.sh (Python). If Kiro ever changes a plan limit, both need to be updated. There's no clean cross-language share for this so leaving it as-is is fine, just keep them in sync.
  2. If kiro-cli is installed but not signed in, the parser throws Kiro usage output did not include monthly credit usage instead of surfacing an auth-style message. Not a blocker — the throw is correct, just a bit cryptic for that specific failure mode. Future improvement could sniff for "sign in" / "login" patterns in the merged stdout+stderr and convert to ProviderError.authenticationFailed with the same string the cold-fetch path uses.

Carried over intentionally not re-raised (consistent with rounds 1–4 conclusions): Process.waitUntilExit() in findBinaryViaWhich / findBinaryViaLoginShell is cold-fetch only and cached upstream; everything from rounds 1–3 (duplicate_imports, --no-interactive, minimumFetchInterval=300, plan-name normalization, TUI parser, overage preservation, icon de-collision, timeout cleanup, docs, query-all.sh entry) is properly landed.

5 rounds of iteration on a single provider is real work, and you actually shipped it without giving up on the shebang fix even after the kernel rules tripped you up three times in a row. nice.

CI Status

  • SwiftLint ✅ pass
  • GitHub Actions Lint ✅ pass
  • Build 🟡 in progress (no regression vs prior HEAD)
  • Build & Test 🟡 in progress

@op-gg-ai-devops
Copy link
Copy Markdown
Contributor

op-gg-ai-devops Bot commented May 20, 2026

✅ AI Code Review Completed

Review finished. Check the PR for inline comments.


📋 View Logs | 🤖 Model: anthropic/claude-opus-4-7|high

Copy link
Copy Markdown
Contributor

@op-gg-ai-devops op-gg-ai-devops Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ngl 6th round and this is just a vibes pass — the only thing that changed since the last review is the Kiro status-bar icon swap from the SF symbol keyboard.badge.eye to a custom KiroIcon asset (the Kiro ghost mascot). nothing else moved.

The icon swap itself is wired up cleanly. all four touch-points the codebase expects are updated consistently:

  • ProviderIdentifier.iconName"KiroIcon"
  • StatusBarController.providerImageNSImage(named: "KiroIcon")
  • MultiProviderStatusBarIconView.drawProviderAlerticonName = "KiroIcon" (then icon.isTemplate = true for red-tint alert path)
  • ModernStatusBarIconView.iconViewImage("KiroIcon").renderingMode(.template) with a dedicated SwiftUI branch, and systemIconName(for: .kiro) correctly returns nil now that the custom asset path owns the rendering

Asset catalog is configured right too: Contents.json has template-rendering-intent: template + preserves-vector-representation: true, and the SVG is single-color (fill="#000000") on a transparent background, so template tinting will work in both light/dark mode and on the red-alert tint path in MultiProviderStatusBarIconView. matches exactly how GrokIcon and the other custom brand assets are wired.

no Kimi k.circle collision risk anymoreKiroIcon is its own asset and doesn't overlap with any existing custom icon (ClaudeIcon, CodexIcon, GrokIcon, KimiIcon/k.circle, etc.). the original reason for picking keyboard.badge.eye (collision avoidance) is moot once the custom asset is in.

Everything from rounds 1–5 is still landed correctly:

  • KiroProvider async pattern with withThrowingTaskGroup + timeout + defer { group.cancelAll(); if process.isRunning { process.terminate() } } for the subprocess leak path
  • kiro-cli chat --no-interactive "/usage" clean invocation (no stdin handshake)
  • Overage preserved in both Swift (KiroUsageSnapshot.remainingCredits = totalCredits - usedCredits, no clamp) and the Python script (remaining_credits: total - used at scripts/query-kiro.sh:156)
  • Parser fixture set covers classic line format + new TUI table format with progress bar (███ X%), MM/DD short reset date, and Bonus credits: block
  • minimumFetchInterval = 300, plan-name normalization order (PRO+ before PRO), shebang on line 1 of scripts/query-kiro.sh
  • Docs AI_USAGE_API_REFERENCE.md section 7 + scripts/query-all.sh entry

carried over intentionally not re-raised (consistent with rounds 1–5): Process.waitUntilExit() in findBinaryViaWhich / findBinaryViaLoginShell is cold-fetch only per prior reviews; the PLAN_TOTALS duplication between Swift and the Python script stays as documented sync.

6 rounds of iteration on one provider, and you closed every loop without giving up — the shebang odyssey alone was 3 commits worth of kernel-rules learning. clean shipping. approving.

CI Status (HEAD 56864e3)

  • SwiftLint ✅ pass
  • GitHub Actions Lint ✅ pass
  • Build 🟡 in progress (no regression expected — pure asset + icon-name change)
  • Build & Test 🟡 in progress

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 56864e376b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

return try await withThrowingTaskGroup(of: String.self) { group in
let process = Process()
process.executableURL = binaryPath
process.arguments = ["chat", "--no-interactive", "/usage"]
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Execute /usage in an interactive Kiro session

This command calls kiro-cli chat --no-interactive "/usage", but Kiro documents slash commands as interactive-chat features while --no-interactive is a single-response non-interactive mode. In that mode, /usage can be treated as plain prompt text instead of emitting the structured usage panel output this parser expects, which causes parseUsageOutput to fail and the Kiro provider to surface an error even for authenticated users.

Useful? React with 👍 / 👎.

Comment thread scripts/query-kiro.sh
Comment on lines +58 to +60
found = shutil.which("kiro-cli")
if found:
return found
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Add login-shell PATH discovery for kiro-cli

Binary discovery here only checks shutil.which in the current process environment plus hardcoded fallback paths, so it misses installs that are only exposed by login-shell startup files (e.g., PATH exported in .zprofile/.bash_profile). In those setups this script incorrectly exits with “kiro-cli not found” despite a valid installation, which breaks local diagnostics and automation using query-kiro.sh.

Useful? React with 👍 / 👎.

@op-gg-ai-devops
Copy link
Copy Markdown
Contributor

op-gg-ai-devops Bot commented May 20, 2026

✅ AI Code Review Completed

Review finished. Check the PR for inline comments.


📋 View Logs | 🤖 Model: anthropic/claude-opus-4-7|high

Copy link
Copy Markdown
Contributor

@op-gg-ai-devops op-gg-ai-devops Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ngl, 7th round on this PR and the one thing the last review explicitly carried over as "not re-raised" — Process.waitUntilExit() blocking in findBinaryViaWhich / findBinaryViaLoginShell — you just went and fixed anyway in commit 29cc25e. that's the kind of follow-through that's actually rare. respect.

What 29cc25e did right:

  • Both lookup paths now go through a shared runLookupProcess(executableURL:arguments:timeout:) helper that mirrors the exact async pattern the codebase already uses for runKiroUsage: withThrowingTaskGroup + child task with withCheckedThrowingContinuation+terminationHandler + a sibling timeout task that throws after 5s + defer { group.cancelAll(); if process.isRunning { process.terminate() } } for the subprocess leak path. matches the documented AGENTS.md rule against Process.waitUntilExit() in async contexts.
  • The Python scripts/query-kiro.sh got the same login-shell PATH discovery ($SHELL -lc 'command -v kiro-cli') ahead of the hardcoded fallback list, with a 5s timeout and a silent except Exception. parity with the Swift discovery order — good.
  • validatedBinaryPath(from:) extracted as a small helper so both lookup methods share the trim + executability check. clean dedup.

Everything from rounds 1–6 is still landed and not regressed:

  • kiro-cli chat --no-interactive "/usage" invocation (no stdin handshake), nonisolated(unsafe) var outputData for the Process readability handler, ANSI strip + non-breaking-space normalization in parseUsageOutput.
  • Overage preserved end-to-end: KiroUsageSnapshot.remainingCredits = totalCredits - usedCredits (no clamp), makeResult passes the negative remaining straight into ProviderUsage.quotaBased, testMakeResultPreservesOverageAmount proves usagePercentage == 105 and remainingQuota == -5_000. Python script matches (remaining_credits: total - used on line 170, no clamp).
  • Parser fixtures still cover classic line format (Credits (X of Y covered in plan)) + new TUI table format with progress bar (███ X%) + MM/DD short reset (rolls year forward when in the past) + Bonus credits: block.
  • Every integration touch-point: ProviderIdentifier (rawValue/displayName/shortName/iconName), ProviderManager, CLIProviderManager, StatusBarController (icon switch + usagePercentCandidates monthly priority + visible providers list), ProviderMenuBuilder Kiro submenu (Monthly Credits + bonus + plan rows), MultiProviderStatusBarIconView + ModernStatusBarIconView with the custom KiroIcon template asset, SubscriptionSettings presets (Free 0 / Pro 20 / Pro+ 40 / Power 200).
  • Shebang on line 1 of scripts/query-kiro.sh, query-all.sh entry, docs section 7 with proper renumbering.

Carried over intentionally, still not blocking:

  1. PLAN_TOTALS duplicated between KiroProvider.planCreditTotal (Swift) and the Python script — no clean cross-language share. keep them in sync when Kiro changes plan limits.
  2. The "signed-in but no usage line" failure mode still surfaces Kiro usage output did not include monthly credit usage rather than an auth-style message. cryptic but correct; future improvement could sniff sign in / login patterns and remap to ProviderError.authenticationFailed.
  3. ProviderMenuBuilder Kiro case shows Credits Left: -50.00 when in overage — accurate per the design ("overage allowed"), but a future polish could flip to Credits Over: 50.00 when negative, matching how TableFormatter.formatQuotaMetrics renders the overage suffix.

7 rounds of iteration, every loop closed, and the optional carry-over got fixed for free. nice work.

CI Status (HEAD 29cc25e)

  • Build ✅ pass
  • Build & Test ✅ pass
  • SwiftLint ✅ pass
  • GitHub Actions Lint ✅ pass

@kargnas kargnas merged commit e7602d0 into opgginc:main May 22, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants