Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions Scripts/lint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@ set -euo pipefail

ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
BIN_DIR="${ROOT_DIR}/.build/lint-tools/bin"
SWIFTLINT_CACHE_DIR="${ROOT_DIR}/.build/swiftlint-cache"

mkdir -p "${SWIFTLINT_CACHE_DIR}"

export XCODE_DEFAULT_TOOLCHAIN_OVERRIDE="${XCODE_DEFAULT_TOOLCHAIN_OVERRIDE:-/Library/Developer/CommandLineTools}"
export TOOLCHAIN_DIR="${TOOLCHAIN_DIR:-/Library/Developer/CommandLineTools}"
export SWIFTLINT_CACHE_PATH="${SWIFTLINT_CACHE_PATH:-${SWIFTLINT_CACHE_DIR}}"

ensure_tools() {
# Always delegate to the installer so pinned versions are enforced.
Expand Down
33 changes: 28 additions & 5 deletions Scripts/package_app.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ SIGNING_MODE=${CODEXBAR_SIGNING:-}
ROOT=$(cd "$(dirname "$0")/.." && pwd)
cd "$ROOT"

MODULE_CACHE_DIR="${ROOT}/.build/module-cache"
export CLANG_MODULE_CACHE_PATH="${CLANG_MODULE_CACHE_PATH:-${MODULE_CACHE_DIR}/clang}"
export SWIFTPM_MODULECACHE_OVERRIDE="${SWIFTPM_MODULECACHE_OVERRIDE:-${MODULE_CACHE_DIR}/swiftpm}"
mkdir -p "${CLANG_MODULE_CACHE_PATH}" "${SWIFTPM_MODULECACHE_OVERRIDE}"

# Load version info
source "$ROOT/version.env"

Expand Down Expand Up @@ -36,15 +41,15 @@ fi

patch_keyboard_shortcuts() {
local util_path="$ROOT/.build/checkouts/KeyboardShortcuts/Sources/KeyboardShortcuts/Utilities.swift"
local recorder_path="$ROOT/.build/checkouts/KeyboardShortcuts/Sources/KeyboardShortcuts/Recorder.swift"
if [[ ! -f "$util_path" ]]; then
return 0
fi
if grep -q "keyboardShortcutsSafeBundle" "$util_path"; then
return 0
fi

chmod +w "$util_path" || true
python3 - "$util_path" <<'PY'
:
else
chmod +w "$util_path" || true
python3 - "$util_path" <<'PY'
import sys
from pathlib import Path

Expand Down Expand Up @@ -96,6 +101,24 @@ if marker not in text:
text = text.replace(marker, "}\n\n" + inject + "\n\nextension Data {")
path.write_text(text)
PY
fi

if [[ -f "$recorder_path" ]]; then
chmod +w "$recorder_path" || true
python3 - "$recorder_path" <<'PY'
import re
import sys
from pathlib import Path

path = Path(sys.argv[1])
text = path.read_text()
if "xcodePreview" not in text:
sys.exit(0)

updated = re.sub(r'\n#Preview\s*\{.*?\n\}\n', '\n', text, flags=re.S)
path.write_text(updated)
PY
fi
}

KEYBOARD_SHORTCUTS_UTIL="$ROOT/.build/checkouts/KeyboardShortcuts/Sources/KeyboardShortcuts/Utilities.swift"
Expand Down
62 changes: 56 additions & 6 deletions Sources/CodexBar/MenuCardView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ struct UsageMenuCardView: View {
let detailRightText: String?
let pacePercent: Double?
let paceOnTop: Bool
let forecastPercent: Double?
let forecastOverflow: Bool
let forecastText: String?

var percentLabel: String {
String(format: "%.0f%% %@", self.percent, self.percentStyle.labelSuffix)
Expand Down Expand Up @@ -334,8 +337,7 @@ private struct MetricRow: View {
percent: self.metric.percent,
tint: self.progressColor,
accessibilityLabel: self.metric.percentStyle.accessibilityLabel,
pacePercent: self.metric.pacePercent,
paceOnTop: self.metric.paceOnTop)
overlay: self.progressOverlay)
VStack(alignment: .leading, spacing: 2) {
HStack(alignment: .firstTextBaseline) {
Text(self.metric.percentLabel)
Expand Down Expand Up @@ -374,9 +376,22 @@ private struct MetricRow: View {
.foregroundStyle(MenuHighlightStyle.secondary(self.isHighlighted))
.lineLimit(1)
}
if let forecastText = self.metric.forecastText {
Text(forecastText)
.font(.footnote)
.foregroundStyle(MenuHighlightStyle.secondary(self.isHighlighted))
.lineLimit(1)
}
}
.frame(maxWidth: .infinity, alignment: .leading)
}

private var progressOverlay: UsageProgressBar.Overlay? {
if let forecastPercent = self.metric.forecastPercent {
return .forecast(percent: forecastPercent, isOverflow: self.metric.forecastOverflow)
}
return .pace(percent: self.metric.pacePercent, onTop: self.metric.paceOnTop)
}
}

private struct UsageNotesContent: View {
Expand Down Expand Up @@ -905,6 +920,7 @@ extension UsageMenuCardView.Model {
let zaiTimeDetail = Self.zaiLimitDetailText(limit: zaiUsage?.timeLimit)
let openRouterQuotaDetail = Self.openRouterQuotaDetail(provider: input.provider, snapshot: snapshot)
if let primary = snapshot.primary {
let forecastDetail = Self.forecastDetail(window: primary, now: input.now, showUsed: input.usageBarsShowUsed)
var primaryDetailText: String? = input.provider == .zai ? zaiTokenDetail : nil
var primaryResetText = Self.resetText(for: primary, style: input.resetTimeDisplayStyle, now: input.now)
if input.provider == .openrouter,
Expand Down Expand Up @@ -932,14 +948,18 @@ extension UsageMenuCardView.Model {
detailLeftText: nil,
detailRightText: nil,
pacePercent: nil,
paceOnTop: true))
paceOnTop: true,
forecastPercent: forecastDetail?.displayPercent,
forecastOverflow: forecastDetail?.isOverflow ?? false,
forecastText: forecastDetail?.summary))
}
if let weekly = snapshot.secondary {
let paceDetail = Self.weeklyPaceDetail(
window: weekly,
now: input.now,
pace: input.weeklyPace,
showUsed: input.usageBarsShowUsed)
let forecastDetail = Self.forecastDetail(window: weekly, now: input.now, showUsed: input.usageBarsShowUsed)
var weeklyResetText = Self.resetText(for: weekly, style: input.resetTimeDisplayStyle, now: input.now)
var weeklyDetailText: String? = input.provider == .zai ? zaiTimeDetail : nil
if input.provider == .warp,
Expand Down Expand Up @@ -968,7 +988,10 @@ extension UsageMenuCardView.Model {
detailLeftText: paceDetail?.leftLabel,
detailRightText: paceDetail?.rightLabel,
pacePercent: paceDetail?.pacePercent,
paceOnTop: paceDetail?.paceOnTop ?? true))
paceOnTop: paceDetail?.paceOnTop ?? true,
forecastPercent: forecastDetail?.displayPercent,
forecastOverflow: forecastDetail?.isOverflow ?? false,
forecastText: forecastDetail?.summary))
}
if input.provider == .kilo,
metrics.contains(where: { $0.id == "primary" }),
Expand All @@ -983,6 +1006,7 @@ extension UsageMenuCardView.Model {
}
}
if input.metadata.supportsOpus, let opus = snapshot.tertiary {
let forecastDetail = Self.forecastDetail(window: opus, now: input.now, showUsed: input.usageBarsShowUsed)
metrics.append(Metric(
id: "tertiary",
title: input.metadata.opusLabel ?? "Sonnet",
Expand All @@ -993,7 +1017,10 @@ extension UsageMenuCardView.Model {
detailLeftText: nil,
detailRightText: nil,
pacePercent: nil,
paceOnTop: true))
paceOnTop: true,
forecastPercent: forecastDetail?.displayPercent,
forecastOverflow: forecastDetail?.isOverflow ?? false,
forecastText: forecastDetail?.summary))
}

