Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 495a254

Browse files
committed
feat(mac): native Swift menubar app + one-command install
Introduces mac/ with a native SwiftUI menubar app that replaces the previous SwiftBar plugin entirely. Install via `npx codeburn menubar`, which downloads the .app from GitHub Releases, strips Gatekeeper quarantine, and drops it into ~/Applications. Highlights - mac/ SwiftUI app: agent tabs, Today/7/30/Month/All period switcher, Trend/Forecast/Pulse/Stats/Plan insights, activity + model breakdowns, optimize findings, CSV/JSON export, Star-on-GitHub banner, live 60s refresh, instant currency switching with offline FX cache. - Security: CodeburnCLI argv-based spawn (no shell interpretation), SafeFile symlink guards + O_NOFOLLOW writes, FX rate clamping to [0.0001, 1_000_000], keychain filtered to account == "default", removed byte-window credential log, in-flight refresh guard, POSIX flock on config.json writes, TerminalLauncher validates argv before AppleScript interpolation. - Performance: shared static NumberFormatter (thousands of allocations per popover redraw eliminated), concurrent pipe drain with 20 MB cap + 60s timeout in DataClient, Observation-tracked reactive UI, 5-min payload cache keyed on (period, provider). - CLI: new `codeburn menubar` subcommand that downloads + installs + launches the .app (no clone, no build). New `status --format menubar-json` payload builder. `export` rewritten to produce a folder of one-table-per-file CSVs with a `.codeburn-export` marker so arbitrary -o paths cannot be silently deleted. - Removed: src/menubar.ts (SwiftBar plugin generator), install-menubar / uninstall-menubar subcommands, `status --format menubar` directive output, tests/menubar.test.ts, tests/security/menubar-injection.test.ts. - Release: .github/workflows/release-menubar.yml builds universal binary, assembles .app, ad-hoc signs, zips, uploads on mac-v* tag push. Runs on the free macos-latest runner. Tests - 230 TypeScript tests pass - 10 Swift CapacityEstimator tests pass - TypeScript typecheck clean - Swift release build clean
1 parent 69268a9 commit 495a254

46 files changed

Lines changed: 6430 additions & 572 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
name: Release macOS Menubar
2+
3+
# Triggers on a `mac-v*` tag push (e.g. `git tag mac-v0.8.0 && git push origin mac-v0.8.0`),
4+
# or manually via the Actions tab. Runs entirely on the free macos-latest runner -- no Apple
5+
# Developer Program membership, no signing certificates, no secrets required. The bundle ships
6+
# ad-hoc signed; `npx codeburn menubar` strips the download quarantine flag on install so
7+
# Gatekeeper stays quiet.
8+
on:
9+
push:
10+
tags:
11+
- 'mac-v*'
12+
workflow_dispatch:
13+
inputs:
14+
version:
15+
description: 'Version label for the bundle (e.g. v0.8.0 or dev-preview)'
16+
required: true
17+
default: 'dev-preview'
18+
19+
permissions:
20+
contents: write # Needed to create the release + upload assets.
21+
22+
jobs:
23+
build:
24+
runs-on: macos-latest
25+
steps:
26+
- name: Checkout
27+
uses: actions/checkout@v4
28+
29+
- name: Resolve version label
30+
id: version
31+
run: |
32+
if [[ "${GITHUB_REF}" == refs/tags/mac-v* ]]; then
33+
echo "value=${GITHUB_REF#refs/tags/mac-}" >> "$GITHUB_OUTPUT"
34+
else
35+
echo "value=${{ github.event.inputs.version }}" >> "$GITHUB_OUTPUT"
36+
fi
37+
38+
- name: Show Swift toolchain
39+
run: swift --version
40+
41+
- name: Build + bundle + zip
42+
run: mac/Scripts/package-app.sh "${{ steps.version.outputs.value }}"
43+
44+
- name: Upload artifact (for manual runs)
45+
if: github.event_name == 'workflow_dispatch'
46+
uses: actions/upload-artifact@v4
47+
with:
48+
name: CodeBurnMenubar-${{ steps.version.outputs.value }}
49+
path: mac/.build/dist/CodeBurnMenubar-*.zip
50+
if-no-files-found: error
51+
52+
- name: Create / update GitHub Release
53+
if: startsWith(github.ref, 'refs/tags/mac-v')
54+
uses: softprops/action-gh-release@v2
55+
with:
56+
tag_name: ${{ github.ref_name }}
57+
name: Menubar ${{ steps.version.outputs.value }}
58+
body: |
59+
Install with:
60+
61+
```
62+
npx codeburn menubar
63+
```
64+
65+
Unsigned build. The installer clears Gatekeeper quarantine on download, so the
66+
app launches without warnings. Direct-download users from this page may see
67+
"cannot verify developer" -- right-click → Open once to dismiss it, or use the
68+
npx command above.
69+
files: mac/.build/dist/CodeBurnMenubar-*.zip
70+
fail_on_unmatched_files: true

