Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
96 commits
Select commit Hold shift + click to select a range
be3d1e0
feat(sources): add Codex usage support with multi-agent scopes
Jul 2, 2026
2daff16
docs(readme)+release: multi-agent positioning, own release/tap pipeli…
Jul 2, 2026
06bce3c
feat(updater): in-app update check, download, and install
Jul 2, 2026
4f1b045
Merge pull request #1 from SunChJ/feat/in-app-updater
SunChJ Jul 2, 2026
136409f
docs: stand on our own branding, move upstream credit to Acknowledgem…
Jul 2, 2026
be5fb54
Merge pull request #2 from SunChJ/docs/attribution
SunChJ Jul 2, 2026
ce3c2be
fix tray popover main-thread updates
Jul 2, 2026
684c227
improve release notes for packaged updates
Jul 2, 2026
3c24e61
Merge pull request #3 from SunChJ/fix/main-thread-tray-popover
SunChJ Jul 2, 2026
69ccad1
Merge upstream changes from HduSy/tokenscope
SunChJ Jul 10, 2026
519a6dd
feat(skills): track Codex skill usage
SunChJ Jul 10, 2026
bb6cb2d
feat(ui): filter total tokens by model
SunChJ Jul 10, 2026
37fc3de
fix(mcp): track Codex tool calls
SunChJ Jul 10, 2026
70288b7
fix: prevent input interruption and refresh pricing
SunChJ Jul 13, 2026
d5f6a0c
fix(ci): run release notes script with bash
SunChJ Jul 13, 2026
06bef99
fix(ci): omit empty updater signing keys
SunChJ Jul 13, 2026
9303716
fix(ci): build unsigned release artifacts
SunChJ Jul 13, 2026
fbad451
release: v1.3.0
SunChJ Jul 13, 2026
9a19ab3
release: v1.3.1
SunChJ Jul 13, 2026
7603a16
release: v1.3.2
SunChJ Jul 13, 2026
4fd5fe1
release: v1.3.3
SunChJ Jul 13, 2026
3d7ec42
release: v1.3.4
SunChJ Jul 13, 2026
6aaba72
release: v1.3.5
SunChJ Jul 14, 2026
b26be8b
feat: add project settlement exports
SunChJ Jul 14, 2026
fbd569c
feat: track failed and aborted work
SunChJ Jul 14, 2026
b7250eb
feat: add turn response performance
SunChJ Jul 14, 2026
0379d57
feat: add context and quota trends
SunChJ Jul 14, 2026
a7e943b
chore: keep Rust lint-clean
SunChJ Jul 14, 2026
df8263b
feat: add collapsible sections and localization
SunChJ Jul 14, 2026
5989ea8
release: v1.3.6
SunChJ Jul 14, 2026
e750145
feat: simplify dashboard status and quota
SunChJ Jul 14, 2026
92b5d01
release: v1.3.7
SunChJ Jul 14, 2026
dcfde4e
fix(ui): move Codex quota below model costs
SunChJ Jul 14, 2026
cd4b203
release: v1.3.8
SunChJ Jul 14, 2026
27da20e
fix(pricing): don't let a cacheless reseller shadow first-party prices
SunChJ Jul 27, 2026
b40963d
fix(store): drop the replayed parent history in a forked Codex thread
SunChJ Jul 27, 2026
0c944cf
feat: disable webview context menu in production
SunChJ Jul 27, 2026
26d6eba
feat(pricing): force-refresh prices via tray Refresh, ETag-conditiona…
SunChJ Jul 27, 2026
74e316f
docs: log the pricing and Codex replay bugs, and the replay caveat
SunChJ Jul 27, 2026
e6edfff
fix(store): assign Codex quota snapshots by active model, not limit_id
SunChJ Jul 27, 2026
df8e739
Merge pull request #4 from SunChJ/fix/usage-accounting-accuracy
SunChJ Jul 27, 2026
e87b6fc
release: v1.4.0
SunChJ Jul 27, 2026
8e9c080
Merge pull request #5 from SunChJ/release/v1.4.0
SunChJ Jul 27, 2026
8fd9b9b
docs: state the bug magnitudes as ratios, not measured usage
SunChJ Jul 27, 2026
a36e103
Merge pull request #6 from SunChJ/docs/scrub-usage-figures
SunChJ Jul 27, 2026
f31d8f6
fix(store): dedup Codex usage by turn id and position, not by file wi…
SunChJ Jul 29, 2026
4bb5787
release: v1.4.1
SunChJ Jul 29, 2026
0bb1534
Merge pull request #7 from SunChJ/fix/codex-replay-content-dedup
SunChJ Jul 29, 2026
2e71126
release: v1.4.2
SunChJ Jul 30, 2026
4978a9d
Merge pull request #8 from SunChJ/fix/codex-replay-content-dedup
SunChJ Jul 30, 2026
0e08823
fix(store): account Codex cumulative usage correctly
SunChJ Jul 30, 2026
b2cba34
Merge pull request #9 from SunChJ/fix/codex-replay-content-dedup
SunChJ Jul 30, 2026
d8513ad
fix: align model cache totals (v1.4.4)
SunChJ Aug 4, 2026
f80d482
Merge pull request #10 from SunChJ/codex/fix-model-cache-stats
SunChJ Aug 4, 2026
8be7f4d
fix: disambiguate tray quota pools (v1.4.5)
SunChJ Aug 4, 2026
421e8f7
Merge pull request #11 from SunChJ/codex/fix-tray-quota-pools
SunChJ Aug 4, 2026
9786d3b
fix: keep Codex and Spark quota pools separate (v1.4.6)
SunChJ Aug 4, 2026
418a4c8
Merge pull request #12 from SunChJ/codex/fix-quota-pool-routing
SunChJ Aug 4, 2026
7038b19
fix: hide providers without period usage (v1.4.7)
SunChJ Aug 4, 2026
02fa67a
Merge pull request #13 from SunChJ/codex/hide-inactive-provider-filters
SunChJ Aug 4, 2026
3506cd5
feat: show independent Spark weekly quota
SunChJ Aug 4, 2026
8c64974
Merge pull request #14 from SunChJ/codex/show-spark-weekly-quota
SunChJ Aug 4, 2026
a56a7ab
chore: bump version to 1.4.8
SunChJ Aug 4, 2026
8de2c2e
Merge pull request #15 from SunChJ/codex/bump-v1.4.8
SunChJ Aug 4, 2026
25efe94
feat: add first-class Pi session support
SunChJ Aug 6, 2026
47303f2
feat: source provider limits through HappyUsage CLI
SunChJ Aug 6, 2026
9818007
chore: bump version to 1.5.1
SunChJ Aug 6, 2026
eaa2369
Merge pull request #16 from SunChJ/codex/bump-v1.4.8
SunChJ Aug 6, 2026
2ca2234
feat: bundle happyusage CLI with the app
SunChJ Aug 6, 2026
f2028dd
Merge pull request #17 from SunChJ/codex/bump-v1.4.8
SunChJ Aug 7, 2026
c9699f9
docs: refresh README for 1.5.1 feature set
SunChJ Aug 7, 2026
8fddbc1
docs: refresh README screenshot for the 1.5.1 UI
SunChJ Aug 7, 2026
58fa9bc
Merge pull request #18 from SunChJ/codex/bump-v1.4.8
SunChJ Aug 7, 2026
0b7ae2a
chore: bump version to 1.5.2
SunChJ Aug 7, 2026
e0f53a3
Merge pull request #19 from SunChJ/codex/bump-v1.4.8
SunChJ Aug 7, 2026
3fd103e
ci: use supported tauri-action input name for updater json
SunChJ Aug 7, 2026
3d7111d
refactor: drop local quota computation, hu is the only source
SunChJ Aug 7, 2026
5812c90
chore: bump version to 1.5.3
SunChJ Aug 7, 2026
61792cc
Merge pull request #21 from SunChJ/codex/quota-hu-only
SunChJ Aug 7, 2026
488119e
fix: resolve hu version via releases redirect, not the rate-limited API
SunChJ Aug 7, 2026
715efd8
Merge pull request #22 from SunChJ/codex/quota-hu-only
SunChJ Aug 7, 2026
0365b30
ci: publish releases atomically via draft
SunChJ Aug 7, 2026
ea60e4b
chore: bump version to 1.5.4
SunChJ Aug 7, 2026
7a416d6
Merge pull request #24 from SunChJ/codex/bump-v1.5.4
SunChJ Aug 7, 2026
6cfc29d
Merge pull request #20 from SunChJ/codex/bump-v1.4.8
SunChJ Aug 7, 2026
0bda519
Merge pull request #23 from SunChJ/codex/quota-hu-only
SunChJ Aug 7, 2026
b31293a
ci: pre-create draft release to avoid the create-release race
SunChJ Aug 7, 2026
76689c0
Merge pull request #25 from SunChJ/codex/fix-release-race
SunChJ Aug 7, 2026
74765e4
fix: package architecture-matched hu on macOS
Aug 7, 2026
fb69267
Merge pull request #26 from SunChJ/codex/fix-hu-arch-macos-only
SunChJ Aug 7, 2026
fd2e4b6
fix(ci): pass release tag to draft creation
Aug 7, 2026
a4c4457
Merge pull request #27 from SunChJ/codex/fix-release-tag-env
SunChJ Aug 7, 2026
7076fd4
release: prepare v1.5.6
Aug 7, 2026
253b11c
Merge pull request #28 from SunChJ/codex/bump-v1.5.6
SunChJ Aug 7, 2026
f79c647
feat: show period-over-period growth as a ratio and per-agent tokens …
SunChJ Aug 10, 2026
49214fe
release: prepare v1.5.7
SunChJ Aug 10, 2026
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
299 changes: 208 additions & 91 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,33 +1,133 @@
name: Release

