tokburn 0.1.0 → 0.1.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/package.json +1 -1
- package/statusline.js +113 -109
package/package.json
CHANGED
package/statusline.js
CHANGED
|
@@ -3,37 +3,16 @@
|
|
|
3
3
|
* tokburn — Status line renderer for Claude Code
|
|
4
4
|
* Reads session JSON from stdin, renders configured modules.
|
|
5
5
|
* Configured via ~/.tokburn/config.json → statusline_modules
|
|
6
|
+
*
|
|
7
|
+
* IMPORTANT: Stdin reading and rendering only happens when run directly.
|
|
8
|
+
* When require()'d as a module, only MODULE_LIST, PRESETS are exported.
|
|
6
9
|
*/
|
|
7
10
|
|
|
8
11
|
const fs = require('fs');
|
|
9
12
|
const path = require('path');
|
|
10
13
|
const { execFileSync } = require('child_process');
|
|
11
14
|
|
|
12
|
-
//
|
|
13
|
-
let input = '';
|
|
14
|
-
try {
|
|
15
|
-
input = fs.readFileSync('/dev/stdin', 'utf8');
|
|
16
|
-
} catch (_) {}
|
|
17
|
-
|
|
18
|
-
let data = {};
|
|
19
|
-
try {
|
|
20
|
-
data = JSON.parse(input);
|
|
21
|
-
} catch (_) {}
|
|
22
|
-
|
|
23
|
-
// Load config
|
|
24
|
-
const configPath = path.join(process.env.HOME || process.env.USERPROFILE, '.tokburn', 'config.json');
|
|
25
|
-
let config = {};
|
|
26
|
-
try {
|
|
27
|
-
if (fs.existsSync(configPath)) {
|
|
28
|
-
config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
29
|
-
}
|
|
30
|
-
} catch (_) {}
|
|
31
|
-
|
|
32
|
-
const enabledModules = config.statusline_modules || [
|
|
33
|
-
'model_context', 'repo_branch', 'current_limit', 'weekly_limit', 'cost'
|
|
34
|
-
];
|
|
35
|
-
|
|
36
|
-
// ── Module renderers ────────────────────────────────────────────────────────────
|
|
15
|
+
// ── Helpers ─────────────────────────────────────────────────────────────────────
|
|
37
16
|
|
|
38
17
|
function dotBar(pct, count) {
|
|
39
18
|
count = count || 10;
|
|
@@ -64,7 +43,6 @@ function formatResetTime(resetTimestamp) {
|
|
|
64
43
|
const remainMins = mins % 60;
|
|
65
44
|
if (hrs < 24) return hrs + 'hr ' + (remainMins > 0 ? remainMins + 'min' : '');
|
|
66
45
|
|
|
67
|
-
// Show day + time for >24hr
|
|
68
46
|
const days = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
|
|
69
47
|
const day = days[reset.getDay()];
|
|
70
48
|
const h = reset.getHours();
|
|
@@ -74,86 +52,90 @@ function formatResetTime(resetTimestamp) {
|
|
|
74
52
|
return day + ' ' + h12 + ':' + String(m).padStart(2, '0') + ampm;
|
|
75
53
|
}
|
|
76
54
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
55
|
+
// ── Module builders (take data as parameter) ────────────────────────────────────
|
|
56
|
+
|
|
57
|
+
function buildModules(data) {
|
|
58
|
+
return {
|
|
59
|
+
model_context: function () {
|
|
60
|
+
const model = (data.model && data.model.display_name) || '?';
|
|
61
|
+
const ctxPct = Math.round((data.context_window && data.context_window.used_percentage) || 0);
|
|
62
|
+
return model + ' | ctx ' + ctxPct + '%';
|
|
63
|
+
},
|
|
64
|
+
|
|
65
|
+
repo_branch: function () {
|
|
66
|
+
const cwd = (data.workspace && data.workspace.current_dir) || data.cwd || '';
|
|
67
|
+
const repoName = path.basename(cwd);
|
|
68
|
+
let branch = '';
|
|
69
|
+
try {
|
|
70
|
+
branch = execFileSync('git', ['-C', cwd, 'branch', '--show-current'], {
|
|
71
|
+
encoding: 'utf8', timeout: 500, stdio: ['pipe', 'pipe', 'pipe'],
|
|
72
|
+
}).trim();
|
|
73
|
+
const status = execFileSync('git', ['-C', cwd, 'status', '--porcelain'], {
|
|
74
|
+
encoding: 'utf8', timeout: 500, stdio: ['pipe', 'pipe', 'pipe'],
|
|
75
|
+
}).trim();
|
|
76
|
+
if (status) branch += '*';
|
|
77
|
+
} catch (_) {}
|
|
78
|
+
|
|
79
|
+
if (branch) return repoName + ' (' + branch + ')';
|
|
80
|
+
return repoName;
|
|
81
|
+
},
|
|
82
|
+
|
|
83
|
+
current_limit: function () {
|
|
84
|
+
const rl = data.rate_limits && data.rate_limits.five_hour;
|
|
85
|
+
if (!rl) return 'current ' + dotBar(0) + ' 0%';
|
|
86
|
+
|
|
87
|
+
const pct = Math.round(rl.used_percentage || 0);
|
|
88
|
+
const reset = formatResetTime(rl.resets_at);
|
|
89
|
+
return 'current ' + dotBar(pct) + ' ' + pct + '%' + (reset ? ' \u21BB ' + reset : '');
|
|
90
|
+
},
|
|
91
|
+
|
|
92
|
+
weekly_limit: function () {
|
|
93
|
+
const rl = data.rate_limits && data.rate_limits.seven_day;
|
|
94
|
+
if (!rl) return 'weekly ' + dotBar(0) + ' 0%';
|
|
95
|
+
|
|
96
|
+
const pct = Math.round(rl.used_percentage || 0);
|
|
97
|
+
const reset = formatResetTime(rl.resets_at);
|
|
98
|
+
return 'weekly ' + dotBar(pct) + ' ' + pct + '%' + (reset ? ' \u21BB ' + reset : '');
|
|
99
|
+
},
|
|
100
|
+
|
|
101
|
+
token_count: function () {
|
|
102
|
+
const inp = (data.context_window && data.context_window.total_input_tokens) || 0;
|
|
103
|
+
const out = (data.context_window && data.context_window.total_output_tokens) || 0;
|
|
104
|
+
return abbreviate(inp + out) + ' tok';
|
|
105
|
+
},
|
|
106
|
+
|
|
107
|
+
cost: function () {
|
|
108
|
+
const cost = (data.cost && data.cost.total_cost_usd) || 0;
|
|
109
|
+
return '$' + cost.toFixed(2);
|
|
110
|
+
},
|
|
111
|
+
|
|
112
|
+
burn_rate: function () {
|
|
113
|
+
try {
|
|
114
|
+
const usagePath = path.join(process.env.HOME || '', '.tokburn', 'usage.jsonl');
|
|
115
|
+
if (!fs.existsSync(usagePath)) return '';
|
|
116
|
+
const raw = fs.readFileSync(usagePath, 'utf8').trim();
|
|
117
|
+
if (!raw) return '';
|
|
118
|
+
const lines = raw.split('\n');
|
|
119
|
+
const today = new Date().toISOString().split('T')[0];
|
|
120
|
+
const todayEntries = [];
|
|
121
|
+
for (const l of lines) {
|
|
122
|
+
if (!l.startsWith('{"timestamp":"' + today)) continue;
|
|
123
|
+
try { todayEntries.push(JSON.parse(l)); } catch (_) {}
|
|
124
|
+
}
|
|
125
|
+
if (todayEntries.length < 2) return '';
|
|
126
|
+
const first = new Date(todayEntries[0].timestamp);
|
|
127
|
+
const last = new Date(todayEntries[todayEntries.length - 1].timestamp);
|
|
128
|
+
const elapsed = (last - first) / 60000;
|
|
129
|
+
if (elapsed <= 0) return '';
|
|
130
|
+
let total = 0;
|
|
131
|
+
for (const e of todayEntries) total += (e.input_tokens || 0) + (e.output_tokens || 0);
|
|
132
|
+
return '~' + abbreviate(Math.round(total / elapsed)) + '/min';
|
|
133
|
+
} catch (_) {
|
|
134
|
+
return '';
|
|
143
135
|
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
const elapsed = (last - first) / 60000;
|
|
148
|
-
if (elapsed <= 0) return '';
|
|
149
|
-
let total = 0;
|
|
150
|
-
for (const e of todayEntries) total += (e.input_tokens || 0) + (e.output_tokens || 0);
|
|
151
|
-
return '~' + abbreviate(Math.round(total / elapsed)) + '/min';
|
|
152
|
-
} catch (_) {
|
|
153
|
-
return '';
|
|
154
|
-
}
|
|
155
|
-
},
|
|
156
|
-
};
|
|
136
|
+
},
|
|
137
|
+
};
|
|
138
|
+
}
|
|
157
139
|
|
|
158
140
|
// ── Available modules metadata (used by init wizard) ────────────────────────────
|
|
159
141
|
|
|
@@ -175,16 +157,38 @@ const PRESETS = {
|
|
|
175
157
|
full: ['model_context', 'repo_branch', 'current_limit', 'weekly_limit', 'token_count', 'cost', 'burn_rate'],
|
|
176
158
|
};
|
|
177
159
|
|
|
178
|
-
// ──
|
|
160
|
+
// ── Main: only runs when executed directly (not require'd) ──────────────────────
|
|
179
161
|
|
|
180
162
|
if (require.main === module) {
|
|
163
|
+
let input = '';
|
|
164
|
+
try {
|
|
165
|
+
input = fs.readFileSync('/dev/stdin', 'utf8');
|
|
166
|
+
} catch (_) {}
|
|
167
|
+
|
|
168
|
+
let data = {};
|
|
169
|
+
try {
|
|
170
|
+
data = JSON.parse(input);
|
|
171
|
+
} catch (_) {}
|
|
172
|
+
|
|
173
|
+
// Load config
|
|
174
|
+
const configPath = path.join(process.env.HOME || process.env.USERPROFILE, '.tokburn', 'config.json');
|
|
175
|
+
let config = {};
|
|
176
|
+
try {
|
|
177
|
+
if (fs.existsSync(configPath)) {
|
|
178
|
+
config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
179
|
+
}
|
|
180
|
+
} catch (_) {}
|
|
181
|
+
|
|
182
|
+
const enabledModules = config.statusline_modules || PRESETS.recommended;
|
|
183
|
+
const modules = buildModules(data);
|
|
184
|
+
|
|
181
185
|
const outputLines = [];
|
|
182
186
|
const lineOneModules = [];
|
|
183
187
|
const extraLines = [];
|
|
184
188
|
|
|
185
189
|
for (const mod of enabledModules) {
|
|
186
|
-
if (!
|
|
187
|
-
const val =
|
|
190
|
+
if (!modules[mod]) continue;
|
|
191
|
+
const val = modules[mod]();
|
|
188
192
|
if (!val) continue;
|
|
189
193
|
|
|
190
194
|
if (mod === 'current_limit' || mod === 'weekly_limit') {
|
|
@@ -202,4 +206,4 @@ if (require.main === module) {
|
|
|
202
206
|
process.stdout.write(outputLines.join('\n'));
|
|
203
207
|
}
|
|
204
208
|
|
|
205
|
-
module.exports = { MODULE_LIST, PRESETS,
|
|
209
|
+
module.exports = { MODULE_LIST, PRESETS, buildModules };
|