if input.provider == .codex, let remaining = input.dashboard?.codeReviewRemainingPercent {
Expand All @@ -1008,7 +1035,10 @@ extension UsageMenuCardView.Model {
detailLeftText: nil,
detailRightText: nil,
pacePercent: nil,
paceOnTop: true))
paceOnTop: true,
forecastPercent: nil,
forecastOverflow: false,
forecastText: nil))
}
return metrics
}
Expand Down Expand Up @@ -1051,6 +1081,12 @@ extension UsageMenuCardView.Model {
let paceOnTop: Bool
}

private struct ForecastDetail {
let displayPercent: Double
let isOverflow: Bool
let summary: String
}

private static func weeklyPaceDetail(
window: RateWindow,
now: Date,
Expand All @@ -1073,6 +1109,20 @@ extension UsageMenuCardView.Model {
paceOnTop: paceOnTop)
}

private static func forecastDetail(
window: RateWindow,
now: Date,
showUsed: Bool) -> ForecastDetail?
{
guard let projection = UsageProjection.linear(window: window, now: now) else { return nil }
let displayPercent = UsageProjectionText.displayPercent(projection: projection, showUsed: showUsed)
guard displayPercent.isFinite else { return nil }
return ForecastDetail(
displayPercent: displayPercent,
isOverflow: projection.isProjectedToOverflow,
summary: UsageProjectionText.summary(projection: projection, now: now))
}

