burnboard-cli 0.1.0

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/cli.mjs ADDED
@@ -0,0 +1,71 @@
1
+ #!/usr/bin/env node
2
+ import fs from "node:fs";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+ import { spawnSync } from "node:child_process";
6
+ import { fileURLToPath } from "node:url";
7
+
8
+ const packageRoot = path.dirname(fileURLToPath(import.meta.url));
9
+ const installRoot = path.join(packageRoot, "install");
10
+ const burnboardRoot = path.join(os.homedir(), ".burnboard");
11
+ const command = process.argv[2] || "help";
12
+ const args = process.argv.slice(3);
13
+
14
+ function option(name, fallback) {
15
+ const index = args.indexOf(name);
16
+ return index >= 0 ? args[index + 1] : fallback;
17
+ }
18
+
19
+ function run(executable, runArgs) {
20
+ const result = spawnSync(executable, runArgs, { stdio: "inherit" });
21
+ if (result.error) throw result.error;
22
+ process.exitCode = result.status ?? 1;
23
+ }
24
+
25
+ function setup() {
26
+ const name = option("--name", args.find((value) => !value.startsWith("-")));
27
+ const server = option("--server", "https://burnboard-public.vercel.app");
28
+ if (!name || name.startsWith("--")) throw new Error('A name is required. Example: npx burnboard-cli setup --name "Harsh Sawant"');
29
+ if (process.platform === "win32") {
30
+ run("powershell.exe", ["-NoLogo", "-NoProfile", "-ExecutionPolicy", "Bypass", "-File", path.join(installRoot, "burnboard-setup.ps1"), "-Name", name, "-ServerUrl", server]);
31
+ } else {
32
+ run("bash", [path.join(installRoot, "burnboard-setup.sh"), name, server]);
33
+ }
34
+ }
35
+
36
+ function sync() {
37
+ const agent = path.join(burnboardRoot, "agent.mjs");
38
+ if (!fs.existsSync(agent)) throw new Error("BurnBoard is not installed. Run the setup command first.");
39
+ run(process.execPath, [agent]);
40
+ }
41
+
42
+ function status() {
43
+ const configPath = path.join(burnboardRoot, "config.json");
44
+ if (!fs.existsSync(configPath)) {
45
+ console.log("BurnBoard is not installed.");
46
+ return;
47
+ }
48
+ const config = JSON.parse(fs.readFileSync(configPath, "utf8").replace(/^\uFEFF/, ""));
49
+ console.log(`BurnBoard is installed for ${config.serverUrl}${config.profileUrl || ""}`);
50
+ console.log(`Tracker: ${fs.existsSync(path.join(burnboardRoot, "agent.mjs")) ? "ready" : "missing"}`);
51
+ if (process.platform === "win32") run("powershell.exe", ["-NoLogo", "-NoProfile", "-Command", "$t=Get-ScheduledTask -TaskName 'Burnboard Token Sync' -ErrorAction SilentlyContinue;if($t){$i=$t|Get-ScheduledTaskInfo;Write-Output ('Schedule: '+$t.State+'; next run: '+$i.NextRunTime)}else{Write-Output 'Schedule: missing'}"]);
52
+ }
53
+
54
+ function help() {
55
+ console.log(`BurnBoard CLI
56
+
57
+ burnboard setup --name "Your Name" Install or repair automatic sync
58
+ burnboard sync Sync usage now
59
+ burnboard status Check the local installation`);
60
+ }
61
+
62
+ try {
63
+ if (command === "setup") setup();
64
+ else if (command === "sync") sync();
65
+ else if (command === "status") status();
66
+ else if (["help", "--help", "-h"].includes(command)) help();
67
+ else throw new Error(`Unknown command: ${command}`);
68
+ } catch (error) {
69
+ console.error(`BurnBoard: ${error instanceof Error ? error.message : error}`);
70
+ process.exitCode = 1;
71
+ }
@@ -0,0 +1,96 @@
1
+ param([Parameter(Mandatory=$true)][string]$Name,[string]$ServerUrl="https://burnboard-public.vercel.app")
2
+ $ErrorActionPreference="Stop"
3
+ function Show-SetupProgress([int]$Percent,[string]$Status){Write-Progress -Activity "Connecting to BurnBoard" -Status $Status -PercentComplete $Percent}
4
+ Show-SetupProgress 5 "Preparing your local tracker..."
5
+ $installDir=Join-Path $env:USERPROFILE ".burnboard"
6
+ New-Item -ItemType Directory -Force -Path $installDir|Out-Null
7
+ $configPath=Join-Path $installDir "config.json"
8
+ $existingConfig=$null
9
+ if(Test-Path -LiteralPath $configPath){try{$existingConfig=Get-Content -LiteralPath $configPath -Raw|ConvertFrom-Json}catch{$existingConfig=$null}}
10
+ if($existingConfig -and $existingConfig.serverUrl -eq $ServerUrl -and $existingConfig.token){
11
+ $fallbackSlug=($Name.ToLowerInvariant() -replace '[^a-z0-9]+','-').Trim('-');if(-not $fallbackSlug){$fallbackSlug="builder"}
12
+ $profileUrl=if($existingConfig.profileUrl -and $existingConfig.profileUrl -ne "/"){$existingConfig.profileUrl -replace '^/intern/', '/builder/'}else{"/builder/$fallbackSlug"}
13
+ $enrollment=[PSCustomObject]@{token=$existingConfig.token;profileUrl=$profileUrl}
14
+ }else{
15
+ Show-SetupProgress 12 "Creating your leaderboard profile..."
16
+ $enrollment=Invoke-RestMethod -Method Post -Uri "$ServerUrl/api/enroll" -ContentType "application/json" -Body (@{firstName=$Name}|ConvertTo-Json)
17
+ }
18
+ Show-SetupProgress 22 "Downloading the latest tracker..."
19
+ Invoke-WebRequest -Uri "$ServerUrl/burnboard-agent.mjs" -OutFile (Join-Path $installDir "agent.mjs")
20
+ $utf8NoBom=New-Object System.Text.UTF8Encoding($false)
21
+ [System.IO.File]::WriteAllText($configPath,(@{serverUrl=$ServerUrl;token=$enrollment.token;profileUrl=$enrollment.profileUrl}|ConvertTo-Json),$utf8NoBom)
22
+ $claudeDir=Join-Path $env:USERPROFILE ".claude";$claudePath=Join-Path $claudeDir "settings.json"
23
+ New-Item -ItemType Directory -Force -Path $claudeDir|Out-Null
24
+ if(Test-Path -LiteralPath $claudePath){$settings=Get-Content -LiteralPath $claudePath -Raw|ConvertFrom-Json}else{$settings=[PSCustomObject]@{}}
25
+ if(-not $settings.PSObject.Properties["env"]){$settings|Add-Member -NotePropertyName env -NotePropertyValue ([PSCustomObject]@{})}
26
+ $telemetryKeys=@("CLAUDE_CODE_ENABLE_TELEMETRY","OTEL_METRICS_EXPORTER","OTEL_LOGS_EXPORTER","OTEL_EXPORTER_OTLP_METRICS_PROTOCOL","OTEL_EXPORTER_OTLP_METRICS_ENDPOINT","OTEL_EXPORTER_OTLP_METRICS_HEADERS","OTEL_METRIC_EXPORT_INTERVAL","OTEL_METRICS_INCLUDE_SESSION_ID")
27
+ foreach($key in $telemetryKeys){if($settings.env.PSObject.Properties[$key]){$settings.env.PSObject.Properties.Remove($key)}}
28
+ [System.IO.File]::WriteAllText($claudePath,($settings|ConvertTo-Json -Depth 20),$utf8NoBom)
29
+ Show-SetupProgress 35 "Scheduling automatic sync every five minutes..."
30
+ $node=(Get-Command node -ErrorAction Stop).Source;$agent=Join-Path $installDir "agent.mjs"
31
+ $launcher=Join-Path $installDir "sync-hidden.vbs"
32
+ $nodeVbs=$node.Replace('"','""');$agentVbs=$agent.Replace('"','""')
33
+ $launcherText="Set shell = CreateObject(`"WScript.Shell`")`r`ncommand = Chr(34) & `"$nodeVbs`" & Chr(34) & `" `" & Chr(34) & `"$agentVbs`" & Chr(34)`r`nexitCode = shell.Run(command, 0, True)`r`nWScript.Quit exitCode`r`n"
34
+ [System.IO.File]::WriteAllText($launcher,$launcherText,$utf8NoBom)
35
+ $wscript=Join-Path $env:SystemRoot "System32\wscript.exe"
36
+ $action=New-ScheduledTaskAction -Execute $wscript -Argument "//B //NoLogo `"$launcher`""
37
+ $trigger=New-ScheduledTaskTrigger -Once -At (Get-Date).AddMinutes(1) -RepetitionInterval (New-TimeSpan -Minutes 5) -RepetitionDuration (New-TimeSpan -Days 3650)
38
+ $settings=New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -StartWhenAvailable -RestartCount 3 -RestartInterval (New-TimeSpan -Minutes 1)
39
+ $userId=[System.Security.Principal.WindowsIdentity]::GetCurrent().Name;$principal=New-ScheduledTaskPrincipal -UserId $userId -LogonType Interactive -RunLevel Limited
40
+ Register-ScheduledTask -TaskName "Burnboard Token Sync" -Action $action -Trigger $trigger -Settings $settings -Principal $principal -Force|Out-Null
41
+ $antigravityProcess=Get-Process -Name "Antigravity" -ErrorAction SilentlyContinue
42
+ if(-not $antigravityProcess){
43
+ $antigravityCandidates=@(
44
+ (Join-Path $env:LOCALAPPDATA "Programs\Antigravity\Antigravity.exe"),
45
+ (Join-Path $env:LOCALAPPDATA "Programs\Antigravity IDE\Antigravity IDE.exe"),
46
+ (Join-Path $env:ProgramFiles "Antigravity\Antigravity.exe"),
47
+ (Join-Path $env:ProgramFiles "Antigravity IDE\Antigravity IDE.exe")
48
+ )
49
+ $antigravityExe=$antigravityCandidates|Where-Object{Test-Path -LiteralPath $_}|Select-Object -First 1
50
+ if($antigravityExe){
51
+ Show-SetupProgress 45 "Opening Antigravity to import its token history..."
52
+ $startedAt=Get-Date
53
+ Start-Process -FilePath $antigravityExe -RedirectStandardOutput "NUL" -RedirectStandardError "\\.\NUL"
54
+ $deadline=(Get-Date).AddSeconds(30)
55
+ do{
56
+ Start-Sleep -Seconds 1
57
+ $serviceReady=@("Antigravity","Antigravity IDE")|ForEach-Object{
58
+ $logs=Join-Path (Join-Path $env:APPDATA $_) "logs"
59
+ if(Test-Path -LiteralPath $logs){Get-ChildItem -LiteralPath $logs -Recurse -File -Filter "*.log" -ErrorAction SilentlyContinue|Where-Object{$_.LastWriteTime -ge $startedAt}|Select-String -Pattern 'listening on random port.*for HTTP\b' -List -ErrorAction SilentlyContinue|Select-Object -First 1}
60
+ }|Select-Object -First 1
61
+ $waited=[Math]::Min(9,[int]((Get-Date)-$startedAt).TotalSeconds)
62
+ Show-SetupProgress (45+$waited) "Waiting for Antigravity's token service..."
63
+ }until($serviceReady -or (Get-Date) -ge $deadline)
64
+ }
65
+ }
66
+ Show-SetupProgress 55 "Scanning local AI-tool history..."
67
+ $syncStarted=Get-Date
68
+ $syncStartInfo=New-Object System.Diagnostics.ProcessStartInfo
69
+ $syncStartInfo.FileName=$node
70
+ $syncStartInfo.Arguments="`"$agent`""
71
+ $syncStartInfo.UseShellExecute=$false
72
+ $syncStartInfo.CreateNoWindow=$true
73
+ $syncStartInfo.RedirectStandardOutput=$true
74
+ $syncStartInfo.RedirectStandardError=$true
75
+ $syncProcess=New-Object System.Diagnostics.Process
76
+ $syncProcess.StartInfo=$syncStartInfo
77
+ if(-not $syncProcess.Start()){throw "Unable to start the initial token sync."}
78
+ $syncOutputTask=$syncProcess.StandardOutput.ReadToEndAsync()
79
+ $syncErrorTask=$syncProcess.StandardError.ReadToEndAsync()
80
+ while(-not $syncProcess.HasExited){
81
+ Start-Sleep -Milliseconds 400
82
+ $elapsed=[int]((Get-Date)-$syncStarted).TotalSeconds
83
+ $percent=[Math]::Min(94,55+[int]($elapsed/2))
84
+ Show-SetupProgress $percent "Counting and uploading token history... $elapsed seconds"
85
+ }
86
+ $syncProcess.WaitForExit()
87
+ $syncExitCode=$syncProcess.ExitCode
88
+ $agentOutput=$syncOutputTask.Result
89
+ $agentError=$syncErrorTask.Result
90
+ $syncProcess.Dispose()
91
+ if($syncExitCode -ne 0){Write-Progress -Activity "Connecting to BurnBoard" -Completed;throw "Initial token sync failed: $agentError$agentOutput"}
92
+ Show-SetupProgress 100 "Complete"
93
+ Write-Progress -Activity "Connecting to BurnBoard" -Completed
94
+ if($agentOutput){Write-Host $agentOutput.Trim()}
95
+ Write-Host "Burnboard is connected: $ServerUrl$($enrollment.profileUrl)"
96
+ Write-Host "Codex, Claude Code, Cursor, and both supported Antigravity installations sync every 5 minutes."
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ NAME="${1:?Usage: burnboard-setup.sh NAME [SERVER_URL]}"; SERVER_URL="${2:-https://burnboard-public.vercel.app}"
4
+ command -v node >/dev/null || { echo "Node.js is required." >&2; exit 1; }
5
+ INSTALL_DIR="$HOME/.burnboard"; CONFIG="$INSTALL_DIR/config.json"; mkdir -p "$INSTALL_DIR" "$HOME/.claude"
6
+ printf '[1/5] Preparing BurnBoard profile...\n'
7
+ if [[ -f "$CONFIG" ]] && node -e 'const c=JSON.parse(require("fs").readFileSync(process.argv[1]));process.exit(c.serverUrl===process.argv[2]&&c.token?0:1)' "$CONFIG" "$SERVER_URL"; then
8
+ TOKEN="$(node -e 'console.log(JSON.parse(require("fs").readFileSync(process.argv[1])).token)' "$CONFIG")"
9
+ PROFILE="$(node -e 'const c=JSON.parse(require("fs").readFileSync(process.argv[1]));console.log((c.profileUrl||(`/builder/${process.argv[2].toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-|-$/g,"")||"builder"}`)).replace(/^\/intern\//,"/builder/"))' "$CONFIG" "$NAME")"
10
+ else
11
+ ENROLLMENT="$(curl -fsS -X POST "$SERVER_URL/api/enroll" -H 'content-type: application/json' --data "$(node -e 'console.log(JSON.stringify({firstName:process.argv[1]}))' "$NAME")")"
12
+ TOKEN="$(node -e 'console.log(JSON.parse(process.argv[1]).token)' "$ENROLLMENT")"; PROFILE="$(node -e 'console.log(JSON.parse(process.argv[1]).profileUrl)' "$ENROLLMENT")"
13
+ fi
14
+ printf '[2/5] Downloading tracker...\n'; curl -fsS "$SERVER_URL/burnboard-agent.mjs" -o "$INSTALL_DIR/agent.mjs"
15
+ node -e 'require("fs").writeFileSync(process.argv[1],JSON.stringify({serverUrl:process.argv[2],token:process.argv[3],profileUrl:process.argv[4]},null,2)+"\n")' "$CONFIG" "$SERVER_URL" "$TOKEN" "$PROFILE"
16
+ node - "$HOME/.claude/settings.json" <<'NODE'
17
+ const fs=require('fs'),file=process.argv[2],current=fs.existsSync(file)?JSON.parse(fs.readFileSync(file,'utf8')):{};current.env=current.env||{};for(const key of ['CLAUDE_CODE_ENABLE_TELEMETRY','OTEL_METRICS_EXPORTER','OTEL_LOGS_EXPORTER','OTEL_EXPORTER_OTLP_METRICS_PROTOCOL','OTEL_EXPORTER_OTLP_METRICS_ENDPOINT','OTEL_EXPORTER_OTLP_METRICS_HEADERS','OTEL_METRIC_EXPORT_INTERVAL','OTEL_METRICS_INCLUDE_SESSION_ID'])delete current.env[key];fs.writeFileSync(file,JSON.stringify(current,null,2)+"\n");
18
+ NODE
19
+ printf '[3/5] Scheduling automatic sync...\n'; (crontab -l 2>/dev/null|grep -v 'burnboard/agent.mjs'||true;echo "*/5 * * * * $(command -v node) $INSTALL_DIR/agent.mjs >/dev/null 2>&1")|crontab -
20
+ if [[ "$(uname -s)" == "Darwin" ]] && ! pgrep -x Antigravity >/dev/null 2>&1 && [[ -d "/Applications/Antigravity.app" ]]; then open -a Antigravity; sleep 8; fi
21
+ printf '[4/5] Counting and uploading local history'; OUT="$INSTALL_DIR/setup-sync.out"; ERR="$INSTALL_DIR/setup-sync.err"; node "$INSTALL_DIR/agent.mjs" >"$OUT" 2>"$ERR" & PID=$!
22
+ while kill -0 "$PID" 2>/dev/null; do printf '.'; sleep 1; done
23
+ if ! wait "$PID"; then printf '\n'; cat "$ERR" >&2; rm -f "$OUT" "$ERR"; exit 1; fi
24
+ printf '\n[5/5] Complete\n'; cat "$OUT"; rm -f "$OUT" "$ERR"
25
+ echo "Burnboard is connected: $SERVER_URL$PROFILE"; echo "Codex, Claude Code, Cursor, and both supported Antigravity installations sync every 5 minutes."
package/package.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "burnboard-cli",
3
+ "version": "0.1.0",
4
+ "description": "Connect local AI coding token usage to BurnBoard",
5
+ "type": "module",
6
+ "bin": {
7
+ "burnboard": "cli.mjs",
8
+ "burnboard-cli": "cli.mjs"
9
+ },
10
+ "files": [
11
+ "cli.mjs",
12
+ "install"
13
+ ],
14
+ "scripts": {
15
+ "prepack": "node prepare.mjs"
16
+ },
17
+ "engines": {
18
+ "node": ">=18"
19
+ },
20
+ "keywords": [
21
+ "burnboard",
22
+ "codex",
23
+ "claude-code",
24
+ "cursor",
25
+ "antigravity"
26
+ ]
27
+ }