# Push a version tag (e.g. v0.1.0) to build the app and publish a GitHub Release.
# A version bump on main creates its tag and publishes automatically. Pushing a
# matching v* tag remains supported for manual recovery/re-runs.
on:
push:
branches:
- main
tags:
- "v*"
paths:
- package.json
- src-tauri/Cargo.toml
- src-tauri/tauri.conf.json
workflow_dispatch:
inputs:
tag:
description: Existing version tag to publish, for example v1.3.0
required: true
type: string

concurrency:
group: release-${{ github.ref }}
cancel-in-progress: false

jobs:
prepare:
runs-on: ubuntu-latest
permissions:
contents: write
outputs:
publish: ${{ steps.version.outputs.publish }}
tag: ${{ steps.version.outputs.tag }}
version: ${{ steps.version.outputs.version }}
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0

- name: Validate version and prepare tag
id: version
shell: bash
run: |
set -euo pipefail
VERSION=$(node scripts/version.mjs)
TAG="v${VERSION}"

if [[ "${GITHUB_REF}" == refs/tags/* ]]; then
if [ "${GITHUB_REF_NAME}" != "$TAG" ]; then
echo "::error::Tag ${GITHUB_REF_NAME} does not match app version ${VERSION}"
exit 1
fi
elif [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
if [ "${{ inputs.tag }}" != "$TAG" ]; then
echo "::error::Requested tag ${{ inputs.tag }} does not match app version ${VERSION}"
exit 1
fi
if ! git rev-parse "$TAG" >/dev/null 2>&1; then
echo "::error::Tag $TAG does not exist"
exit 1
fi
else
BEFORE="${{ github.event.before }}"
PREVIOUS_PACKAGE=$(git show "${BEFORE}:package.json" 2>/dev/null || true)
PREVIOUS_VERSION=""
if [ -n "$PREVIOUS_PACKAGE" ]; then
PREVIOUS_VERSION=$(printf '%s' "$PREVIOUS_PACKAGE" | node -e \
'let s=""; process.stdin.on("data",d=>s+=d).on("end",()=>process.stdout.write(JSON.parse(s).version))')
fi
if [ "$VERSION" = "$PREVIOUS_VERSION" ]; then
echo "Version unchanged at $VERSION; nothing to publish."
echo "publish=false" >> "$GITHUB_OUTPUT"
exit 0
fi
if git ls-remote --exit-code --tags origin "refs/tags/$TAG" >/dev/null 2>&1; then
echo "::error::Tag $TAG already exists; choose a new version"
exit 1
fi
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git tag -a "$TAG" -m "Tokenscope $TAG"
git push origin "$TAG"
fi

echo "publish=true" >> "$GITHUB_OUTPUT"
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
echo "version=$VERSION" >> "$GITHUB_OUTPUT"

# Create the release once, here, before the platform jobs run. Two
# concurrent "create release" calls for the same tag race (one gets a
# 403 "Resource not accessible by integration"); pre-creating a draft
# lets every platform job find it and upload its assets, and the
# publish job flips it public only after all of them succeeded.
- name: Create draft release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG: ${{ steps.version.outputs.tag }}
run: |
if ! gh release view "$TAG" >/dev/null 2>&1; then
gh release create "$TAG" \
--draft \
--title "Tokenscope $TAG" \
--notes "Work in progress — assets are uploaded by the platform build jobs; the release becomes public once every platform succeeds." \
--repo "${{ github.repository }}"
fi

release:
# Only build/publish for version tags. workflow_dispatch from a branch would
# otherwise set the version/tag to the branch name and publish a malformed
# release (and fail the Homebrew step on the resulting 404).
if: startsWith(github.ref, 'refs/tags/v')
needs: prepare
if: needs.prepare.outputs.publish == 'true'
permissions:
contents: write
strategy:
# One platform's failure must not cancel the other — a broken Windows
# bundler shouldn't block the macOS .dmg from publishing (and vice versa).
# One platform's failure must not cancel the other.
fail-fast: false
matrix:
include:
# Build one app per CPU architecture so each package contains the
# matching single-architecture HappyUsage binary.
- os: macos-latest
target: aarch64-apple-darwin
rust-targets: aarch64-apple-darwin
- os: macos-latest
target: universal-apple-darwin
rust-targets: aarch64-apple-darwin,x86_64-apple-darwin
- os: windows-latest
target: x86_64-pc-windows-msvc
rust-targets: x86_64-pc-windows-msvc
target: x86_64-apple-darwin
rust-targets: x86_64-apple-darwin
runs-on: ${{ matrix.os }}
env:
RELEASE_TAG: ${{ needs.prepare.outputs.tag }}
RELEASE_VERSION: ${{ needs.prepare.outputs.version }}
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
steps:
- uses: actions/checkout@v6

Expand All @@ -53,107 +153,124 @@ jobs:
workspaces: "./src-tauri -> target"

- name: Install frontend deps
run: pnpm install
run: pnpm install --frozen-lockfile

- name: Validate updater signing
shell: bash
run: |
if [ -z "$TAURI_SIGNING_PRIVATE_KEY" ]; then
echo "::error::TAURI_SIGNING_PRIVATE_KEY is required for updater artifacts"
exit 1
fi

- name: Build & publish release
- name: Prepare release notes
id: release_notes
shell: bash
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gen=$(gh api --method POST \
"repos/${{ github.repository }}/releases/generate-notes" \
-f tag_name="$RELEASE_TAG" --jq '.body' || true)

{
cat <<'BODY'
Menu-bar / system-tray dashboard for local AI coding agent token usage (Claude Code + Codex).

## Install

**macOS**: download `Tokenscope_*_aarch64.dmg` on Apple Silicon or `Tokenscope_*_x64.dmg` on Intel. Unsigned — right-click → **Open** on first launch, or install via `brew install --cask sunchj/tokenscope/tokenscope`.

BODY
if [ -n "$gen" ]; then
printf '\n%s\n' "$gen"
else
printf '\n## What changed\n\n- Changes included since the previous release.\n'
fi
} > release-body.md

{
echo "body<<RELEASE_BODY"
cat release-body.md
echo "RELEASE_BODY"
} >> "$GITHUB_OUTPUT"

- name: Build and upload release (draft)
uses: tauri-apps/tauri-action@v0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# --- Code signing + notarization (optional, macOS only) ---
# UNSIGNED BUILD BY DEFAULT. Do NOT uncomment these until the matching
# secrets actually exist: tauri's bundler treats an empty
# APPLE_CERTIFICATE env var as "a certificate is present" and then
# fails on `security import` ("SecKeychainItemImport ... not valid").
#
# Once you have an Apple Developer ID, add these in repo
# Settings → Secrets and uncomment to get signed + notarized builds:
#
# APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }} # base64 of Developer ID .p12
# APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} # .p12 export password
# APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }} # "Developer ID Application: Name (TEAMID)"
# APPLE_ID: ${{ secrets.APPLE_ID }} # Apple ID email
# APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }} # app-specific password
# APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} # 10-char Team ID
# Windows builds are also unsigned — users get a SmartScreen warning
# on first launch, see README "Install on Windows".
with:
tagName: ${{ github.ref_name }}
releaseName: "Tokenscope ${{ github.ref_name }}"
releaseBody: |
Menu-bar / system-tray dashboard for Claude CLI token usage.

**macOS**: download `Tokenscope_*_universal.dmg` (Apple Silicon + Intel). Unsigned — right-click → **Open** on first launch, or install via `brew install --cask hdusy/tokenscope/tokenscope`.

**Windows**: download `Tokenscope_*_x64-setup.exe`. Unsigned — on first launch click **More info → Run anyway** to dismiss SmartScreen.
# Publish immediately (not a draft): the asset's public download URL
# must be live for the Homebrew Cask step below to fetch it and for
# `brew install` to work.
releaseDraft: false
tagName: ${{ env.RELEASE_TAG }}
releaseName: "Tokenscope ${{ env.RELEASE_TAG }}"
releaseBody: ${{ steps.release_notes.outputs.body }}
# Draft until every platform has uploaded: a failed job must not
# leave a partial public release with only one macOS architecture.
releaseDraft: true
prerelease: false
args: --target ${{ matrix.target }}

# Append an auto-generated changelog (PRs/commits since the previous tag)
# to the release notes. Best-effort: never fail the release over notes.
# Only run once (on the macOS leg) — both jobs publish to the same release.
- name: Add changelog to release
if: matrix.os == 'macos-latest'
continue-on-error: true
includeUpdaterJson: true
args: --target ${{ matrix.target }} --config src-tauri/tauri.release.conf.json

publish:
needs: [prepare, release]
if: needs.prepare.outputs.publish == 'true'
runs-on: ubuntu-latest
permissions:
contents: write
env:
RELEASE_TAG: ${{ needs.prepare.outputs.tag }}
RELEASE_VERSION: ${{ needs.prepare.outputs.version }}
steps:
- uses: actions/checkout@v6

# Every platform uploaded its draft assets; flip the release public.
- name: Publish release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gen=$(gh api --method POST \
"repos/${{ github.repository }}/releases/generate-notes" \
-f tag_name="${{ github.ref_name }}" --jq '.body' || true)
cur=$(gh release view "${{ github.ref_name }}" --json body --jq '.body' || true)
{
printf '%s\n' "$cur"
if [ -n "$gen" ]; then printf '\n%s\n' "$gen"; fi
} > release-body.md
gh release edit "${{ github.ref_name }}" --notes-file release-body.md
gh release edit "$RELEASE_TAG" --draft=false
echo "Published $RELEASE_TAG"

- name: Update Homebrew Cask
if: matrix.os == 'macos-latest'
env:
HOMEBREW_TAP_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }}
run: |
VERSION="${GITHUB_REF_NAME#v}"
DMG_NAME="Tokenscope_${VERSION}_universal.dmg"
DOWNLOAD_URL="https://github.com/HduSy/tokenscope/releases/download/${GITHUB_REF_NAME}/${DMG_NAME}"

# Wait for the release asset to be available, then compute sha256.
# -f makes curl fail (non-zero) on a 404 so we keep retrying instead
# of hashing GitHub's error page and writing a bogus checksum.
SHA256=""
for i in $(seq 1 10); do
if curl -fsSL "$DOWNLOAD_URL" -o /tmp/tokenscope.dmg; then
SHA256=$(shasum -a 256 /tmp/tokenscope.dmg | cut -d' ' -f1)
break
fi
echo "Retry $i: asset not ready yet..."
sleep 10
done
if [ -z "$SHA256" ]; then
echo "::error::Release asset $DMG_NAME never became available"; exit 1
if [ -z "$HOMEBREW_TAP_TOKEN" ]; then
echo "HOMEBREW_TAP_TOKEN not set; skipping tap update."
exit 0
fi
BASE_URL="https://github.com/${{ github.repository }}/releases/download/${RELEASE_TAG}"
fetch_sha() {
arch="$1"
name="Tokenscope_${RELEASE_VERSION}_${arch}.dmg"
for i in $(seq 1 10); do
if curl -fsSL "${BASE_URL}/${name}" -o "/tmp/tokenscope-${arch}.dmg"; then
shasum -a 256 "/tmp/tokenscope-${arch}.dmg" | cut -d' ' -f1
return 0
fi
echo "Retry $i: $name not ready yet..." >&2
sleep 10
done
echo "::error::Release asset $name never became available" >&2
return 1
}
SHA_ARM="$(fetch_sha aarch64)"
SHA_INTEL="$(fetch_sha x64)"

# Clone the tap repo and update the cask
git clone https://x-access-token:${HOMEBREW_TAP_TOKEN}@github.com/HduSy/homebrew-tokenscope.git /tmp/homebrew-tap
git clone https://x-access-token:${HOMEBREW_TAP_TOKEN}@github.com/SunChJ/homebrew-tokenscope.git /tmp/homebrew-tap
cat > /tmp/homebrew-tap/Casks/tokenscope.rb <<CASK
cask "tokenscope" do
version "${VERSION}"
sha256 "${SHA256}"
version "${RELEASE_VERSION}"
arch arm: "aarch64", intel: "x64"
sha256 arm: "${SHA_ARM}", intel: "${SHA_INTEL}"

url "${DOWNLOAD_URL}"
url "${BASE_URL}/Tokenscope_#{version}_#{arch}.dmg"
name "Tokenscope"
desc "macOS menu-bar dashboard for Claude CLI token usage"
homepage "https://github.com/HduSy/tokenscope"

depends_on macos: ">= :catalina"
desc "Menu-bar dashboard for local AI coding agent token usage (Claude Code, Codex)"
homepage "https://github.com/${{ github.repository }}"

depends_on macos: :catalina
app "Tokenscope.app"

# Unsigned/unnotarized build: strip the quarantine flag Homebrew
# adds so the app opens without the "Apple cannot verify" prompt.
postflight do
system_command "/usr/bin/xattr",
args: ["-cr", "#{appdir}/Tokenscope.app"],
Expand All @@ -166,5 +283,5 @@ jobs:
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add Casks/tokenscope.rb
git diff --cached --quiet || git commit -m "Update cask to v${VERSION}"
git diff --cached --quiet || git commit -m "Update cask to ${RELEASE_TAG}"
git push
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,5 @@ public/dev-dashboard.json
.history/
*.log
claude-design-files/

src-tauri/bin/hu
Loading