README.md

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
<img src="https://raw.githubusercontent.com/AgentSeal/codeburn/main/assets/dashboard.jpg" alt="CodeBurn TUI dashboard" width="620" />
2020
</p>
2121

22-
By task type, tool, model, MCP server, and project. Supports **Claude Code**, **Codex** (OpenAI), **Cursor**, **OpenCode**, **Pi**, and **GitHub Copilot** with a provider plugin system. Tracks one-shot success rate per activity type so you can see where the AI nails it first try vs. burns tokens on edit/test/fix retries. Interactive TUI dashboard with gradient charts, responsive panels, and keyboard navigation. macOS menu bar widget via SwiftBar. CSV/JSON export.
22+
By task type, tool, model, MCP server, and project. Supports **Claude Code**, **Codex** (OpenAI), **Cursor**, **OpenCode**, **Pi**, and **GitHub Copilot** with a provider plugin system. Tracks one-shot success rate per activity type so you can see where the AI nails it first try vs. burns tokens on edit/test/fix retries. Interactive TUI dashboard with gradient charts, responsive panels, and keyboard navigation. Native macOS menubar app in `mac/`. CSV/JSON export.
2323

2424
Works by reading session data directly from disk. No wrapper, no proxy, no API keys. Pricing from LiteLLM (auto-cached, all models supported).
2525

@@ -156,14 +156,13 @@ The menu bar widget includes a currency picker with 17 common currencies. For an
156156

157157
## Menu Bar
158158

159-
<img src="https://cdn.jsdelivr.net/gh/AgentSeal/codeburn@main/assets/menubar.png" alt="CodeBurn SwiftBar menu bar widget" width="260" />
159+
<img src="https://cdn.jsdelivr.net/gh/AgentSeal/codeburn@main/assets/menubar.png" alt="CodeBurn macOS menubar app" width="420" />
160160

161161
```bash
162-
codeburn install-menubar # install SwiftBar/xbar plugin
163-
codeburn uninstall-menubar # remove it
162+
npx codeburn menubar
164163
```
165164

