burnbar 0.0.0-dev.37.1
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.
- package/LICENSE +21 -0
- package/README.md +245 -0
- package/install.ps1 +206 -0
- package/npm/bin/burnbar.cjs +95 -0
- package/package.json +27 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Lahiru S Liyanage
|
|
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.
|
package/README.md
ADDED
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
<img src="assets/icon.png" alt="BurnBar icon" width="96">
|
|
4
|
+
|
|
5
|
+
# BurnBar
|
|
6
|
+
|
|
7
|
+
[](https://github.com/nooblk-98/burnbar/actions/workflows/build.yml)
|
|
8
|
+
[](https://github.com/nooblk-98/burnbar/releases)
|
|
9
|
+
[](go.mod)
|
|
10
|
+
[](#installation)
|
|
11
|
+
[](LICENSE)
|
|
12
|
+
|
|
13
|
+
**Claude and Codex usage meter for the Windows taskbar.**<br>
|
|
14
|
+
See how fast you're burning through your plan limits without leaving what you're doing.
|
|
15
|
+
|
|
16
|
+
[Features](#features) • [See it in action](#see-it-in-action) • [Installation](#installation) • [Build from source](#build-from-source)
|
|
17
|
+
|
|
18
|
+
<img src="assets/social-preview.png" alt="BurnBar, Claude and Codex on your Windows taskbar, with quotas, history, and alerts" width="848">
|
|
19
|
+
|
|
20
|
+
</div>
|
|
21
|
+
|
|
22
|
+
Claude and Codex have session and longer-term plan limits. BurnBar keeps both providers on your
|
|
23
|
+
taskbar, warns you before you run out, and shows whether you're using them faster than the time
|
|
24
|
+
window allows. Use both together, or select one from **Usage providers** in the right-click menu.
|
|
25
|
+
Codex window lengths come from your account rather than assuming every plan uses the same limits.
|
|
26
|
+
|
|
27
|
+
> [!NOTE]
|
|
28
|
+
> BurnBar is an unofficial community project, not affiliated with or endorsed by Anthropic or OpenAI.
|
|
29
|
+
> "Claude" and "Codex" belong to their respective owners.
|
|
30
|
+
|
|
31
|
+
## Features
|
|
32
|
+
|
|
33
|
+
- **Claude + Codex**: one compact meter rotates between providers every 8 seconds with a downward
|
|
34
|
+
flip animation. The provider name stays visible above its two quota bars. Rotation pauses on
|
|
35
|
+
hover and while details are open, and respects Windows' animation setting.
|
|
36
|
+
Select Claude only or Codex only to keep one provider visible. Bars turn red at 90%.
|
|
37
|
+
- **Pace marker**: a tick on each bar shows how much of the window's time has passed. A bar past its
|
|
38
|
+
tick means you'll hit the limit before it resets.
|
|
39
|
+
- **Stays out of the way**: finds free space on the taskbar so it never covers app icons, follows the
|
|
40
|
+
light/dark theme and display scaling, and hides during fullscreen apps and games.
|
|
41
|
+
The meter narrows when taskbar buttons need more room. If no safe gap fits, it hides
|
|
42
|
+
until space opens; the tray icon and details remain available. It never floats over desktop
|
|
43
|
+
content. Vertical or too-short taskbars also use the tray icon. Search, Start and ordinary
|
|
44
|
+
window clicks keep it visible when space is available.
|
|
45
|
+
- **Details panel**: separate provider panels, reported quota windows, reset times, and independent
|
|
46
|
+
24-hour and 7-day history charts. Claude also shows this week's usage by product when available.
|
|
47
|
+
- **Alerts**: Windows notifications at 80% and 95%, and when a limit you'd been close to resets.
|
|
48
|
+
- **Fresh numbers**: refreshes on a schedule, within a minute of Claude Code or Codex activity, and right after
|
|
49
|
+
a limit resets, backing off automatically if the API rate-limits.
|
|
50
|
+
- **Multi-monitor**: a meter on every taskbar, plus a tray icon with tooltip and menu.
|
|
51
|
+
- **Lightweight**: a single executable with no installer. Codex usage uses your existing Codex CLI.
|
|
52
|
+
- **Start with Windows**: enabled on first launch for new users; turn it off from the
|
|
53
|
+
right-click menu at any time. Existing startup preferences are preserved.
|
|
54
|
+
- **About**: right-click the meter or tray icon for the app version, developer credits,
|
|
55
|
+
and a link to this repository.
|
|
56
|
+
|
|
57
|
+
## See it in action
|
|
58
|
+
|
|
59
|
+
<div align="center">
|
|
60
|
+
<img src="docs/images/burnbar-demo.gif" alt="BurnBar on Windows, showing the rotating taskbar meter and Claude and Codex usage details" width="848">
|
|
61
|
+
<br>
|
|
62
|
+
<sub>BurnBar in action: Claude and Codex usage on the taskbar, with details a click away.</sub>
|
|
63
|
+
</div>
|
|
64
|
+
|
|
65
|
+
## Installation
|
|
66
|
+
|
|
67
|
+
You need Windows 10 or 11 and a signed-in provider:
|
|
68
|
+
|
|
69
|
+
- **Claude:** [Claude Code](https://claude.com/claude-code) signed in with a Claude subscription.
|
|
70
|
+
- **Codex:** [Codex CLI](https://developers.openai.com/codex/cli/) signed in with **ChatGPT**
|
|
71
|
+
(`codex login`). API-key-only accounts do not provide these subscription usage limits.
|
|
72
|
+
BurnBar also detects the CLI bundled with the Windows Codex desktop app when available.
|
|
73
|
+
|
|
74
|
+
Both providers rotate through the meter by default. A missing login affects only that provider. Select
|
|
75
|
+
**Usage providers** from the right-click menu to hide a provider you don't use.
|
|
76
|
+
|
|
77
|
+
### Install or update with npx
|
|
78
|
+
|
|
79
|
+
Requires Windows, [Node.js 22+](https://nodejs.org/) (includes npm/npx), and
|
|
80
|
+
Windows PowerShell 5.1. BurnBar itself does not need Node.js after installation.
|
|
81
|
+
|
|
82
|
+
Install or update using these npm channels (the stable `latest` tag is created
|
|
83
|
+
by the first stable release after this workflow reaches main):
|
|
84
|
+
|
|
85
|
+
```powershell
|
|
86
|
+
# Latest stable release (same command for first install and updates)
|
|
87
|
+
npx --yes burnbar@latest install
|
|
88
|
+
|
|
89
|
+
# Latest development build (may contain bugs)
|
|
90
|
+
npx --yes burnbar@development install
|
|
91
|
+
|
|
92
|
+
# Switch back to stable
|
|
93
|
+
npx --yes burnbar@latest update --channel stable
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
`latest` and `development` are **npm installer channels**. The installer selects
|
|
97
|
+
the matching **GitHub binary channel** by default; `--channel` overrides it.
|
|
98
|
+
The installer package version is not a pin to a particular executable version:
|
|
99
|
+
each invocation checks GitHub for the latest published build of that channel.
|
|
100
|
+
Updates are explicit, not an always-running automatic updater.
|
|
101
|
+
|
|
102
|
+
**Before the first stable npm publish**, use the development installer to install
|
|
103
|
+
the stable executable:
|
|
104
|
+
|
|
105
|
+
```powershell
|
|
106
|
+
npx --yes burnbar@development install --channel stable
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Alternatively, use the GitHub-backed package (also
|
|
110
|
+
requires Git). These commands do not require an npm account or npm publication:
|
|
111
|
+
|
|
112
|
+
```powershell
|
|
113
|
+
npx --yes github:nooblk-98/burnbar#development install --channel stable
|
|
114
|
+
npx --yes github:nooblk-98/burnbar#development install --channel development
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Both channels use `%LOCALAPPDATA%\Programs\BurnBar` and preserve existing settings,
|
|
118
|
+
history and startup preferences when switching. The CLI delegates to its bundled
|
|
119
|
+
PowerShell installer, verifies SHA-256 before replacing anything, and creates a
|
|
120
|
+
Start menu shortcut. A development push publishes an immutable prerelease build;
|
|
121
|
+
stable installs never select those prereleases.
|
|
122
|
+
|
|
123
|
+
```powershell
|
|
124
|
+
# Optional flags work with either package source
|
|
125
|
+
npx --yes burnbar@latest update --install-dir 'D:\Apps\BurnBar' --no-start
|
|
126
|
+
npx --yes burnbar@development install --dry-run
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
`--dry-run` reads release metadata only: no binary download, file replacement,
|
|
130
|
+
process shutdown or launch. See [release setup](docs/RELEASING.md) for npm publishing.
|
|
131
|
+
|
|
132
|
+
### Install or update with PowerShell (no Node.js required)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
Run this in a regular PowerShell window; administrator access is not needed:
|
|
136
|
+
|
|
137
|
+
```powershell
|
|
138
|
+
& ([scriptblock]::Create((irm https://raw.githubusercontent.com/nooblk-98/burnbar/main/install.ps1)))
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
The same command installs or updates the **latest stable release** in
|
|
142
|
+
`%LOCALAPPDATA%\Programs\BurnBar`, verifies its SHA-256 checksum, adds a Start menu
|
|
143
|
+
shortcut, and launches BurnBar. Existing settings and history are kept. A running
|
|
144
|
+
BurnBar instance in your session is closed after the download is verified.
|
|
145
|
+
|
|
146
|
+
You can [review the installer](install.ps1) before running it. To choose a folder
|
|
147
|
+
or install without launching:
|
|
148
|
+
|
|
149
|
+
```powershell
|
|
150
|
+
$installer = irm https://raw.githubusercontent.com/nooblk-98/burnbar/main/install.ps1
|
|
151
|
+
& ([scriptblock]::Create($installer)) -InstallDir 'D:\Apps\BurnBar' -NoStart
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
For a manual install, download `burnbar.exe` from the
|
|
155
|
+
[releases page](https://github.com/nooblk-98/burnbar/releases), put it anywhere,
|
|
156
|
+
and run it.
|
|
157
|
+
|
|
158
|
+
On its first normal launch, BurnBar enables **Start with Windows** for a new profile.
|
|
159
|
+
You can disable it from the right-click menu. Demo and diagnostic modes do not enable it.
|
|
160
|
+
|
|
161
|
+
> [!NOTE]
|
|
162
|
+
> BurnBar is distributed **unsigned**. Windows Smart App Control may block it,
|
|
163
|
+
> and SmartScreen may show *Windows protected your PC*.
|
|
164
|
+
|
|
165
|
+
### If Smart App Control blocks BurnBar (Windows 11)
|
|
166
|
+
|
|
167
|
+
> [!WARNING]
|
|
168
|
+
> Turning off Smart App Control reduces protection for your whole PC. Recent Windows
|
|
169
|
+
> updates support turning it back on, but availability depends on your device and
|
|
170
|
+
> configuration. If Windows warns that restoring it requires a reset or reinstall,
|
|
171
|
+
> do not proceed expecting a temporary change.
|
|
172
|
+
|
|
173
|
+
1. Install the latest Windows updates before changing this setting.
|
|
174
|
+
2. Open **Windows Security > App & browser control > Smart App Control settings**.
|
|
175
|
+
3. If you trust the download and choose to proceed, select **Off** before running
|
|
176
|
+
BurnBar. Read the confirmation carefully.
|
|
177
|
+
4. Download `burnbar.exe` from this repository's releases, place it in your preferred
|
|
178
|
+
folder, and run it. BurnBar is portable and has no installer.
|
|
179
|
+
5. Afterwards, return to **Smart App Control settings** and select **On**, if
|
|
180
|
+
available. Restart if Windows asks, and confirm protection is on.
|
|
181
|
+
|
|
182
|
+
Turning protection back on may block BurnBar again. Running it once does not create
|
|
183
|
+
an exception for the app. See [Microsoft's Smart App Control FAQ](https://support.microsoft.com/en-us/windows/security/threat-malware-protection/smart-app-control-frequently-asked-questions)
|
|
184
|
+
for current requirements and limitations.
|
|
185
|
+
|
|
186
|
+
## Build from source
|
|
187
|
+
|
|
188
|
+
You need [Go 1.26+](https://go.dev/dl/) on Windows.
|
|
189
|
+
|
|
190
|
+
```powershell
|
|
191
|
+
git clone https://github.com/nooblk-98/burnbar.git
|
|
192
|
+
cd burnbar
|
|
193
|
+
.\build.ps1
|
|
194
|
+
.\bin\burnbar.exe
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
`build.ps1` runs vet and any locally available tests, then builds `bin\burnbar.exe`.
|
|
198
|
+
Add `-Resources` to regenerate the icon, manifest and version info, or `-SkipTests`
|
|
199
|
+
to skip local tests. Test sources are excluded from Git.
|
|
200
|
+
|
|
201
|
+
The executable also accepts a few flags for development:
|
|
202
|
+
|
|
203
|
+
| Flag | Description |
|
|
204
|
+
|---|---|
|
|
205
|
+
| `-demo` | Show sample numbers and history without calling the API |
|
|
206
|
+
| `-providers both` | Show Claude and Codex; also accepts `claude` or `codex`. Defaults to the saved menu setting. |
|
|
207
|
+
| `-taskbar-layout` | Print measured taskbar spans and the compact meter's available slot, then exit. Useful for diagnosing placement. |
|
|
208
|
+
| `-snapshot <dir>` | Render PNG previews and the provider-rotation GIF in both themes, then exit. Combine with `-demo -providers both`. |
|
|
209
|
+
| `-once` | Print usage summaries for the selected providers, then exit. Needs a console build: `go build -o bin\burnbar-cli.exe ./cmd/burnbar` |
|
|
210
|
+
|
|
211
|
+
> [!WARNING]
|
|
212
|
+
> Local builds and release builds are unsigned, so Windows **Smart App Control** may block them.
|
|
213
|
+
> See [Releasing BurnBar](docs/RELEASING.md) for the release process.
|
|
214
|
+
|
|
215
|
+
<details>
|
|
216
|
+
<summary><strong>Project layout</strong></summary>
|
|
217
|
+
|
|
218
|
+
```
|
|
219
|
+
cmd/burnbar/ entry point, flags, embedded icon and manifest
|
|
220
|
+
internal/app/ Windows UI: taskbar meters, tray icon, details panel, menu, Win32 and UI Automation
|
|
221
|
+
internal/usage/ Claude usage client and Codex app-server account client
|
|
222
|
+
internal/render/ drawing the meter, details panel, charts and tray icon
|
|
223
|
+
internal/layout/ choosing a free spot on the taskbar
|
|
224
|
+
internal/config/ settings and alert state
|
|
225
|
+
internal/history/ usage history for the charts
|
|
226
|
+
tools/genres/ icon and Windows resource generator
|
|
227
|
+
assets/ app icon
|
|
228
|
+
docs/ screenshots and release guide
|
|
229
|
+
.github/workflows/ CI, GitHub releases and optional winget publishing
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
</details>
|
|
233
|
+
|
|
234
|
+
## Contributing
|
|
235
|
+
|
|
236
|
+
Contributors are welcome! Bug fixes, documentation improvements, and new AI tool
|
|
237
|
+
integrations are all appreciated. Feel free to open a pull request.
|
|
238
|
+
|
|
239
|
+
See the [contribution guide](CONTRIBUTING.md) for setup and checks, the
|
|
240
|
+
[roadmap](ROADMAP.md) for upcoming work, and [privacy details](PRIVACY.md) for data
|
|
241
|
+
access and storage.
|
|
242
|
+
|
|
243
|
+
Support for more AI tools is coming soon. To request a specific tool or suggest a
|
|
244
|
+
feature, [open an issue](https://github.com/nooblk-98/burnbar/issues/new/choose) and tell us
|
|
245
|
+
what you would like to see.
|
package/install.ps1
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
#requires -Version 5.1
|
|
2
|
+
<#
|
|
3
|
+
.SYNOPSIS
|
|
4
|
+
Installs or updates a stable or development BurnBar release for the current user.
|
|
5
|
+
.EXAMPLE
|
|
6
|
+
& .\install.ps1
|
|
7
|
+
.EXAMPLE
|
|
8
|
+
& .\install.ps1 -InstallDir 'D:\Apps\BurnBar' -NoStart
|
|
9
|
+
#>
|
|
10
|
+
[CmdletBinding(SupportsShouldProcess = $true)]
|
|
11
|
+
param(
|
|
12
|
+
[string]$InstallDir = (Join-Path $env:LOCALAPPDATA 'Programs\BurnBar'),
|
|
13
|
+
[switch]$NoStart,
|
|
14
|
+
[ValidateSet('stable', 'development')]
|
|
15
|
+
[string]$Channel = 'stable'
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
$ErrorActionPreference = 'Stop'
|
|
19
|
+
if ([Environment]::OSVersion.Platform -ne [PlatformID]::Win32NT) {
|
|
20
|
+
throw 'BurnBar requires Windows.'
|
|
21
|
+
}
|
|
22
|
+
if (-not [Environment]::Is64BitOperatingSystem) {
|
|
23
|
+
throw 'The BurnBar release requires 64-bit Windows.'
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
$installPath = [IO.Path]::GetFullPath([Environment]::ExpandEnvironmentVariables($InstallDir))
|
|
27
|
+
$targetExe = Join-Path $installPath 'burnbar.exe'
|
|
28
|
+
$headers = @{ 'User-Agent' = 'BurnBar-Installer'; Accept = 'application/vnd.github+json' }
|
|
29
|
+
$previousTls = [Net.ServicePointManager]::SecurityProtocol
|
|
30
|
+
$stagePath = $null
|
|
31
|
+
$preserveBackup = $false
|
|
32
|
+
$stoppedPaths = @()
|
|
33
|
+
$installComplete = $false
|
|
34
|
+
|
|
35
|
+
function Get-BurnBarProcesses {
|
|
36
|
+
param([string]$Target, [bool]$TargetOnly)
|
|
37
|
+
$session = [Diagnostics.Process]::GetCurrentProcess().SessionId
|
|
38
|
+
foreach ($candidate in @(Get-Process -Name 'burnbar*' -ErrorAction SilentlyContinue)) {
|
|
39
|
+
try {
|
|
40
|
+
if ($candidate.SessionId -ne $session -or -not $candidate.Path) { continue }
|
|
41
|
+
$candidatePath = [IO.Path]::GetFullPath($candidate.Path)
|
|
42
|
+
if ($candidatePath -eq $Target) {
|
|
43
|
+
$candidate
|
|
44
|
+
} elseif (-not $TargetOnly) {
|
|
45
|
+
$info = [Diagnostics.FileVersionInfo]::GetVersionInfo($candidatePath)
|
|
46
|
+
if ($info.ProductName -eq 'BurnBar' -and $info.OriginalFilename -eq 'burnbar.exe') {
|
|
47
|
+
$candidate
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
} catch {
|
|
51
|
+
# Never stop an inaccessible or unverified process based only on its name.
|
|
52
|
+
continue
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
[Net.ServicePointManager]::SecurityProtocol = $previousTls -bor [Net.SecurityProtocolType]::Tls12
|
|
59
|
+
Write-Host "Checking the latest BurnBar $Channel release..."
|
|
60
|
+
if ($Channel -eq 'stable') {
|
|
61
|
+
$release = Invoke-RestMethod -Uri 'https://api.github.com/repos/nooblk-98/burnbar/releases/latest' -Headers $headers -TimeoutSec 30
|
|
62
|
+
if ($release.draft -or $release.prerelease -or -not $release.tag_name) {
|
|
63
|
+
throw 'GitHub did not return a published stable release.'
|
|
64
|
+
}
|
|
65
|
+
} else {
|
|
66
|
+
# Each development build has immutable assets. Never update two assets on
|
|
67
|
+
# a rolling release, where concurrent clients could fetch a mixed pair.
|
|
68
|
+
$release = $null
|
|
69
|
+
for ($page = 1; $page -le 20; $page++) {
|
|
70
|
+
$releases = @((Invoke-RestMethod -Uri "https://api.github.com/repos/nooblk-98/burnbar/releases?per_page=100&page=$page" -Headers $headers -TimeoutSec 30))
|
|
71
|
+
$release = $releases | Where-Object { -not $_.draft -and $_.prerelease -and $_.tag_name -match '^dev-[0-9]+-[0-9]+$' } | Select-Object -First 1
|
|
72
|
+
if ($release -or $releases.Count -lt 100) { break }
|
|
73
|
+
}
|
|
74
|
+
if (-not $release) { throw 'No published development build was found. Try the stable channel or wait for the development workflow.' }
|
|
75
|
+
Write-Warning 'Installing an unreleased development build. Switch to --channel stable to return to a stable release.'
|
|
76
|
+
}
|
|
77
|
+
$exeAssets = @($release.assets | Where-Object { $_.name -ceq 'burnbar.exe' })
|
|
78
|
+
$hashAssets = @($release.assets | Where-Object { $_.name -ceq 'burnbar.exe.sha256' })
|
|
79
|
+
if ($exeAssets.Count -ne 1 -or $hashAssets.Count -ne 1) {
|
|
80
|
+
throw "Release $($release.tag_name) must contain burnbar.exe and burnbar.exe.sha256."
|
|
81
|
+
}
|
|
82
|
+
foreach ($asset in @($exeAssets[0], $hashAssets[0])) {
|
|
83
|
+
$assetUri = [Uri]$asset.browser_download_url
|
|
84
|
+
$expectedAssetPath = '/nooblk-98/burnbar/releases/download/' + [Uri]::EscapeDataString($release.tag_name) + '/' + $asset.name
|
|
85
|
+
if ($assetUri.Scheme -ne 'https' -or $assetUri.Host -ne 'github.com' -or
|
|
86
|
+
$assetUri.AbsolutePath -cne $expectedAssetPath -or $assetUri.Query -or $assetUri.Fragment) {
|
|
87
|
+
throw 'The release contains an unexpected download URL.'
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
if (-not $PSCmdlet.ShouldProcess($targetExe, "Install or update BurnBar $($release.tag_name)")) {
|
|
91
|
+
return
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
[IO.Directory]::CreateDirectory($installPath) | Out-Null
|
|
95
|
+
$stagePath = Join-Path $installPath ('.burnbar-update-' + [Guid]::NewGuid().ToString('N'))
|
|
96
|
+
[IO.Directory]::CreateDirectory($stagePath) | Out-Null
|
|
97
|
+
$downloadExe = Join-Path $stagePath 'burnbar.exe'
|
|
98
|
+
$downloadHash = Join-Path $stagePath 'burnbar.exe.sha256'
|
|
99
|
+
$backupExe = Join-Path $stagePath 'burnbar.previous.exe'
|
|
100
|
+
Write-Host "Downloading BurnBar $($release.tag_name)..."
|
|
101
|
+
Invoke-WebRequest -UseBasicParsing -Uri $hashAssets[0].browser_download_url -Headers $headers -OutFile $downloadHash -TimeoutSec 60 | Out-Null
|
|
102
|
+
Invoke-WebRequest -UseBasicParsing -Uri $exeAssets[0].browser_download_url -Headers $headers -OutFile $downloadExe -TimeoutSec 180 | Out-Null
|
|
103
|
+
$hashText = (Get-Content -LiteralPath $downloadHash -Raw).Trim()
|
|
104
|
+
if ($hashText -notmatch '\A([0-9a-fA-F]{64})[ \t]+\*?burnbar\.exe\z') {
|
|
105
|
+
throw 'The release checksum file has an unexpected format.'
|
|
106
|
+
}
|
|
107
|
+
$expectedHash = $Matches[1].ToLowerInvariant()
|
|
108
|
+
$actualHash = (Get-FileHash -LiteralPath $downloadExe -Algorithm SHA256).Hash.ToLowerInvariant()
|
|
109
|
+
if ($actualHash -ne $expectedHash) {
|
|
110
|
+
throw 'SHA-256 verification failed. The existing installation was not changed.'
|
|
111
|
+
}
|
|
112
|
+
$digest = $exeAssets[0].digest
|
|
113
|
+
if ($digest -and $digest -ne ('sha256:' + $actualHash)) {
|
|
114
|
+
throw 'The download does not match the release asset digest. Installation was stopped.'
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
$sameFile = (Test-Path -LiteralPath $targetExe -PathType Leaf) -and
|
|
118
|
+
((Get-FileHash -LiteralPath $targetExe -Algorithm SHA256).Hash.ToLowerInvariant() -eq $actualHash)
|
|
119
|
+
$targetRunning = $false
|
|
120
|
+
foreach ($running in @(Get-BurnBarProcesses -Target $targetExe -TargetOnly $NoStart.IsPresent)) {
|
|
121
|
+
if ($running.HasExited) { continue }
|
|
122
|
+
if ($sameFile -and $running.Path -eq $targetExe) {
|
|
123
|
+
$targetRunning = $true
|
|
124
|
+
continue
|
|
125
|
+
}
|
|
126
|
+
$runningPath = $running.Path
|
|
127
|
+
Write-Host 'Closing the running BurnBar instance...'
|
|
128
|
+
$closed = $running.CloseMainWindow() -and $running.WaitForExit(3000)
|
|
129
|
+
if (-not $closed) {
|
|
130
|
+
Stop-Process -Id $running.Id -Force -ErrorAction Stop
|
|
131
|
+
if (-not $running.WaitForExit(5000)) { throw 'BurnBar did not exit; the update was stopped.' }
|
|
132
|
+
}
|
|
133
|
+
$stoppedPaths += $runningPath
|
|
134
|
+
}
|
|
135
|
+
if (-not $sameFile) {
|
|
136
|
+
if (Test-Path -LiteralPath $targetExe -PathType Leaf) {
|
|
137
|
+
[IO.File]::Replace($downloadExe, $targetExe, $backupExe)
|
|
138
|
+
} else {
|
|
139
|
+
[IO.File]::Move($downloadExe, $targetExe)
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
$installComplete = $true
|
|
143
|
+
|
|
144
|
+
try {
|
|
145
|
+
$programs = Join-Path $env:APPDATA 'Microsoft\Windows\Start Menu\Programs'
|
|
146
|
+
[IO.Directory]::CreateDirectory($programs) | Out-Null
|
|
147
|
+
$shell = New-Object -ComObject WScript.Shell
|
|
148
|
+
$shortcut = $shell.CreateShortcut((Join-Path $programs 'BurnBar.lnk'))
|
|
149
|
+
$shortcut.TargetPath = $targetExe
|
|
150
|
+
$shortcut.WorkingDirectory = $installPath
|
|
151
|
+
$shortcut.IconLocation = "$targetExe,0"
|
|
152
|
+
$shortcut.Description = 'Claude and Codex usage monitor for the Windows taskbar'
|
|
153
|
+
$shortcut.Save()
|
|
154
|
+
} catch {
|
|
155
|
+
Write-Warning "BurnBar is installed, but its Start menu shortcut could not be created: $($_.Exception.Message)"
|
|
156
|
+
}
|
|
157
|
+
if (-not $NoStart -and -not $targetRunning) {
|
|
158
|
+
try {
|
|
159
|
+
$started = Start-Process -FilePath $targetExe -WorkingDirectory $installPath -WindowStyle Hidden -PassThru
|
|
160
|
+
Start-Sleep -Milliseconds 1000
|
|
161
|
+
if ($started.HasExited) {
|
|
162
|
+
Write-Warning 'BurnBar exited after launch. Another copy may be running; close it and launch the installed executable.'
|
|
163
|
+
}
|
|
164
|
+
} catch {
|
|
165
|
+
Write-Warning "BurnBar is installed, but Windows could not launch it: $($_.Exception.Message)"
|
|
166
|
+
Write-Warning 'See the README for unsigned-app and Smart App Control guidance.'
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
if ($sameFile) {
|
|
170
|
+
Write-Host "BurnBar $($release.tag_name) is already up to date."
|
|
171
|
+
} else {
|
|
172
|
+
Write-Host "Installed BurnBar $($release.tag_name)."
|
|
173
|
+
}
|
|
174
|
+
Write-Host "Location: $targetExe"
|
|
175
|
+
} catch {
|
|
176
|
+
$installError = $_
|
|
177
|
+
if ($stagePath -and (Test-Path -LiteralPath (Join-Path $stagePath 'burnbar.previous.exe'))) {
|
|
178
|
+
try {
|
|
179
|
+
[IO.File]::Copy((Join-Path $stagePath 'burnbar.previous.exe'), $targetExe, $true)
|
|
180
|
+
} catch {
|
|
181
|
+
$preserveBackup = $true
|
|
182
|
+
Write-Warning "The previous executable is preserved at $stagePath\burnbar.previous.exe."
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
if (-not $NoStart -and -not $installComplete) {
|
|
186
|
+
foreach ($oldPath in @($stoppedPaths | Select-Object -Unique)) {
|
|
187
|
+
try { Start-Process -FilePath $oldPath -WindowStyle Hidden | Out-Null } catch {
|
|
188
|
+
Write-Warning "Could not restart the previous BurnBar instance at $oldPath."
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
throw $installError
|
|
193
|
+
} finally {
|
|
194
|
+
[Net.ServicePointManager]::SecurityProtocol = $previousTls
|
|
195
|
+
if ($stagePath) {
|
|
196
|
+
# Remove only our explicitly named files, then the empty staging directory.
|
|
197
|
+
# No recursive deletion or paths outside the unique staging directory.
|
|
198
|
+
foreach ($name in @('burnbar.exe', 'burnbar.exe.sha256')) {
|
|
199
|
+
Remove-Item -LiteralPath (Join-Path $stagePath $name) -Force -ErrorAction SilentlyContinue
|
|
200
|
+
}
|
|
201
|
+
if (-not $preserveBackup) {
|
|
202
|
+
Remove-Item -LiteralPath (Join-Path $stagePath 'burnbar.previous.exe') -Force -ErrorAction SilentlyContinue
|
|
203
|
+
try { [IO.Directory]::Delete($stagePath) } catch { }
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
const { spawnSync } = require('node:child_process');
|
|
6
|
+
const pkg = require('../../package.json');
|
|
7
|
+
|
|
8
|
+
const help = `BurnBar installer ${pkg.version}
|
|
9
|
+
|
|
10
|
+
Usage: burnbar [install|update] [options]
|
|
11
|
+
--channel stable|development Release channel (default: matches installer package)
|
|
12
|
+
--install-dir <directory> Override %LOCALAPPDATA%\\Programs\\BurnBar
|
|
13
|
+
--no-start Install without launching BurnBar
|
|
14
|
+
--dry-run Check release metadata without modifying the installation
|
|
15
|
+
--help Show this help
|
|
16
|
+
--version Show the installer package version
|
|
17
|
+
|
|
18
|
+
Install and update use the same verified replacement process. Switching channels
|
|
19
|
+
replaces the executable but preserves your settings and history.
|
|
20
|
+
Requires Windows x64 (or x64 emulation), Node.js 22+, and Windows PowerShell 5.1.
|
|
21
|
+
`;
|
|
22
|
+
|
|
23
|
+
function parseArgs(args) {
|
|
24
|
+
const options = { channel: pkg.version.includes('-dev.') ? 'development' : 'stable' };
|
|
25
|
+
const seen = new Set();
|
|
26
|
+
if (args[0] === 'install' || args[0] === 'update') args = args.slice(1);
|
|
27
|
+
for (let i = 0; i < args.length; i++) {
|
|
28
|
+
const arg = args[i];
|
|
29
|
+
if (seen.has(arg)) throw new Error(`Duplicate option: ${arg}`);
|
|
30
|
+
seen.add(arg);
|
|
31
|
+
switch (arg) {
|
|
32
|
+
case '--channel':
|
|
33
|
+
case '--install-dir': {
|
|
34
|
+
const value = args[++i];
|
|
35
|
+
if (!value || value.startsWith('-')) throw new Error(`${arg} needs a value`);
|
|
36
|
+
if (arg === '--channel') {
|
|
37
|
+
if (!['stable', 'development'].includes(value)) throw new Error('Channel must be stable or development');
|
|
38
|
+
options.channel = value;
|
|
39
|
+
} else {
|
|
40
|
+
options.installDir = path.resolve(value);
|
|
41
|
+
}
|
|
42
|
+
break;
|
|
43
|
+
}
|
|
44
|
+
case '--no-start': options.noStart = true; break;
|
|
45
|
+
case '--dry-run': options.dryRun = true; break;
|
|
46
|
+
case '--help': options.help = true; break;
|
|
47
|
+
case '--version': options.version = true; break;
|
|
48
|
+
default: throw new Error(`Unknown argument: ${arg}. Use --help for usage.`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return options;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function invocation(options, env = process.env) {
|
|
55
|
+
if (!env.SystemRoot || !path.win32.isAbsolute(env.SystemRoot)) {
|
|
56
|
+
throw new Error('Cannot locate Windows PowerShell: SystemRoot is missing or invalid');
|
|
57
|
+
}
|
|
58
|
+
// Use the system executable and a bundled script. Never fetch/execute a remote
|
|
59
|
+
// script or interpolate user input into a PowerShell command string.
|
|
60
|
+
const executable = path.win32.join(env.SystemRoot, 'System32', 'WindowsPowerShell', 'v1.0', 'powershell.exe');
|
|
61
|
+
const args = ['-NoLogo', '-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass',
|
|
62
|
+
'-File', path.resolve(__dirname, '../../install.ps1'), '-Channel', options.channel];
|
|
63
|
+
if (options.installDir) args.push('-InstallDir', options.installDir);
|
|
64
|
+
if (options.noStart) args.push('-NoStart');
|
|
65
|
+
if (options.dryRun) args.push('-WhatIf');
|
|
66
|
+
return { executable, args };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function main(args = process.argv.slice(2), runtime = { platform: process.platform, env: process.env, spawnSync }) {
|
|
70
|
+
try {
|
|
71
|
+
const options = parseArgs(args);
|
|
72
|
+
if (options.help) { console.log(help); return 0; }
|
|
73
|
+
if (options.version) { console.log(pkg.version); return 0; }
|
|
74
|
+
if (runtime.platform !== 'win32') throw new Error('BurnBar requires Windows. No files were installed.');
|
|
75
|
+
const { executable, args: psArgs } = invocation(options, runtime.env);
|
|
76
|
+
// PowerShell 7 can pass its incompatible module search path to Windows
|
|
77
|
+
// PowerShell 5.1 (e.g. when npx is launched from pwsh). Let 5.1 construct
|
|
78
|
+
// its own module path so standard cmdlets such as Get-FileHash resolve.
|
|
79
|
+
const childEnv = { ...runtime.env };
|
|
80
|
+
for (const key of Object.keys(childEnv)) {
|
|
81
|
+
if (key.toLowerCase() === 'psmodulepath') delete childEnv[key];
|
|
82
|
+
}
|
|
83
|
+
const result = runtime.spawnSync(executable, psArgs, {
|
|
84
|
+
stdio: 'inherit', shell: false, windowsHide: true, env: childEnv,
|
|
85
|
+
});
|
|
86
|
+
if (result.error) throw new Error(`Could not run Windows PowerShell: ${result.error.message}`);
|
|
87
|
+
return result.status ?? 1;
|
|
88
|
+
} catch (error) {
|
|
89
|
+
console.error(`BurnBar: ${error.message}`);
|
|
90
|
+
return 1;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (require.main === module) process.exitCode = main();
|
|
95
|
+
module.exports = { parseArgs, invocation, main };
|
package/package.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "burnbar",
|
|
3
|
+
"version": "0.0.0-dev.37.1",
|
|
4
|
+
"description": "Install or update the BurnBar Windows taskbar usage meter",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/nooblk-98/burnbar"
|
|
9
|
+
},
|
|
10
|
+
"homepage": "https://github.com/nooblk-98/burnbar#installation",
|
|
11
|
+
"bugs": {
|
|
12
|
+
"url": "https://github.com/nooblk-98/burnbar/issues"
|
|
13
|
+
},
|
|
14
|
+
"bin": {
|
|
15
|
+
"burnbar": "npm/bin/burnbar.cjs"
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"npm/bin/burnbar.cjs",
|
|
19
|
+
"install.ps1"
|
|
20
|
+
],
|
|
21
|
+
"engines": {
|
|
22
|
+
"node": ">=22"
|
|
23
|
+
},
|
|
24
|
+
"publishConfig": {
|
|
25
|
+
"access": "public"
|
|
26
|
+
}
|
|
27
|
+
}
|