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
5 changes: 5 additions & 0 deletions Scripts/lint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ check_package_signing() {
"${ROOT_DIR}/Scripts/test_package_signing.sh"
}

check_package_ccusage_helper() {
"${ROOT_DIR}/Scripts/test_package_ccusage_helper.sh"
}

check_package_info_plist() {
"${ROOT_DIR}/Scripts/test_package_info_plist.sh"
}
Expand Down Expand Up @@ -110,6 +114,7 @@ run_portable_checks() {
check_package_product_paths
check_package_strip
check_package_signing
check_package_ccusage_helper
check_package_info_plist
check_release_dsym_paths
check_sparkle_signing_paths
Expand Down
58 changes: 58 additions & 0 deletions Scripts/package_app.sh
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,60 @@ verify_binary_arches() {
done
}

install_optional_ccusage_helper() {
local source="${CODEXBAR_CCUSAGE_SOURCE:-}"
local required="${CODEXBAR_REQUIRE_CCUSAGE:-0}"
local version="${CODEXBAR_CCUSAGE_VERSION:-}"
local expected_sha256="${CODEXBAR_CCUSAGE_SHA256:-}"
local destination="$APP/Contents/Helpers/ccusage"
local provenance="$destination.provenance"

case "$required" in
0|1) ;;
*)
echo "ERROR: CODEXBAR_REQUIRE_CCUSAGE must be 0 or 1" >&2
return 1
;;
esac

if [[ -z "$source" ]]; then
if [[ "$required" == "1" ]]; then
echo "ERROR: CODEXBAR_REQUIRE_CCUSAGE=1 requires CODEXBAR_CCUSAGE_SOURCE" >&2
return 1
fi
rm -f "$destination" "$provenance"
return 0
fi

if [[ -z "$version" || -z "$expected_sha256" ]]; then
echo "ERROR: CODEXBAR_CCUSAGE_SOURCE requires CODEXBAR_CCUSAGE_VERSION and CODEXBAR_CCUSAGE_SHA256" >&2
return 1
fi
if [[ ! "$expected_sha256" =~ ^[[:xdigit:]]{64}$ ]]; then
echo "ERROR: CODEXBAR_CCUSAGE_SHA256 must be a 64-character hexadecimal SHA-256 digest" >&2
return 1
fi

source="$(printf '%s' "$source" | sed 's#^~/#'"$HOME"'/#')"
if [[ ! -f "$source" ]]; then
echo "ERROR: CODEXBAR_CCUSAGE_SOURCE is not a file: $source" >&2
return 1
fi
verify_binary_arches "$source" "${ARCH_LIST[@]}"
local actual_sha256
actual_sha256=$(shasum -a 256 "$source" | awk '{print $1}')
local normalized_actual normalized_expected
normalized_actual=$(printf '%s' "$actual_sha256" | tr '[:upper:]' '[:lower:]')
normalized_expected=$(printf '%s' "$expected_sha256" | tr '[:upper:]' '[:lower:]')
if [[ "$normalized_actual" != "$normalized_expected" ]]; then
echo "ERROR: ccusage SHA-256 mismatch (expected: $expected_sha256, actual: $actual_sha256)" >&2
return 1
fi
cp "$source" "$destination"
chmod 755 "$destination"
printf 'version=%s\nsha256=%s\n' "$version" "$normalized_actual" >"$provenance"
}

