Skip to content

Commit 60a7b01

Browse files
committed
add a demo mode and setup readme
1 parent c9e1fdc commit 60a7b01

6 files changed

Lines changed: 109 additions & 29 deletions

File tree

README.md

Lines changed: 40 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,66 +1,79 @@
11
<h1 align="center">codeburn-rs</h1>
22

3-
See where your AI coding tokens go but **600x faster**
3+
<p align="center">See where your AI coding tokens go — <b>600× faster</b></p>
44

5-
Benchmarked against the published JS version (`npx codeburn`) with hyperfine on MacBook Pro (M1 Pro, 16GB, 1TB).
5+
<p align="center">
6+
<img src="demo.png" alt="codeburn dashboard screenshot" width="800">
7+
</p>
68

7-
| Rust cache state | JS | Rust (`cburn`) | JS (`npx codeburn`) | Speedup |
8-
| ---------------- | --------------- | -------------- | ------------------- | ------- |
9-
| cached output | cached | 6.0 ms | 3.66 s | ~610× |
10-
| cached sources | cached | 10.9 ms | 3.66 s | ~335× |
11-
| cold (bo cache) | cold (no cache) | 76 ms | 7.71 s | ~101× |
9+
A Rust rewrite of [codeburn](https://github.com/AgentSeal/codeburn). Supports Claude, Codex, Opencode, Pi, and Copilot.
1210

13-
Supported providers: Claude, Codex, Opencode, Pi, Copilot.
11+
## Benchmarks
12+
13+
Measured with hyperfine on a MacBook Pro (M1 Pro, 16GB, 1TB) against `npx codeburn`.
14+
15+
| Scenario | `cburn` | `npx codeburn` | Speedup |
16+
| --------------- | ------- | -------------- | ------- |
17+
| Cached output | 6.0 ms | N/A | ~610× |
18+
| Cached sources | 10.9 ms | 3.66 s | ~335× |
19+
| Cold (no cache) | 76 ms | 7.71 s | ~101× |
1420

1521
## Install
1622

17-
### Homebrew
23+
**Homebrew**
1824

1925
```sh
2026
brew install rossnoah/tap/cburn
2127
```
2228

23-
### Shell installer
29+
## Usage
30+
31+
Launch the interactive dashboard:
2432

2533
```sh
26-
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/rossnoah/codeburn-rs/releases/latest/download/cburn-installer.sh | sh
34+
cburn
2735
```
2836

29-
### From source
37+
**Shell installer**
3038

3139
```sh
32-
cargo install --git https://github.com/rossnoah/codeburn-rs
40+
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/rossnoah/codeburn-rs/releases/latest/download/cburn-installer.sh | sh
3341
```
3442

35-
## Usage
36-
37-
Open the interactive dashboard:
43+
**Install from source**
3844

3945
```sh
40-
cburn
46+
cargo install --git https://github.com/rossnoah/codeburn-rs
4147
```
4248

43-
### Other commands
49+
Other commands:
4450

4551
```sh
4652
cburn today # jump to today's usage
4753
cburn month # jump to this month's usage
4854
cburn report --period 30days # report over a custom period
4955
cburn report --provider claude # filter to a single provider
50-
cburn status # compact terminal snapshot (today + week + month)
51-
cburn export --format csv # export usage data to CSV or JSON
56+
cburn status # compact snapshot (today + week + month)
57+
cburn export --format csv # export usage data (csv or json)
5258
cburn currency GBP # change display currency
5359
```
5460

55-
For full options, see `cburn --help` or `cburn <subcommand> --help`. The binary is named `cburn` to avoid colliding with the npm `codeburn` package — if you don't have the npm version installed and prefer the full name, alias it.
61+
Run `cburn --help` or `cburn <subcommand> --help` for full options.
62+
63+
The binary is named `cburn` to avoid colliding with the npm `codeburn` package. If you don't have the npm version installed and prefer the full name, add an alias to your shell config:
64+
65+
```sh
66+
echo 'alias codeburn=cburn' >> ~/.zshrc # update profile
67+
alias codeburn=cburn # update current shell
68+
```
69+
70+
or
5671

5772
```sh
58-
alias codeburn=cburn
73+
echo 'alias codeburn=cburn' >> ~/.bashrc # update profile
74+
alias codeburn=cburn # update current shell
5975
```
6076

61-
> Cursor support is currently disabled: Cursor stopped writing per-call token
62-
> counts to its local `state.vscdb` in early 2026, so any parse of that DB
63-
> now reports $0 regardless of actual usage. The parser code is retained in
64-
> case the data layout is restored upstream.
77+
## Notes
6578

66-
Original JS version: (https://github.com/AgentSeal/codeburn).
79+
> **Cursor support is currently disabled.** Cursor stopped writing per-call token counts to its local `state.vscdb` in early 2026, so parsing that DB now reports $0 regardless of actual usage. The parser code is retained in case the data layout is restored upstream.

demo.png

1.15 MB
Loading

src/cli.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,11 @@ pub struct Cli {
3636
/// full parse pipeline without the output replay short-circuit.
3737
#[arg(long, global = true)]
3838
pub no_output_cache: bool,
39+
40+
/// Render the real usage data, but replace every project path with a
41+
/// stable fake name. Useful for screenshots and demos.
42+
#[arg(long, global = true)]
43+
pub demo: bool,
3944
}
4045

4146
#[derive(Subcommand)]

src/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ fn main() -> Result<()> {
4747

4848
parser::set_cache_bypass(cli.no_cache);
4949
parser::set_output_cache_bypass(cli.no_output_cache);
50+
parser::set_demo_mode(cli.demo);
5051

5152
let command = cli.command.unwrap_or(Commands::Report {
5253
period: Period::Week,

src/parser.rs

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,10 +59,63 @@ pub fn is_output_cache_bypassed() -> bool {
5959
OUTPUT_CACHE_BYPASS.load(std::sync::atomic::Ordering::Acquire)
6060
}
6161

62+
/// `--demo` mode: keep the real numbers but rename every project to a
63+
/// stable fake string before it surfaces in the UI / export.
64+
static DEMO_MODE: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
65+
66+
pub fn set_demo_mode(on: bool) {
67+
DEMO_MODE.store(on, std::sync::atomic::Ordering::Release);
68+
}
69+
70+
pub fn is_demo_mode() -> bool {
71+
DEMO_MODE.load(std::sync::atomic::Ordering::Acquire)
72+
}
73+
6274
fn unsanitize_path(dir_name: &str) -> String {
6375
dir_name.replace('-', "/")
6476
}
6577

78+
/// Stable, plausible-looking fake project names used when `--demo` is on.
79+
/// The display name is `<base>-<hh>` where `base` is picked by hashing
80+
/// the real path against this list and `hh` is a 2-char hex disambiguator
81+
/// from a separate hash bit-mix. ~9000 distinct names, so collisions
82+
/// between real projects in one report are very rare.
83+
const DEMO_NAMES: &[&str] = &[
84+
"atlas-engine", "nimbus-api", "phoenix-cli", "obsidian-store",
85+
"emerald-runner", "comet-parser", "velvet-router", "meridian-graph",
86+
"onyx-vault", "harbor-sync", "granite-cache", "copper-stream",
87+
"juniper-bot", "meadow-app", "horizon-tools", "twilight-db",
88+
"crystal-edge", "ember-spark", "crater-loop", "spectra-ui",
89+
"voyager-task", "aurora-feed", "mosaic-grid", "tidewater-job",
90+
"riverstone-svc", "lighthouse-pkg", "silverleaf-fmt", "canvas-flow",
91+
"prism-codec", "summit-link", "breeze-deploy", "glacier-store",
92+
"forge-runtime", "sandstone-app", "thicket-mod",
93+
];
94+
95+
fn fake_project_name(real: &str) -> String {
96+
use std::hash::Hasher;
97+
let mut h1 = rustc_hash::FxHasher::default();
98+
h1.write(real.as_bytes());
99+
let n = h1.finish();
100+
// Mix differently for the suffix so name-pick and suffix don't
101+
// co-vary; otherwise two paths landing on the same base name would
102+
// also collide on the suffix.
103+
let suffix_byte = ((n.rotate_left(17) ^ 0x9E37_79B9_7F4A_7C15) & 0xFF) as u8;
104+
let base = DEMO_NAMES[(n as usize) % DEMO_NAMES.len()];
105+
format!("~/Code/{}-{:02x}", base, suffix_byte)
106+
}
107+
108+
/// Convert a sanitized provider directory name to the path string the UI
109+
/// renders. Returns the real path normally; under `--demo` returns a
110+
/// stable fake name so screenshots don't leak local directory names.
111+
fn display_project_path(raw: &str) -> String {
112+
let real = unsanitize_path(raw);
113+
if !is_demo_mode() {
114+
return real;
115+
}
116+
fake_project_name(&real)
117+
}
118+
66119
/// Compute cache-invalidation metadata for every known source and fold
67120
/// the results into a single `u64` hash plus a per-path `(mtime, size)`
68121
/// map. The hash is the output cache's "session-files signature" — any
@@ -742,7 +795,7 @@ fn finalize_projects(
742795
let total_cost: f64 = sessions.iter().map(|s| s.total_cost_usd).sum();
743796
let total_calls: u64 = sessions.iter().map(|s| s.api_calls).sum();
744797
ProjectSummary {
745-
project_path: unsanitize_path(&project),
798+
project_path: display_project_path(&project),
746799
sessions,
747800
total_cost_usd: total_cost,
748801
total_api_calls: total_calls,
@@ -1024,7 +1077,7 @@ fn aggregate_static_from_summary(
10241077
.into_iter()
10251078
.filter(|(_, a)| a.calls > 0)
10261079
.map(|(p, a)| StaticProjectAggregate {
1027-
project_path: unsanitize_path(&p),
1080+
project_path: display_project_path(&p),
10281081
total_cost_usd: a.cost,
10291082
total_api_calls: a.calls,
10301083
})

src/tui/mod.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,14 @@ where
129129
let bypass = crate::parser::is_cache_bypassed();
130130
let output_cache_bypass = crate::parser::is_output_cache_bypassed();
131131

132+
// Keep demo and real outputs on separate cache entries by stealing
133+
// the top bit of `extra` for the demo flag.
134+
let extra = if crate::parser::is_demo_mode() {
135+
extra | (1u64 << 63)
136+
} else {
137+
extra
138+
};
139+
132140
let (pre_stats, session_sig) = if !bypass && !output_cache_bypass {
133141
crate::parser::stat_all_sources()
134142
} else {

0 commit comments

Comments
 (0)