private static func creditsLine(
metadata: ProviderMetadata,
credits: CreditsSnapshot?,
Expand Down
10 changes: 9 additions & 1 deletion Sources/CodexBar/MenuHighlightStyle.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
import SwiftUI

private struct MenuItemHighlightedKey: EnvironmentKey {
static let defaultValue = false
}

// swiftformat:disable:next environmentEntry
extension EnvironmentValues {
@Entry var menuItemHighlighted: Bool = false
var menuItemHighlighted: Bool {
get { self[MenuItemHighlightedKey.self] }
set { self[MenuItemHighlightedKey.self] = newValue }
}
}

enum MenuHighlightStyle {
Expand Down
15 changes: 13 additions & 2 deletions Sources/CodexBar/PreferencesProviderDetailView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -383,8 +383,7 @@ private struct ProviderMetricInlineRow: View {
percent: self.metric.percent,
tint: self.progressColor,
accessibilityLabel: self.metric.percentStyle.accessibilityLabel,
pacePercent: self.metric.pacePercent,
paceOnTop: self.metric.paceOnTop)
overlay: self.progressOverlay)
.frame(minWidth: ProviderSettingsMetrics.metricBarWidth, maxWidth: .infinity)

HStack(alignment: .firstTextBaseline, spacing: 8) {
Expand Down Expand Up @@ -423,12 +422,24 @@ private struct ProviderMetricInlineRow: View {
.font(.footnote)
.foregroundStyle(.tertiary)
}
if let forecastText = self.metric.forecastText, !forecastText.isEmpty {
Text(forecastText)
.font(.footnote)
.foregroundStyle(.tertiary)
}
}
.frame(maxWidth: .infinity, alignment: .leading)
}
.padding(.vertical, 2)
}

private var progressOverlay: UsageProgressBar.Overlay? {
if let forecastPercent = self.metric.forecastPercent {
return .forecast(percent: forecastPercent, isOverflow: self.metric.forecastOverflow)
}
return .pace(percent: self.metric.pacePercent, onTop: self.metric.paceOnTop)
}

private var detailText: String? {
guard let detailText = self.metric.detailText, !detailText.isEmpty else { return nil }
return detailText
Expand Down
Loading