install_binary() {
local name="$1"
local dest="$2"
Expand Down Expand Up @@ -509,6 +563,7 @@ strip_release_binary "$APP/Contents/Helpers/CodexBarCLI"
# Watchdog helper: ensures `claude` probes die when CodexBar crashes/gets killed.
install_binary "CodexBarClaudeWatchdog" "$APP/Contents/Helpers/CodexBarClaudeWatchdog"
strip_release_binary "$APP/Contents/Helpers/CodexBarClaudeWatchdog"
install_optional_ccusage_helper
install_widget_extension
strip_release_binary "$APP/Contents/PlugIns/CodexBarWidget.appex/Contents/MacOS/CodexBarWidget"

Expand Down Expand Up @@ -596,6 +651,9 @@ fi
if [[ -f "${APP}/Contents/Helpers/CodexBarClaudeWatchdog" ]]; then
codesign "${CODESIGN_ARGS[@]}" "${APP}/Contents/Helpers/CodexBarClaudeWatchdog"
fi
if [[ -f "${APP}/Contents/Helpers/ccusage" ]]; then
codesign "${CODESIGN_ARGS[@]}" "${APP}/Contents/Helpers/ccusage"
fi

# Sign widget extension if present
if [[ -d "${APP}/Contents/PlugIns/CodexBarWidget.appex" ]]; then
Expand Down
89 changes: 89 additions & 0 deletions Scripts/test_package_ccusage_helper.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
#!/usr/bin/env bash
set -euo pipefail

ROOT=$(cd "$(dirname "$0")/.." && pwd)
PACKAGE_SCRIPT="$ROOT/Scripts/package_app.sh"
FUNCTIONS_FILE=$(mktemp "${TMPDIR:-/tmp}/codexbar-package-ccusage-functions.XXXXXX")
trap 'rm -f "$FUNCTIONS_FILE"' EXIT

python3 - "$PACKAGE_SCRIPT" "$FUNCTIONS_FILE" <<'PY'
import sys
from pathlib import Path

script = Path(sys.argv[1]).read_text()
functions = []
for name in ("verify_binary_arches", "install_optional_ccusage_helper"):
start = script.index(f"{name}() {{")
end = script.index("\n}\n", start) + 3
functions.append(script[start:end])
Path(sys.argv[2]).write_text("\n\n".join(functions))
PY

source "$FUNCTIONS_FILE"

TEMP_DIR=$(mktemp -d "${TMPDIR:-/tmp}/codexbar-package-ccusage.XXXXXX")
trap 'rm -f "$FUNCTIONS_FILE"; rm -rf "$TEMP_DIR"' EXIT
APP="$TEMP_DIR/CodexBar.app"
SOURCE="$TEMP_DIR/ccusage"
mkdir -p "$APP/Contents/Helpers"
printf '#!/bin/sh\nexit 0\n' >"$SOURCE"
chmod 755 "$SOURCE"

lipo() {
printf 'arm64 x86_64\n'
}

ARCH_LIST=(arm64 x86_64)
CODEXBAR_CCUSAGE_SOURCE="$SOURCE"
CODEXBAR_REQUIRE_CCUSAGE=1
CODEXBAR_CCUSAGE_VERSION="20.0.19"
CODEXBAR_CCUSAGE_SHA256="$(shasum -a 256 "$SOURCE" | awk '{print $1}')"
install_optional_ccusage_helper
[[ -x "$APP/Contents/Helpers/ccusage" ]]
cmp -s "$SOURCE" "$APP/Contents/Helpers/ccusage"
grep -Fq "version=20.0.19" "$APP/Contents/Helpers/ccusage.provenance"
grep -Fq "sha256=$CODEXBAR_CCUSAGE_SHA256" "$APP/Contents/Helpers/ccusage.provenance"

rm -f "$APP/Contents/Helpers/ccusage" "$APP/Contents/Helpers/ccusage.provenance"
unset CODEXBAR_CCUSAGE_SOURCE
CODEXBAR_REQUIRE_CCUSAGE=0
install_optional_ccusage_helper
[[ ! -e "$APP/Contents/Helpers/ccusage" ]]
[[ ! -e "$APP/Contents/Helpers/ccusage.provenance" ]]

CODEXBAR_REQUIRE_CCUSAGE=1
if install_optional_ccusage_helper 2>"$TEMP_DIR/missing-source.log"; then
echo "Missing required ccusage source unexpectedly succeeded" >&2
exit 1
fi
grep -Fq "requires CODEXBAR_CCUSAGE_SOURCE" "$TEMP_DIR/missing-source.log"

CODEXBAR_CCUSAGE_SOURCE="$TEMP_DIR/missing"
CODEXBAR_REQUIRE_CCUSAGE=0
if install_optional_ccusage_helper 2>"$TEMP_DIR/missing-file.log"; then
echo "Missing ccusage file unexpectedly succeeded" >&2
exit 1
fi
grep -Fq "is not a file" "$TEMP_DIR/missing-file.log"

CODEXBAR_CCUSAGE_SOURCE="$SOURCE"
CODEXBAR_CCUSAGE_SHA256="$(printf '0%.0s' {1..64})"
if install_optional_ccusage_helper 2>"$TEMP_DIR/hash.log"; then
echo "SHA-256 mismatch unexpectedly succeeded" >&2
exit 1
fi
grep -Fq "SHA-256 mismatch" "$TEMP_DIR/hash.log"

CODEXBAR_CCUSAGE_SOURCE="$SOURCE"
CODEXBAR_REQUIRE_CCUSAGE=0
CODEXBAR_CCUSAGE_SHA256="$(shasum -a 256 "$SOURCE" | awk '{print $1}')"
lipo() {
printf 'arm64\n'
}
if (install_optional_ccusage_helper) 2>"$TEMP_DIR/arch.log"; then
echo "Architecture mismatch unexpectedly succeeded" >&2
exit 1
fi
grep -Fq "arch mismatch" "$TEMP_DIR/arch.log"

echo "Package ccusage helper tests passed."
1 change: 1 addition & 0 deletions Sources/CodexBar/SpendDashboardController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -483,6 +483,7 @@ enum SpendDashboardSource {
encoder.append(snapshot.currencyCode)
encoder.append(snapshot.historyDays)
encoder.append(snapshot.historyCoverageIsEstablished)
encoder.append(snapshot.historyFallbackCoverageIsEstablished)
encoder.append(snapshot.updatedAt.timeIntervalSinceReferenceDate)
encoder.append(snapshot.last30DaysTokens)
encoder.append(snapshot.last30DaysCostUSD)
Expand Down
4 changes: 3 additions & 1 deletion Sources/CodexBar/SpendDashboardModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -568,7 +568,9 @@ struct SpendDashboardModel: Equatable, Sendable {
bounds: ClosedRange<Date>,
displayCalendar: Calendar) -> ClosedRange<Date>?
{
guard input.snapshot.historyCoverageIsEstablished else { return nil }
guard input.snapshot.historyCoverageIsEstablished
|| input.snapshot.historyFallbackCoverageIsEstablished
else { return nil }
let sourceCoverage = Self.sourceCoverageInterval(input: input, displayCalendar: displayCalendar)
let overlapStart = max(bounds.lowerBound, sourceCoverage.lowerBound)
let overlapEnd = min(bounds.upperBound, sourceCoverage.upperBound)
Expand Down
4 changes: 4 additions & 0 deletions Sources/CodexBarCLI/CLICostCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,7 @@ extension CodexBarCLI {
sessionCostUSD: snapshot?.sessionCostUSD,
historyDays: snapshot?.historyDays,
historyCoverageIsEstablished: snapshot?.historyCoverageIsEstablished,
historyFallbackCoverageIsEstablished: snapshot?.historyFallbackCoverageIsEstablished,
last30DaysTokens: snapshot?.last30DaysTokens,
last30DaysCostUSD: snapshot?.last30DaysCostUSD,
meteredCostUSD: snapshot?.meteredCostUSD,
Expand Down Expand Up @@ -490,6 +491,7 @@ struct CostPayload: Encodable, Sendable {
let sessionCostUSD: Double?
let historyDays: Int?
let historyCoverageIsEstablished: Bool?
let historyFallbackCoverageIsEstablished: Bool?
let last30DaysTokens: Int?
let last30DaysCostUSD: Double?
let meteredCostUSD: Double?
Expand All @@ -507,6 +509,7 @@ struct CostPayload: Encodable, Sendable {
sessionCostUSD: Double?,
historyDays: Int?,
historyCoverageIsEstablished: Bool? = nil,
historyFallbackCoverageIsEstablished: Bool? = nil,
last30DaysTokens: Int?,
last30DaysCostUSD: Double?,
meteredCostUSD: Double? = nil,
Expand All @@ -523,6 +526,7 @@ struct CostPayload: Encodable, Sendable {
self.sessionCostUSD = sessionCostUSD
self.historyDays = historyDays
self.historyCoverageIsEstablished = historyCoverageIsEstablished
self.historyFallbackCoverageIsEstablished = historyFallbackCoverageIsEstablished
self.last30DaysTokens = last30DaysTokens
self.last30DaysCostUSD = last30DaysCostUSD
self.meteredCostUSD = meteredCostUSD
Expand Down
Loading