Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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