166-
Requires [SwiftBar](https://github.com/swiftbar/SwiftBar) (`brew install --cask swiftbar`). Shows today's cost in the menu bar with a flame icon. Dropdown shows activity breakdown, model costs, token stats, per-provider cost breakdown, and a currency picker. Refreshes every 5 minutes.
165+
One command: downloads the latest `.app`, installs into `~/Applications`, and launches it. Re-run with `--force` to reinstall. Native Swift + SwiftUI app lives in `mac/` (see `mac/README.md` for build details). Shows today's cost with a flame icon, opens a popover with agent tabs, period switcher (Today / 7 Days / 30 Days / Month / All), Trend / Forecast / Pulse / Stats / Plan insights, activity and model breakdowns, optimize findings, and CSV/JSON export. Refreshes live via FSEvents plus a 60-second poll.
167166

168167
## What it tracks
169168

@@ -269,7 +268,7 @@ src/
269268
classifier.ts 13-category task classifier
270269
types.ts Type definitions
271270
format.ts Text rendering (status bar)
272-
menubar.ts SwiftBar plugin generator
271+
menubar-json.ts Payload builder consumed by the native macOS menubar app in mac/
273272
export.ts CSV/JSON multi-period export
274273
config.ts Config file management (~/.config/codeburn/)
275274
currency.ts Currency conversion, exchange rates, Intl formatting

assets/menubar.png

2.81 KB
Loading

mac/.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
.build/
2+
.swiftpm/
3+
Package.resolved
4+
*.xcodeproj/
5+
*.xcworkspace/
6+
DerivedData/

mac/Package.swift

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// swift-tools-version: 6.0
2+
import PackageDescription
3+
4+
let package = Package(
5+
name: "CodeBurnMenubar",
6+
platforms: [
7+
.macOS(.v14)
8+
],
9+
products: [
10+
.executable(name: "CodeBurnMenubar", targets: ["CodeBurnMenubar"])
11+
],
12+
targets: [
13+
.executableTarget(
14+
name: "CodeBurnMenubar",
15+
path: "Sources/CodeBurnMenubar",
16+
resources: [
17+
.process("../../Resources")
18+
],
19+
swiftSettings: [
20+
.enableUpcomingFeature("StrictConcurrency")
21+
]
22+
),
23+
.testTarget(
24+
name: "CodeBurnMenubarTests",
25+
dependencies: ["CodeBurnMenubar"],
26+
path: "Tests/CodeBurnMenubarTests"
27+
)
28+
]
29+
)

mac/README.md

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
# CodeBurn Menubar (macOS)
2+
3+
Native Swift + SwiftUI menubar app. The codeburn menubar surface.
4+
5+
## Requirements
6+
7+
- macOS 14+ (Sonoma)
8+
- Swift 6.0+ toolchain (bundled with Xcode 16 or standalone)
9+
- `codeburn` CLI installed globally (`npm install -g codeburn`) or available at a path you pass via `CODEBURN_BIN`
10+
11+
## Install (end users)
12+
13+
One command:
14+
15+
```bash
16+
npx codeburn menubar
17+
```
18+
19+
That's it. The command downloads the latest signed `.app` from GitHub Releases, drops it into `~/Applications`, clears Gatekeeper quarantine, and launches it. Re-running it upgrades in place with `--force`, or just launches the existing copy otherwise.
20+
21+
If you already have the CLI installed globally (`npm install -g codeburn`), `codeburn menubar` works the same way.
22+
23+
### Build from source
24+
25+
For contributors running a local build instead of the packaged release:
26+
27+
```bash
28+
npm install -g codeburn # CLI the app shells out to for data
29+
git clone https://github.com/AgentSeal/codeburn.git
30+
cd codeburn/mac
31+
swift build -c release
32+
.build/release/CodeBurnMenubar # launch
33+
```
34+
35+
## Build & run (dev against a local CLI checkout)
36+
37+
```bash
38+
cd mac
39+
swift build
40+
# Point the app at your dev CLI build instead of the globally installed `codeburn`:
41+
npm --prefix .. run build
42+
CODEBURN_BIN="node $(pwd)/../dist/cli.js" swift run
43+
```
44+
45+
The app registers itself as a menubar accessory (`LSUIElement = true` at runtime). No Dock icon.
46+
47+
## Data source
48+
49+
On launch and every 60 seconds thereafter, the app spawns `codeburn status --format menubar-json --no-optimize` directly (argv, no shell) via `CodeburnCLI.makeProcess` and decodes the JSON into `MenubarPayload`. The manual refresh button in the footer invokes the same command without `--no-optimize`, which includes optimize findings but takes longer.
50+
51+
Override the binary via the `CODEBURN_BIN` environment variable (default: `codeburn` on PATH). The value is validated against a strict allowlist (alphanumerics plus `._/-` space) before use, so a malicious env var can't inject shell commands.
52+
53+
## Project layout
54+
55+
```
56+
mac/
57+
├── Package.swift SwiftPM manifest
58+
├── Sources/CodeBurnMenubar/
59+
│ ├── CodeBurnApp.swift @main + MenuBarExtra scene
60+
│ ├── AppStore.swift @Observable store + enums
61+
│ ├── Data/MenubarPayload.swift Codable payload types + placeholder
62+
│ ├── Theme/Theme.swift Design tokens (warm terracotta palette)
63+
│ └── Views/MenuBarContent.swift Popover layout + footer action bar
64+
└── README.md This file
65+
```
66+
67+
## Status
68+
69+
Live data wired. Next iterations:
70+
71+
1. FSEvents watch for `~/.claude/projects/` changes (debounced refresh on real edits)
72+
2. Persistent disk cache for optimize findings so the default refresh can include them without the 30-second penalty
73+
3. Currency metadata in the JSON payload + Swift-side formatting
74+
4. Sparkle auto-update
75+
5. DMG packaging + Homebrew Cask tap
76+
77+
## Design tokens
78+
79+
Sourced from `~/codeburn-menubar-mac-swiftui.html`. Warm terracotta-ember palette:
80+
81+
- Accent (light): `#C9521D`
82+
- Accent (dark): `#E8774A`
83+
- Ember deep: `#8B3E13`
84+
- Ember glow: `#F0A070`
85+
- Surface (light): `#FAF7F3`
86+
- Surface (dark): `#1C1816`
87+
88+
SF Mono for currency values; SF Pro Rounded for hero.

mac/Scripts/package-app.sh

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
#!/usr/bin/env bash
2+
# Builds a universal CodeBurnMenubar.app bundle from the SwiftPM target and drops a
3+
# distributable zip alongside. Used by the GitHub release workflow; also runnable locally.
4+
#
5+
# Usage:
6+
# mac/Scripts/package-app.sh [<version>]
7+
# Defaults to `dev` if no version is given.
8+
9+
set -euo pipefail
10+
11+
VERSION="${1:-dev}"
12+
BUNDLE_NAME="CodeBurnMenubar.app"
13+
BUNDLE_ID="org.agentseal.codeburn-menubar"
14+
EXECUTABLE_NAME="CodeBurnMenubar"
15+
MIN_MACOS="14.0"
16+
17+
repo_root() {
18+
git rev-parse --show-toplevel 2>/dev/null || (cd "$(dirname "$0")/../.." && pwd)
19+
}
20+
21+
ROOT=$(repo_root)
22+
MAC_DIR="${ROOT}/mac"
23+
DIST_DIR="${MAC_DIR}/.build/dist"
24+
25+
cd "${MAC_DIR}"
26+
27+
echo "▸ Cleaning previous dist..."
28+
rm -rf "${DIST_DIR}"
29+
mkdir -p "${DIST_DIR}"
30+
31+
echo "▸ Building universal binary (arm64 + x86_64)..."
32+
swift build -c release --arch arm64 --arch x86_64
33+
34+
BIN_PATH=$(swift build -c release --arch arm64 --arch x86_64 --show-bin-path)
35+
BUILT_BINARY="${BIN_PATH}/${EXECUTABLE_NAME}"
36+
if [[ ! -x "${BUILT_BINARY}" ]]; then
37+
echo "Binary not found at ${BUILT_BINARY}" >&2
38+
exit 1
39+
fi
40+
41+
echo "▸ Assembling ${BUNDLE_NAME}..."
42+
BUNDLE="${DIST_DIR}/${BUNDLE_NAME}"
43+
mkdir -p "${BUNDLE}/Contents/MacOS"
44+
mkdir -p "${BUNDLE}/Contents/Resources"
45+
cp "${BUILT_BINARY}" "${BUNDLE}/Contents/MacOS/${EXECUTABLE_NAME}"
46+
47+
cat > "${BUNDLE}/Contents/Info.plist" <<PLIST
48+
<?xml version="1.0" encoding="UTF-8"?>
49+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
50+
<plist version="1.0">
51+
<dict>
52+
<key>CFBundleDevelopmentRegion</key>
53+
<string>en</string>
54+
<key>CFBundleDisplayName</key>
55+
<string>CodeBurn Menubar</string>
56+
<key>CFBundleExecutable</key>
57+
<string>${EXECUTABLE_NAME}</string>
58+
<key>CFBundleIconFile</key>
59+
<string>AppIcon</string>
60+
<key>CFBundleIdentifier</key>
61+
<string>${BUNDLE_ID}</string>
62+
<key>CFBundleInfoDictionaryVersion</key>
63+
<string>6.0</string>
64+
<key>CFBundleName</key>
65+
<string>${EXECUTABLE_NAME}</string>
66+
<key>CFBundlePackageType</key>
67+
<string>APPL</string>
68+
<key>CFBundleShortVersionString</key>
69+
<string>${VERSION}</string>
70+
<key>CFBundleVersion</key>
71+
<string>${VERSION}</string>
72+
<key>LSMinimumSystemVersion</key>
73+
<string>${MIN_MACOS}</string>
74+
<key>LSUIElement</key>
75+
<true/>
76+
<key>NSHighResolutionCapable</key>
77+
<true/>
78+
<key>NSHumanReadableCopyright</key>
79+
<string>© AgentSeal</string>
80+
</dict>
81+
</plist>
82+
PLIST
83+
84+
cat > "${BUNDLE}/Contents/PkgInfo" <<'PKG'
85+
APPL????
86+
PKG
87+
88+
# Ad-hoc sign so macOS treats the bundle as internally consistent. This does NOT give us a
89+
# recognisable developer name in Finder (that needs the $99 Developer ID cert), but it
90+
# satisfies macOS's minimum bundle-validity checks on 14+ and prevents some Gatekeeper edge
91+
# cases on managed Macs.
92+
echo "▸ Ad-hoc signing..."
93+
codesign --force --sign - --timestamp=none --deep "${BUNDLE}" 2>/dev/null || true
94+
codesign --verify --deep --strict "${BUNDLE}" 2>/dev/null || echo " (signature verify skipped)"
95+
96+
ZIP_NAME="CodeBurnMenubar-${VERSION}.zip"
97+
ZIP_PATH="${DIST_DIR}/${ZIP_NAME}"
98+
echo "▸ Packaging ${ZIP_NAME}..."
99+
(cd "${DIST_DIR}" && /usr/bin/ditto -c -k --keepParent "${BUNDLE_NAME}" "${ZIP_NAME}")
100+
101+
echo ""
102+
echo "✓ Built ${ZIP_PATH}"
103+
ls -la "${DIST_DIR}"

0 commit comments

Comments
 (0)