cc-menubar 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cc_menubar-0.2.0/.github/workflows/release.yml +91 -0
- cc_menubar-0.2.0/.github/workflows/test.yml +21 -0
- cc_menubar-0.2.0/.gitignore +8 -0
- cc_menubar-0.2.0/.release-please-manifest.json +3 -0
- cc_menubar-0.2.0/CHANGELOG.md +15 -0
- cc_menubar-0.2.0/CLAUDE.md +71 -0
- cc_menubar-0.2.0/CONTRIBUTING.md +58 -0
- cc_menubar-0.2.0/LICENSE +21 -0
- cc_menubar-0.2.0/PKG-INFO +156 -0
- cc_menubar-0.2.0/README.md +129 -0
- cc_menubar-0.2.0/demo/capture.sh +55 -0
- cc_menubar-0.2.0/pyproject.toml +53 -0
- cc_menubar-0.2.0/release-please-config.json +16 -0
- cc_menubar-0.2.0/src/cc_menubar/__init__.py +0 -0
- cc_menubar-0.2.0/src/cc_menubar/bash_utils.py +51 -0
- cc_menubar-0.2.0/src/cc_menubar/classifier.py +122 -0
- cc_menubar-0.2.0/src/cc_menubar/cli.py +299 -0
- cc_menubar-0.2.0/src/cc_menubar/collectors/__init__.py +0 -0
- cc_menubar-0.2.0/src/cc_menubar/collectors/blocks.py +51 -0
- cc_menubar-0.2.0/src/cc_menubar/collectors/cache.py +39 -0
- cc_menubar-0.2.0/src/cc_menubar/collectors/jsonl.py +178 -0
- cc_menubar-0.2.0/src/cc_menubar/collectors/quota.py +64 -0
- cc_menubar-0.2.0/src/cc_menubar/config.py +191 -0
- cc_menubar-0.2.0/src/cc_menubar/constants.py +120 -0
- cc_menubar-0.2.0/src/cc_menubar/defaults.toml +46 -0
- cc_menubar-0.2.0/src/cc_menubar/render.py +481 -0
- cc_menubar-0.2.0/tests/__init__.py +0 -0
- cc_menubar-0.2.0/tests/test_bash_utils.py +51 -0
- cc_menubar-0.2.0/tests/test_config.py +82 -0
- cc_menubar-0.2.0/tests/test_quota.py +50 -0
- cc_menubar-0.2.0/tests/test_render.py +125 -0
- cc_menubar-0.2.0/uv.lock +191 -0
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
name: release
|
|
2
|
+
on:
|
|
3
|
+
push:
|
|
4
|
+
branches: [main]
|
|
5
|
+
permissions:
|
|
6
|
+
contents: write
|
|
7
|
+
pull-requests: write
|
|
8
|
+
jobs:
|
|
9
|
+
release-please:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
outputs:
|
|
12
|
+
release_created: ${{ steps.release.outputs.release_created }}
|
|
13
|
+
tag_name: ${{ steps.release.outputs.tag_name }}
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/create-github-app-token@v2
|
|
16
|
+
id: app-token
|
|
17
|
+
with:
|
|
18
|
+
app-id: ${{ vars.RELEASE_PLEASE_APP_ID }}
|
|
19
|
+
private-key: ${{ secrets.RELEASE_PLEASE_PRIVATE_KEY }}
|
|
20
|
+
- id: release
|
|
21
|
+
uses: googleapis/release-please-action@v4
|
|
22
|
+
with:
|
|
23
|
+
token: ${{ steps.app-token.outputs.token }}
|
|
24
|
+
config-file: release-please-config.json
|
|
25
|
+
|
|
26
|
+
- name: Checkout release PR branch
|
|
27
|
+
if: ${{ steps.release.outputs.pr && !steps.release.outputs.release_created }}
|
|
28
|
+
uses: actions/checkout@v4
|
|
29
|
+
with:
|
|
30
|
+
token: ${{ steps.app-token.outputs.token }}
|
|
31
|
+
ref: release-please--branches--main
|
|
32
|
+
|
|
33
|
+
- name: Setup uv
|
|
34
|
+
if: ${{ steps.release.outputs.pr && !steps.release.outputs.release_created }}
|
|
35
|
+
uses: astral-sh/setup-uv@v5
|
|
36
|
+
|
|
37
|
+
- name: Update uv.lock in release PR
|
|
38
|
+
if: ${{ steps.release.outputs.pr && !steps.release.outputs.release_created }}
|
|
39
|
+
env:
|
|
40
|
+
GH_TOKEN: ${{ steps.app-token.outputs.token }}
|
|
41
|
+
APP_SLUG: ${{ steps.app-token.outputs.app-slug }}
|
|
42
|
+
run: |
|
|
43
|
+
uv lock
|
|
44
|
+
if git diff --quiet uv.lock; then
|
|
45
|
+
echo "uv.lock already up to date"
|
|
46
|
+
else
|
|
47
|
+
BOT_USER_ID=$(gh api "/users/${APP_SLUG}[bot]" --jq .id)
|
|
48
|
+
git config user.name "${APP_SLUG}[bot]"
|
|
49
|
+
git config user.email "${BOT_USER_ID}+${APP_SLUG}[bot]@users.noreply.github.com"
|
|
50
|
+
git add uv.lock
|
|
51
|
+
git commit -m "chore: update uv.lock"
|
|
52
|
+
git push
|
|
53
|
+
fi
|
|
54
|
+
|
|
55
|
+
bump-tap:
|
|
56
|
+
needs: release-please
|
|
57
|
+
if: ${{ needs.release-please.outputs.release_created == 'true' }}
|
|
58
|
+
runs-on: ubuntu-latest
|
|
59
|
+
steps:
|
|
60
|
+
- uses: actions/create-github-app-token@v2
|
|
61
|
+
id: app-token
|
|
62
|
+
with:
|
|
63
|
+
app-id: ${{ vars.RELEASE_PLEASE_APP_ID }}
|
|
64
|
+
private-key: ${{ secrets.RELEASE_PLEASE_PRIVATE_KEY }}
|
|
65
|
+
repositories: homebrew-tap
|
|
66
|
+
- name: Dispatch formula update
|
|
67
|
+
uses: peter-evans/repository-dispatch@v3
|
|
68
|
+
with:
|
|
69
|
+
token: ${{ steps.app-token.outputs.token }}
|
|
70
|
+
repository: calvindotsg/homebrew-tap
|
|
71
|
+
event-type: formula-update
|
|
72
|
+
client-payload: |-
|
|
73
|
+
{
|
|
74
|
+
"formula": "cc-menubar",
|
|
75
|
+
"version": "${{ needs.release-please.outputs.tag_name }}",
|
|
76
|
+
"url": "https://github.com/calvindotsg/cc-menubar/archive/refs/tags/${{ needs.release-please.outputs.tag_name }}.tar.gz",
|
|
77
|
+
"type": "python"
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
pypi-publish:
|
|
81
|
+
needs: release-please
|
|
82
|
+
if: ${{ needs.release-please.outputs.release_created == 'true' }}
|
|
83
|
+
runs-on: ubuntu-latest
|
|
84
|
+
environment: pypi
|
|
85
|
+
permissions:
|
|
86
|
+
id-token: write
|
|
87
|
+
steps:
|
|
88
|
+
- uses: actions/checkout@v4
|
|
89
|
+
- uses: astral-sh/setup-uv@v5
|
|
90
|
+
- run: uv build
|
|
91
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
name: test
|
|
2
|
+
on:
|
|
3
|
+
push:
|
|
4
|
+
branches: [main]
|
|
5
|
+
pull_request:
|
|
6
|
+
branches: [main]
|
|
7
|
+
jobs:
|
|
8
|
+
test:
|
|
9
|
+
runs-on: macos-latest
|
|
10
|
+
strategy:
|
|
11
|
+
matrix:
|
|
12
|
+
python-version: ["3.11", "3.12", "3.13"]
|
|
13
|
+
steps:
|
|
14
|
+
- uses: actions/checkout@v4
|
|
15
|
+
- uses: astral-sh/setup-uv@v5
|
|
16
|
+
with:
|
|
17
|
+
python-version: ${{ matrix.python-version }}
|
|
18
|
+
- run: uv lock --check
|
|
19
|
+
- run: uv sync
|
|
20
|
+
- run: uv run ruff check src/ tests/
|
|
21
|
+
- run: uv run pytest
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [0.2.0](https://github.com/calvindotsg/cc-menubar/compare/v0.1.0...v0.2.0) (2026-04-16)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* initial cc-menubar package ([f15018d](https://github.com/calvindotsg/cc-menubar/commit/f15018d521334bf0749c8369c08cf1c1ddded253))
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Bug Fixes
|
|
12
|
+
|
|
13
|
+
* extract bash commands from JSONL and fix Top Commands aggregation ([3c3a70f](https://github.com/calvindotsg/cc-menubar/commit/3c3a70f144ff865c7db2b11768cae9b1376cee41))
|
|
14
|
+
|
|
15
|
+
## Changelog
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
## Quick Commands
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
uv sync # Install dependencies
|
|
7
|
+
uv run ruff check src/ tests/ # Lint
|
|
8
|
+
uv run ruff format src/ tests/ # Format
|
|
9
|
+
uv run pytest # Test
|
|
10
|
+
uv run cc-menubar render # Test render output
|
|
11
|
+
uv run cc-menubar config # Show merged config
|
|
12
|
+
uv run cc-menubar install # Install SwiftBar wrapper
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Architecture
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
cc-menubar/
|
|
19
|
+
├── src/cc_menubar/
|
|
20
|
+
│ ├── cli.py # Typer CLI: render, install, uninstall, init, config
|
|
21
|
+
│ ├── config.py # 3-layer TOML config merge (defaults → user → env)
|
|
22
|
+
│ ├── defaults.toml # Bundled default config
|
|
23
|
+
│ ├── render.py # SwiftBar output formatter + Theme class
|
|
24
|
+
│ ├── constants.py # Categories, tool sets, theme presets
|
|
25
|
+
│ ├── classifier.py # Activity classifier (CodeBurn port)
|
|
26
|
+
│ ├── bash_utils.py # Shell command extraction (CodeBurn port)
|
|
27
|
+
│ └── collectors/
|
|
28
|
+
│ ├── quota.py # Read /tmp/claude-statusline-usage.json
|
|
29
|
+
│ ├── blocks.py # ccusage blocks --json subprocess
|
|
30
|
+
│ ├── jsonl.py # Single-pass JSONL parser
|
|
31
|
+
│ └── cache.py # Aggregate cache (300s TTL)
|
|
32
|
+
└── tests/
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Key Patterns
|
|
36
|
+
|
|
37
|
+
### TOML Config Merge
|
|
38
|
+
3 layers: bundled `defaults.toml` → user `~/.config/cc-menubar/config.toml` → `CC_MENUBAR_*` env vars. Deep merge at section level.
|
|
39
|
+
|
|
40
|
+
### SwiftBar Output DSL
|
|
41
|
+
- stdout = SwiftBar display, stderr = diagnostics
|
|
42
|
+
- `---` separates title from dropdown
|
|
43
|
+
- `sfimage=NAME` for SF Symbols, `sfvalue=0.0-1.0` for variable fill
|
|
44
|
+
- `color=#light,#dark` for automatic appearance switching
|
|
45
|
+
- `fold=true` for collapsible sections
|
|
46
|
+
- `dropdown=false` to hide cycling lines from dropdown
|
|
47
|
+
- `font=Menlo size=11` for monospace data rows
|
|
48
|
+
|
|
49
|
+
### Caching
|
|
50
|
+
- Quota: reads `/tmp/claude-statusline-usage.json` (written by statusline.py)
|
|
51
|
+
- Blocks: `ccusage blocks --json --active` subprocess with timeout
|
|
52
|
+
- JSONL: single-pass parse of `~/.claude/projects/*/**.jsonl`, mtime-filtered
|
|
53
|
+
- Aggregate cache: `/tmp/cc-menubar-cache.json`, configurable TTL
|
|
54
|
+
|
|
55
|
+
### Graceful Degradation
|
|
56
|
+
- Each collector returns `None` on failure; render skips that section
|
|
57
|
+
- Render catches all exceptions; fallback to static `-- | sfimage=gauge.with.needle.fill`
|
|
58
|
+
- `sfvalue` requires macOS 13.0+ — older versions get static icon
|
|
59
|
+
- `fold=true` degrades to expanded sections on older SwiftBar versions
|
|
60
|
+
|
|
61
|
+
## Constraints
|
|
62
|
+
|
|
63
|
+
- **Minimal PATH**: Wrapper sets `/opt/homebrew/bin:/usr/local/bin:$HOME/.local/bin:$PATH`
|
|
64
|
+
- **sfimage always monochrome**: SwiftBar forces `isTemplate = true` on all sfimage icons
|
|
65
|
+
- **ccusage optional**: blocks section gracefully skips if ccusage not installed
|
|
66
|
+
- **Quota read-only**: Only reads statusline.py cache, never calls OAuth directly
|
|
67
|
+
- **Typer sole dependency**: Rich is transitive via Typer
|
|
68
|
+
|
|
69
|
+
## Release Process
|
|
70
|
+
|
|
71
|
+
Conventional commits → release-please PR → PyPI (OIDC) → tap dispatch. Same flow as mac-upkeep.
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
## Development Setup
|
|
4
|
+
|
|
5
|
+
> **Note:** macOS is required for development and testing.
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
git clone https://github.com/calvindotsg/cc-menubar.git
|
|
9
|
+
cd cc-menubar
|
|
10
|
+
uv sync
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Code Style
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
uv run ruff check src/ tests/ # Lint
|
|
17
|
+
uv run ruff format src/ tests/ # Format
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Testing
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
uv run pytest # Run tests
|
|
24
|
+
uv run pytest --cov # With coverage
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Dependencies
|
|
28
|
+
|
|
29
|
+
After modifying `pyproject.toml` dependencies, run `uv lock` to update the lockfile. CI runs `uv lock --check` and will fail on stale lockfiles.
|
|
30
|
+
|
|
31
|
+
## Commit Conventions
|
|
32
|
+
|
|
33
|
+
This project uses [Conventional Commits v1.0.0](https://www.conventionalcommits.org/en/v1.0.0/).
|
|
34
|
+
|
|
35
|
+
| Type | When |
|
|
36
|
+
|------|------|
|
|
37
|
+
| `feat` | New CLI subcommand, dropdown section, or user-facing behavior |
|
|
38
|
+
| `fix` | Bug fix (render error, config parse issue, etc.) |
|
|
39
|
+
| `docs` | README, CLAUDE.md, CONTRIBUTING.md changes |
|
|
40
|
+
| `chore` | Dependencies, config files |
|
|
41
|
+
| `ci` | GitHub Actions workflows |
|
|
42
|
+
| `test` | Test additions or fixes |
|
|
43
|
+
| `refactor` | Code restructuring without behavior change |
|
|
44
|
+
|
|
45
|
+
Examples:
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
feat: add opusplan health dropdown section
|
|
49
|
+
fix: handle missing ccusage gracefully
|
|
50
|
+
docs: update README with config reference
|
|
51
|
+
chore: update typer to 0.13
|
|
52
|
+
ci: add macOS runner to test matrix
|
|
53
|
+
test: add test for quota parsing edge case
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Release Process
|
|
57
|
+
|
|
58
|
+
Automated via [release-please](https://github.com/googleapis/release-please). Use conventional commits — `feat:` and `fix:` trigger version bumps. See CLAUDE.md for full flow.
|
cc_menubar-0.2.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 calvindotsg
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: cc-menubar
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Pace your quota — proactive forecasting for Claude Code Max, not retroactive tracking
|
|
5
|
+
Project-URL: Homepage, https://github.com/calvindotsg/cc-menubar
|
|
6
|
+
Project-URL: Repository, https://github.com/calvindotsg/cc-menubar
|
|
7
|
+
Project-URL: Issues, https://github.com/calvindotsg/cc-menubar/issues
|
|
8
|
+
Project-URL: Changelog, https://github.com/calvindotsg/cc-menubar/blob/main/CHANGELOG.md
|
|
9
|
+
Author: Calvin
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: claude-code,cli,developer-tools,forecasting,macos,menubar,quota,swiftbar
|
|
13
|
+
Classifier: Development Status :: 3 - Alpha
|
|
14
|
+
Classifier: Environment :: Console
|
|
15
|
+
Classifier: Environment :: MacOS X
|
|
16
|
+
Classifier: Intended Audience :: Developers
|
|
17
|
+
Classifier: Operating System :: MacOS
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Topic :: System :: Systems Administration
|
|
23
|
+
Classifier: Typing :: Typed
|
|
24
|
+
Requires-Python: >=3.11
|
|
25
|
+
Requires-Dist: typer>=0.12
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
|
|
28
|
+
# cc-menubar
|
|
29
|
+
|
|
30
|
+
Pace your quota — proactive forecasting for Claude Code Max, not retroactive tracking.
|
|
31
|
+
|
|
32
|
+
30+ Claude Code usage tools exist. Almost all show what you already spent. cc-menubar inverts this: instead of "how much did I use?", it answers "how far will my quota take me?"
|
|
33
|
+
|
|
34
|
+

|
|
35
|
+
|
|
36
|
+

|
|
37
|
+
|
|
38
|
+
## Three Principles
|
|
39
|
+
|
|
40
|
+
| Principle | What it means |
|
|
41
|
+
|-----------|--------------|
|
|
42
|
+
| **Forecast remaining, don't sum spent** | Show what's LEFT (runway), not what's USED (cost). The gauge depletes like fuel — 1.0 to 0.0. |
|
|
43
|
+
| **Pace by phase** | Different work phases burn tokens differently. Activity classifier shows where tokens go. |
|
|
44
|
+
| **Maintain headroom** | Don't run hot. Context Efficiency and quota pacing give early awareness, not late alerts. |
|
|
45
|
+
|
|
46
|
+
## Install
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
brew install calvindotsg/tap/cc-menubar
|
|
50
|
+
brew install --cask swiftbar
|
|
51
|
+
cc-menubar install
|
|
52
|
+
open -a SwiftBar
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Or with [uv](https://docs.astral.sh/uv/):
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
uv tool install cc-menubar
|
|
59
|
+
cc-menubar install
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Menu Bar Icon
|
|
63
|
+
|
|
64
|
+
A gauge icon showing remaining quota. The needle position reflects how much quota is left in the current window (default: 5-hour). Configurable text, color thresholds, and metric cycling.
|
|
65
|
+
|
|
66
|
+
## Dropdown Sections
|
|
67
|
+
|
|
68
|
+
| Section | Content | Visibility |
|
|
69
|
+
|---------|---------|------------|
|
|
70
|
+
| Quota & Runway | 5h/7d remaining, pace vs reset, burn rate | Always |
|
|
71
|
+
| Activity (7d) | Category bars with one-shot rate | Always |
|
|
72
|
+
| Projects | Per-project calls + subagent % | Always |
|
|
73
|
+
| Tools & Commands | Top tools, top bash commands | Always |
|
|
74
|
+
| Opusplan Health | Opus vs Haiku substitution % | When Opus model detected |
|
|
75
|
+
| Context Efficiency | >150K session %, P50/P90, cache hit % | When sufficient data exists |
|
|
76
|
+
|
|
77
|
+
## Configuration
|
|
78
|
+
|
|
79
|
+
Config at `~/.config/cc-menubar/config.toml`. Built-in defaults apply automatically.
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
cc-menubar init # Generate commented config
|
|
83
|
+
cc-menubar config # Show merged config
|
|
84
|
+
cc-menubar config --default # Show all defaults
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Title Options
|
|
88
|
+
|
|
89
|
+
```toml
|
|
90
|
+
[title]
|
|
91
|
+
symbol = "gauge.with.needle.fill" # SF Symbol name
|
|
92
|
+
text = "none" # "none" | "percent" | "label"
|
|
93
|
+
color = "monochrome" # "monochrome" | "threshold" | "always"
|
|
94
|
+
metric = "5h" # "5h" | "7d"
|
|
95
|
+
cycle = [] # ["5h", "7d", "opusplan", "context"]
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Theme
|
|
99
|
+
|
|
100
|
+
```toml
|
|
101
|
+
[theme]
|
|
102
|
+
preset = "ayu" # "ayu" (default) or "catppuccin"
|
|
103
|
+
|
|
104
|
+
# Override individual roles
|
|
105
|
+
[theme.light]
|
|
106
|
+
success = "#custom"
|
|
107
|
+
|
|
108
|
+
[theme.dark]
|
|
109
|
+
success = "#custom"
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Sections
|
|
113
|
+
|
|
114
|
+
```toml
|
|
115
|
+
[quota]
|
|
116
|
+
enabled = true
|
|
117
|
+
|
|
118
|
+
[activity]
|
|
119
|
+
enabled = true
|
|
120
|
+
days = 7
|
|
121
|
+
|
|
122
|
+
[tools]
|
|
123
|
+
enabled = true
|
|
124
|
+
top_n = 10
|
|
125
|
+
|
|
126
|
+
[projects]
|
|
127
|
+
enabled = true
|
|
128
|
+
[projects.aliases]
|
|
129
|
+
# "-Users-me-myproject" = "My Project"
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## CLI Commands
|
|
133
|
+
|
|
134
|
+
| Command | Purpose |
|
|
135
|
+
|---------|---------|
|
|
136
|
+
| `render` | Output SwiftBar text (called by wrapper) |
|
|
137
|
+
| `install` | Write SwiftBar wrapper + create config |
|
|
138
|
+
| `uninstall` | Remove SwiftBar wrapper |
|
|
139
|
+
| `init` | Generate config file |
|
|
140
|
+
| `config` | Show merged config |
|
|
141
|
+
|
|
142
|
+
## Data Sources
|
|
143
|
+
|
|
144
|
+
- **Quota**: Reads `/tmp/claude-statusline-usage.json` (written by Claude Code statusline)
|
|
145
|
+
- **Burn rate**: `ccusage blocks --json --active` (optional, install via `brew install ccusage`)
|
|
146
|
+
- **Activity, tools, models, context**: JSONL files in `~/.claude/projects/`
|
|
147
|
+
|
|
148
|
+
## Requirements
|
|
149
|
+
|
|
150
|
+
- macOS 13.0+ (for variable-value SF Symbols)
|
|
151
|
+
- Python 3.11+
|
|
152
|
+
- [SwiftBar](https://github.com/swiftbar/SwiftBar) or [xbar](https://xbarapp.com/)
|
|
153
|
+
|
|
154
|
+
## License
|
|
155
|
+
|
|
156
|
+
MIT
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# cc-menubar
|
|
2
|
+
|
|
3
|
+
Pace your quota — proactive forecasting for Claude Code Max, not retroactive tracking.
|
|
4
|
+
|
|
5
|
+
30+ Claude Code usage tools exist. Almost all show what you already spent. cc-menubar inverts this: instead of "how much did I use?", it answers "how far will my quota take me?"
|
|
6
|
+
|
|
7
|
+

|
|
8
|
+
|
|
9
|
+

|
|
10
|
+
|
|
11
|
+
## Three Principles
|
|
12
|
+
|
|
13
|
+
| Principle | What it means |
|
|
14
|
+
|-----------|--------------|
|
|
15
|
+
| **Forecast remaining, don't sum spent** | Show what's LEFT (runway), not what's USED (cost). The gauge depletes like fuel — 1.0 to 0.0. |
|
|
16
|
+
| **Pace by phase** | Different work phases burn tokens differently. Activity classifier shows where tokens go. |
|
|
17
|
+
| **Maintain headroom** | Don't run hot. Context Efficiency and quota pacing give early awareness, not late alerts. |
|
|
18
|
+
|
|
19
|
+
## Install
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
brew install calvindotsg/tap/cc-menubar
|
|
23
|
+
brew install --cask swiftbar
|
|
24
|
+
cc-menubar install
|
|
25
|
+
open -a SwiftBar
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Or with [uv](https://docs.astral.sh/uv/):
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
uv tool install cc-menubar
|
|
32
|
+
cc-menubar install
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Menu Bar Icon
|
|
36
|
+
|
|
37
|
+
A gauge icon showing remaining quota. The needle position reflects how much quota is left in the current window (default: 5-hour). Configurable text, color thresholds, and metric cycling.
|
|
38
|
+
|
|
39
|
+
## Dropdown Sections
|
|
40
|
+
|
|
41
|
+
| Section | Content | Visibility |
|
|
42
|
+
|---------|---------|------------|
|
|
43
|
+
| Quota & Runway | 5h/7d remaining, pace vs reset, burn rate | Always |
|
|
44
|
+
| Activity (7d) | Category bars with one-shot rate | Always |
|
|
45
|
+
| Projects | Per-project calls + subagent % | Always |
|
|
46
|
+
| Tools & Commands | Top tools, top bash commands | Always |
|
|
47
|
+
| Opusplan Health | Opus vs Haiku substitution % | When Opus model detected |
|
|
48
|
+
| Context Efficiency | >150K session %, P50/P90, cache hit % | When sufficient data exists |
|
|
49
|
+
|
|
50
|
+
## Configuration
|
|
51
|
+
|
|
52
|
+
Config at `~/.config/cc-menubar/config.toml`. Built-in defaults apply automatically.
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
cc-menubar init # Generate commented config
|
|
56
|
+
cc-menubar config # Show merged config
|
|
57
|
+
cc-menubar config --default # Show all defaults
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Title Options
|
|
61
|
+
|
|
62
|
+
```toml
|
|
63
|
+
[title]
|
|
64
|
+
symbol = "gauge.with.needle.fill" # SF Symbol name
|
|
65
|
+
text = "none" # "none" | "percent" | "label"
|
|
66
|
+
color = "monochrome" # "monochrome" | "threshold" | "always"
|
|
67
|
+
metric = "5h" # "5h" | "7d"
|
|
68
|
+
cycle = [] # ["5h", "7d", "opusplan", "context"]
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Theme
|
|
72
|
+
|
|
73
|
+
```toml
|
|
74
|
+
[theme]
|
|
75
|
+
preset = "ayu" # "ayu" (default) or "catppuccin"
|
|
76
|
+
|
|
77
|
+
# Override individual roles
|
|
78
|
+
[theme.light]
|
|
79
|
+
success = "#custom"
|
|
80
|
+
|
|
81
|
+
[theme.dark]
|
|
82
|
+
success = "#custom"
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Sections
|
|
86
|
+
|
|
87
|
+
```toml
|
|
88
|
+
[quota]
|
|
89
|
+
enabled = true
|
|
90
|
+
|
|
91
|
+
[activity]
|
|
92
|
+
enabled = true
|
|
93
|
+
days = 7
|
|
94
|
+
|
|
95
|
+
[tools]
|
|
96
|
+
enabled = true
|
|
97
|
+
top_n = 10
|
|
98
|
+
|
|
99
|
+
[projects]
|
|
100
|
+
enabled = true
|
|
101
|
+
[projects.aliases]
|
|
102
|
+
# "-Users-me-myproject" = "My Project"
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## CLI Commands
|
|
106
|
+
|
|
107
|
+
| Command | Purpose |
|
|
108
|
+
|---------|---------|
|
|
109
|
+
| `render` | Output SwiftBar text (called by wrapper) |
|
|
110
|
+
| `install` | Write SwiftBar wrapper + create config |
|
|
111
|
+
| `uninstall` | Remove SwiftBar wrapper |
|
|
112
|
+
| `init` | Generate config file |
|
|
113
|
+
| `config` | Show merged config |
|
|
114
|
+
|
|
115
|
+
## Data Sources
|
|
116
|
+
|
|
117
|
+
- **Quota**: Reads `/tmp/claude-statusline-usage.json` (written by Claude Code statusline)
|
|
118
|
+
- **Burn rate**: `ccusage blocks --json --active` (optional, install via `brew install ccusage`)
|
|
119
|
+
- **Activity, tools, models, context**: JSONL files in `~/.claude/projects/`
|
|
120
|
+
|
|
121
|
+
## Requirements
|
|
122
|
+
|
|
123
|
+
- macOS 13.0+ (for variable-value SF Symbols)
|
|
124
|
+
- Python 3.11+
|
|
125
|
+
- [SwiftBar](https://github.com/swiftbar/SwiftBar) or [xbar](https://xbarapp.com/)
|
|
126
|
+
|
|
127
|
+
## License
|
|
128
|
+
|
|
129
|
+
MIT
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Capture cc-menubar README screenshots.
|
|
3
|
+
#
|
|
4
|
+
# Prerequisites:
|
|
5
|
+
# - cc-menubar installed and running in SwiftBar
|
|
6
|
+
# - macOS dark mode + dark wallpaper
|
|
7
|
+
# - Real Claude Code usage data
|
|
8
|
+
#
|
|
9
|
+
# Usage:
|
|
10
|
+
# ./demo/capture.sh icon # Capture menu bar strip
|
|
11
|
+
# ./demo/capture.sh dropdown # Capture expanded dropdown
|
|
12
|
+
# ./demo/capture.sh optimize # Optimize existing PNGs
|
|
13
|
+
set -euo pipefail
|
|
14
|
+
|
|
15
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
16
|
+
|
|
17
|
+
optimize() {
|
|
18
|
+
for f in "$SCRIPT_DIR"/*.png; do
|
|
19
|
+
[ -f "$f" ] || continue
|
|
20
|
+
local before=$(stat -f%z "$f")
|
|
21
|
+
sips -s format png -s formatOptions best "$f" --out "$f" >/dev/null 2>&1
|
|
22
|
+
local after=$(stat -f%z "$f")
|
|
23
|
+
echo "$(basename "$f"): $(( before / 1024 ))KB → $(( after / 1024 ))KB"
|
|
24
|
+
done
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
case "${1:-help}" in
|
|
28
|
+
icon)
|
|
29
|
+
echo "Capture the menu bar strip showing the gauge icon."
|
|
30
|
+
echo "Steps:"
|
|
31
|
+
echo " 1. Ensure cc-menubar is running in SwiftBar"
|
|
32
|
+
echo " 2. A crosshair cursor will appear — select the menu bar region"
|
|
33
|
+
echo " around the gauge icon (include a few neighboring icons for context)"
|
|
34
|
+
screencapture -i "$SCRIPT_DIR/menubar.png"
|
|
35
|
+
echo "Saved: $SCRIPT_DIR/menubar.png"
|
|
36
|
+
optimize
|
|
37
|
+
;;
|
|
38
|
+
dropdown)
|
|
39
|
+
echo "Capture the expanded dropdown."
|
|
40
|
+
echo "Steps:"
|
|
41
|
+
echo " 1. Click the gauge icon to open the dropdown"
|
|
42
|
+
echo " 2. A crosshair cursor will appear after 3 seconds — select the dropdown"
|
|
43
|
+
sleep 3
|
|
44
|
+
screencapture -i "$SCRIPT_DIR/dropdown.png"
|
|
45
|
+
echo "Saved: $SCRIPT_DIR/dropdown.png"
|
|
46
|
+
optimize
|
|
47
|
+
;;
|
|
48
|
+
optimize)
|
|
49
|
+
optimize
|
|
50
|
+
;;
|
|
51
|
+
*)
|
|
52
|
+
echo "Usage: $0 {icon|dropdown|optimize}"
|
|
53
|
+
exit 1
|
|
54
|
+
;;
|
|
55
|
+
esac
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "cc-menubar"
|
|
3
|
+
version = "0.2.0"
|
|
4
|
+
description = "Pace your quota — proactive forecasting for Claude Code Max, not retroactive tracking"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = "MIT"
|
|
7
|
+
license-files = ["LICEN[CS]E*"]
|
|
8
|
+
requires-python = ">=3.11"
|
|
9
|
+
dependencies = ["typer>=0.12"]
|
|
10
|
+
authors = [{name = "Calvin"}]
|
|
11
|
+
keywords = ["claude-code", "macos", "menubar", "swiftbar", "quota", "forecasting", "cli", "developer-tools"]
|
|
12
|
+
classifiers = [
|
|
13
|
+
"Development Status :: 3 - Alpha",
|
|
14
|
+
"Environment :: Console",
|
|
15
|
+
"Environment :: MacOS X",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"Operating System :: MacOS",
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"Programming Language :: Python :: 3.11",
|
|
20
|
+
"Programming Language :: Python :: 3.12",
|
|
21
|
+
"Programming Language :: Python :: 3.13",
|
|
22
|
+
"Topic :: System :: Systems Administration",
|
|
23
|
+
"Typing :: Typed",
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
[project.urls]
|
|
27
|
+
Homepage = "https://github.com/calvindotsg/cc-menubar"
|
|
28
|
+
Repository = "https://github.com/calvindotsg/cc-menubar"
|
|
29
|
+
Issues = "https://github.com/calvindotsg/cc-menubar/issues"
|
|
30
|
+
Changelog = "https://github.com/calvindotsg/cc-menubar/blob/main/CHANGELOG.md"
|
|
31
|
+
|
|
32
|
+
[project.scripts]
|
|
33
|
+
cc-menubar = "cc_menubar.cli:app"
|
|
34
|
+
|
|
35
|
+
[dependency-groups]
|
|
36
|
+
dev = ["ruff>=0.8", "pytest>=8"]
|
|
37
|
+
|
|
38
|
+
[build-system]
|
|
39
|
+
requires = ["hatchling"]
|
|
40
|
+
build-backend = "hatchling.build"
|
|
41
|
+
|
|
42
|
+
[tool.hatch.build.targets.wheel]
|
|
43
|
+
packages = ["src/cc_menubar"]
|
|
44
|
+
|
|
45
|
+
[tool.ruff]
|
|
46
|
+
target-version = "py311"
|
|
47
|
+
line-length = 100
|
|
48
|
+
|
|
49
|
+
[tool.ruff.lint]
|
|
50
|
+
select = ["E", "F", "I", "N", "W", "UP"]
|
|
51
|
+
|
|
52
|
+
[tool.pytest.ini_options]
|
|
53
|
+
testpaths = ["tests"]
|