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 +71 -0
- package/install/burnboard-setup.ps1 +96 -0
- package/install/burnboard-setup.sh +25 -0
- package/package.json +27 -0
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
|
+
}
|