tokburn 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/display.js ADDED
@@ -0,0 +1,342 @@
1
+ const { calculateCost } = require('./costs');
2
+
3
+ function fmt(n) {
4
+ return n.toLocaleString('en-US');
5
+ }
6
+
7
+ function fmtCost(n) {
8
+ return '$' + n.toFixed(2);
9
+ }
10
+
11
+ function pad(str, len, align) {
12
+ str = String(str);
13
+ if (align === 'right') {
14
+ return str.padStart(len);
15
+ }
16
+ return str.padEnd(len);
17
+ }
18
+
19
+ function formatToday(entries) {
20
+ const today = new Date().toISOString().split('T')[0];
21
+ let totalInput = 0;
22
+ let totalOutput = 0;
23
+ let totalCost = 0;
24
+ const byModel = {};
25
+
26
+ for (const e of entries) {
27
+ const inp = e.input_tokens || 0;
28
+ const out = e.output_tokens || 0;
29
+ totalInput += inp;
30
+ totalOutput += out;
31
+ const cost = calculateCost(e.model, inp, out);
32
+ totalCost += cost;
33
+
34
+ const model = e.model || 'unknown';
35
+ if (!byModel[model]) {
36
+ byModel[model] = { input: 0, output: 0, cost: 0 };
37
+ }
38
+ byModel[model].input += inp;
39
+ byModel[model].output += out;
40
+ byModel[model].cost += cost;
41
+ }
42
+
43
+ const totalTokens = totalInput + totalOutput;
44
+ const lines = [];
45
+
46
+ lines.push('');
47
+ lines.push(` tokburn \u2014 Today (${today})`);
48
+ lines.push(' ' + '\u2500'.repeat(39));
49
+ lines.push(` Total Tokens \u2502 ${fmt(totalTokens)}`);
50
+ lines.push(` Input \u2502 ${fmt(totalInput)}`);
51
+ lines.push(` Output \u2502 ${fmt(totalOutput)}`);
52
+ lines.push(` Requests \u2502 ${fmt(entries.length)}`);
53
+ lines.push(` Est. Cost \u2502 ${fmtCost(totalCost)}`);
54
+ lines.push('');
55
+
56
+ const models = Object.keys(byModel).sort((a, b) => byModel[b].cost - byModel[a].cost);
57
+ if (models.length > 0) {
58
+ lines.push(' By Model:');
59
+ for (const model of models) {
60
+ const m = byModel[model];
61
+ const name = pad(model, 22);
62
+ lines.push(` ${name}\u2502 ${pad(fmt(m.input), 10, 'right')} in \u2502 ${pad(fmt(m.output), 10, 'right')} out \u2502 ${fmtCost(m.cost)}`);
63
+ }
64
+ lines.push('');
65
+ }
66
+
67
+ if (entries.length === 0) {
68
+ lines.push(' No usage recorded today.');
69
+ lines.push('');
70
+ }
71
+
72
+ return lines.join('\n');
73
+ }
74
+
75
+ function formatWeek(entriesByDay) {
76
+ const days = Object.keys(entriesByDay).sort();
77
+ const COL = { date: 12, input: 10, output: 10, total: 10, cost: 8 };
78
+ const WIDTH = COL.date + COL.input + COL.output + COL.total + COL.cost + 8; // separators
79
+
80
+ const lines = [];
81
+ lines.push('');
82
+ lines.push(' tokburn \u2014 Last 7 Days');
83
+ lines.push(' ' + '\u2500'.repeat(WIDTH));
84
+ lines.push(
85
+ ' ' +
86
+ pad('Date', COL.date) + '\u2502 ' +
87
+ pad('Input', COL.input, 'right') + ' \u2502 ' +
88
+ pad('Output', COL.output, 'right') + ' \u2502 ' +
89
+ pad('Total', COL.total, 'right') + ' \u2502 ' +
90
+ pad('Cost', COL.cost, 'right')
91
+ );
92
+ lines.push(' ' + '\u2500'.repeat(WIDTH));
93
+
94
+ let grandInput = 0;
95
+ let grandOutput = 0;
96
+ let grandCost = 0;
97
+
98
+ for (const day of days) {
99
+ const entries = entriesByDay[day];
100
+ let dayInput = 0;
101
+ let dayOutput = 0;
102
+ let dayCost = 0;
103
+
104
+ for (const e of entries) {
105
+ const inp = e.input_tokens || 0;
106
+ const out = e.output_tokens || 0;
107
+ dayInput += inp;
108
+ dayOutput += out;
109
+ dayCost += calculateCost(e.model, inp, out);
110
+ }
111
+
112
+ grandInput += dayInput;
113
+ grandOutput += dayOutput;
114
+ grandCost += dayCost;
115
+
116
+ const dayTotal = dayInput + dayOutput;
117
+ lines.push(
118
+ ' ' +
119
+ pad(day, COL.date) + '\u2502 ' +
120
+ pad(fmt(dayInput), COL.input, 'right') + ' \u2502 ' +
121
+ pad(fmt(dayOutput), COL.output, 'right') + ' \u2502 ' +
122
+ pad(fmt(dayTotal), COL.total, 'right') + ' \u2502 ' +
123
+ pad(fmtCost(dayCost), COL.cost, 'right')
124
+ );
125
+ }
126
+
127
+ lines.push(' ' + '\u2500'.repeat(WIDTH));
128
+
129
+ const grandTotal = grandInput + grandOutput;
130
+ lines.push(
131
+ ' ' +
132
+ pad('Total', COL.date) + '\u2502 ' +
133
+ pad(fmt(grandInput), COL.input, 'right') + ' \u2502 ' +
134
+ pad(fmt(grandOutput), COL.output, 'right') + ' \u2502 ' +
135
+ pad(fmt(grandTotal), COL.total, 'right') + ' \u2502 ' +
136
+ pad(fmtCost(grandCost), COL.cost, 'right')
137
+ );
138
+ lines.push('');
139
+
140
+ return lines.join('\n');
141
+ }
142
+
143
+ function formatStatus(running, todaySummary) {
144
+ const lines = [];
145
+ lines.push('');
146
+ if (running) {
147
+ lines.push(' tokburn proxy: \x1b[32m\u25CF running\x1b[0m');
148
+ } else {
149
+ lines.push(' tokburn proxy: \x1b[90m\u25CB stopped\x1b[0m');
150
+ }
151
+
152
+ if (todaySummary) {
153
+ const totalTokens = (todaySummary.input || 0) + (todaySummary.output || 0);
154
+ lines.push(` Today: ${fmt(totalTokens)} tokens (${fmt(todaySummary.requests || 0)} requests) \u2022 ${fmtCost(todaySummary.cost || 0)}`);
155
+ } else {
156
+ lines.push(' Today: no usage recorded');
157
+ }
158
+ lines.push('');
159
+
160
+ return lines.join('\n');
161
+ }
162
+
163
+ function startLiveTUI() {
164
+ const store = require('./store');
165
+
166
+ const ESC = '\x1b';
167
+ const CSI = ESC + '[';
168
+
169
+ // Switch to alternate screen, hide cursor
170
+ process.stdout.write(CSI + '?1049h');
171
+ process.stdout.write(CSI + '?25l');
172
+
173
+ function timeAgo(ts) {
174
+ const diff = Date.now() - new Date(ts).getTime();
175
+ const secs = Math.floor(diff / 1000);
176
+ if (secs < 60) return `${secs}s ago`;
177
+ const mins = Math.floor(secs / 60);
178
+ if (mins < 60) return `${mins}m ago`;
179
+ const hrs = Math.floor(mins / 60);
180
+ return `${hrs}h ago`;
181
+ }
182
+
183
+ function drawBox(content, width) {
184
+ const lines = [];
185
+ lines.push('\u250C' + '\u2500'.repeat(width - 2) + '\u2510');
186
+ for (const line of content) {
187
+ const stripped = line.replace(/\x1b\[[0-9;]*m/g, '');
188
+ const padding = Math.max(0, width - 4 - stripped.length);
189
+ lines.push('\u2502 ' + line + ' '.repeat(padding) + ' \u2502');
190
+ }
191
+ lines.push('\u2514' + '\u2500'.repeat(width - 2) + '\u2518');
192
+ return lines;
193
+ }
194
+
195
+ function dotBar(pct, count) {
196
+ // Render dot indicators like: ●●●●○○○○○
197
+ count = count || 10;
198
+ const filled = Math.round((pct / 100) * count);
199
+ const empty = count - filled;
200
+ return '\u25CF'.repeat(filled) + '\u25CB'.repeat(empty);
201
+ }
202
+
203
+ function colorForPct(pct) {
204
+ if (pct >= 80) return '\x1b[31m'; // red
205
+ if (pct >= 50) return '\x1b[33m'; // amber
206
+ return '\x1b[32m'; // green
207
+ }
208
+
209
+ function render() {
210
+ const entries = store.getToday();
211
+ const { getConfig } = require('./config');
212
+ const conf = getConfig();
213
+ const plan = conf.plan || 'pro';
214
+ const limits = conf.limits || {};
215
+ const limit = (limits[plan] && limits[plan].estimated_tokens) || 500000;
216
+
217
+ const now = new Date();
218
+ const timestamp = now.toLocaleTimeString();
219
+
220
+ let totalInput = 0;
221
+ let totalOutput = 0;
222
+ let totalCost = 0;
223
+
224
+ for (const e of entries) {
225
+ totalInput += e.input_tokens || 0;
226
+ totalOutput += e.output_tokens || 0;
227
+ totalCost += calculateCost(e.model, e.input_tokens || 0, e.output_tokens || 0);
228
+ }
229
+
230
+ const totalTokens = totalInput + totalOutput;
231
+ const usagePct = Math.min(100, (totalTokens / limit) * 100);
232
+
233
+ // Burn rate
234
+ let burnRate = 0;
235
+ let timeRemaining = '';
236
+ if (entries.length > 0) {
237
+ const first = new Date(entries[0].timestamp);
238
+ const elapsed = (now - first) / 60000;
239
+ if (elapsed > 0) {
240
+ burnRate = Math.round(totalTokens / elapsed);
241
+ const tokensLeft = limit - totalTokens;
242
+ if (burnRate > 0 && tokensLeft > 0) {
243
+ const minsLeft = Math.round(tokensLeft / burnRate);
244
+ timeRemaining = minsLeft >= 60
245
+ ? Math.floor(minsLeft / 60) + 'hr ' + (minsLeft % 60) + 'min remaining'
246
+ : minsLeft + 'min remaining';
247
+ }
248
+ }
249
+ }
250
+
251
+ // Activity indicator
252
+ let active = false;
253
+ if (entries.length > 0) {
254
+ const last = new Date(entries[entries.length - 1].timestamp);
255
+ active = (now - last) < 5000;
256
+ }
257
+
258
+ const activityDot = active
259
+ ? '\x1b[32m\u25CF\x1b[0m'
260
+ : '\x1b[90m\u25CF\x1b[0m';
261
+
262
+ const pctColor = colorForPct(usagePct);
263
+
264
+ const content = [];
265
+ content.push(`\x1b[1mtokburn live\x1b[0m ${activityDot} ${timestamp}`);
266
+ content.push('');
267
+
268
+ // Progress bar with dots
269
+ const dots = dotBar(usagePct, 30);
270
+ content.push(` ${pctColor}${dots}\x1b[0m`);
271
+ content.push(` ${pctColor}${Math.round(usagePct)}% of 5hr limit\x1b[0m ${timeRemaining ? '~' + timeRemaining : ''}`);
272
+ content.push('');
273
+
274
+ content.push(` Input Tokens ${pad(fmt(totalInput), 14, 'right')}`);
275
+ content.push(` Output Tokens ${pad(fmt(totalOutput), 14, 'right')}`);
276
+ content.push(` Total Tokens ${pad(fmt(totalTokens), 14, 'right')}`);
277
+ content.push(` Requests ${pad(fmt(entries.length), 14, 'right')}`);
278
+ content.push('');
279
+ content.push(` Burn Rate ${pad(fmt(burnRate) + ' tok/min', 14, 'right')}`);
280
+ content.push(` Est. Cost ${pad(fmtCost(totalCost), 14, 'right')}`);
281
+ content.push('');
282
+
283
+ // Last 5 requests
284
+ content.push('\x1b[1m Recent Requests\x1b[0m');
285
+ content.push(' ' + '\u2500'.repeat(42));
286
+ const last5 = entries.slice(-5).reverse();
287
+ if (last5.length === 0) {
288
+ content.push(' (none yet)');
289
+ } else {
290
+ for (const e of last5) {
291
+ const model = (e.model || 'unknown').replace('claude-', '').substring(0, 14);
292
+ const tokens = (e.input_tokens || 0) + (e.output_tokens || 0);
293
+ const ago = timeAgo(e.timestamp);
294
+ content.push(` ${pad(model, 16)}${pad(fmt(tokens) + ' tok', 12, 'right')} ${ago}`);
295
+ }
296
+ }
297
+
298
+ const WIDTH = 52;
299
+ const box = drawBox(content, WIDTH);
300
+
301
+ // Move cursor to top-left and draw
302
+ process.stdout.write(CSI + 'H');
303
+ process.stdout.write(CSI + '2J');
304
+ process.stdout.write('\n');
305
+ for (const line of box) {
306
+ process.stdout.write(' ' + line + '\n');
307
+ }
308
+ process.stdout.write('\n \x1b[90mPress q to quit\x1b[0m\n');
309
+ }
310
+
311
+ render();
312
+ const interval = setInterval(render, 1000);
313
+
314
+ // Listen for keypress
315
+ if (process.stdin.isTTY) {
316
+ process.stdin.setRawMode(true);
317
+ process.stdin.resume();
318
+ process.stdin.setEncoding('utf8');
319
+ process.stdin.on('data', (key) => {
320
+ if (key === 'q' || key === 'Q' || key === '\x03') { // q or Ctrl+C
321
+ cleanup();
322
+ }
323
+ });
324
+ }
325
+
326
+ function cleanup() {
327
+ clearInterval(interval);
328
+ // Show cursor, switch back from alternate screen
329
+ process.stdout.write(CSI + '?25h');
330
+ process.stdout.write(CSI + '?1049l');
331
+ if (process.stdin.isTTY) {
332
+ process.stdin.setRawMode(false);
333
+ }
334
+ process.exit(0);
335
+ }
336
+
337
+ // Handle SIGINT gracefully
338
+ process.on('SIGINT', cleanup);
339
+ process.on('SIGTERM', cleanup);
340
+ }
341
+
342
+ module.exports = { formatToday, formatWeek, formatStatus, startLiveTUI };
package/init.js ADDED
@@ -0,0 +1,260 @@
1
+ /**
2
+ * tokburn — init.js
3
+ * Interactive setup wizard for Claude Code integration.
4
+ * Configures: plan limits, proxy daemon, shell env var, status line.
5
+ */
6
+
7
+ const fs = require('fs');
8
+ const path = require('path');
9
+ const readline = require('readline');
10
+ const { getConfig, setConfig, getTokburnDir } = require('./config');
11
+ const { startDaemon, isRunning } = require('./proxy');
12
+
13
+ const PLANS = {
14
+ pro: { window_hours: 5, estimated_tokens: 500000 },
15
+ max: { window_hours: 5, estimated_tokens: 2000000 },
16
+ api: { window_hours: 24, estimated_tokens: 10000000 },
17
+ };
18
+
19
+ async function runInit() {
20
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
21
+ const ask = (q) => new Promise((resolve) => rl.question(q, resolve));
22
+ const home = process.env.HOME || process.env.USERPROFILE;
23
+
24
+ console.log('');
25
+ console.log(' tokburn setup');
26
+ console.log(' ' + '\u2500'.repeat(35));
27
+ console.log('');
28
+
29
+ // ── Detect environment ──────────────────────────────────────────────────────
30
+
31
+ const shell = path.basename(process.env.SHELL || 'bash');
32
+ const rcFile = shell === 'zsh' ? '.zshrc' : shell === 'fish' ? '.config/fish/config.fish' : '.bashrc';
33
+ const rcPath = path.join(home, rcFile);
34
+ const claudeDir = path.join(home, '.claude');
35
+ const claudeSettings = path.join(claudeDir, 'settings.json');
36
+ const hasClaudeCode = fs.existsSync(claudeDir);
37
+
38
+ console.log(' Detected: ' + shell + ' shell' + (hasClaudeCode ? ', Claude Code installed' : ''));
39
+ console.log('');
40
+
41
+ // ── Step 1: Plan ────────────────────────────────────────────────────────────
42
+
43
+ console.log(' [1/4] Which Claude plan are you on?');
44
+ console.log('');
45
+ console.log(' 1) Pro ~500K tokens / 5hr window');
46
+ console.log(' 2) Max ~2M tokens / 5hr window');
47
+ console.log(' 3) API only (no plan limits)');
48
+ console.log('');
49
+ const planChoice = (await ask(' > ')).trim();
50
+ const plan = planChoice === '2' ? 'max' : planChoice === '3' ? 'api' : 'pro';
51
+ console.log('');
52
+
53
+ // ── Step 2: Proxy ───────────────────────────────────────────────────────────
54
+
55
+ console.log(' [2/4] Start the proxy daemon?');
56
+ console.log(' Enables per-request tracking for detailed breakdowns.');
57
+ console.log('');
58
+ console.log(' 1) Yes, start now');
59
+ console.log(' 2) No, skip');
60
+ console.log('');
61
+ const proxyChoice = (await ask(' > ')).trim();
62
+ const wantProxy = proxyChoice !== '2';
63
+ console.log('');
64
+
65
+ if (wantProxy) {
66
+ if (isRunning()) {
67
+ console.log(' Proxy already running.');
68
+ } else {
69
+ const result = startDaemon();
70
+ console.log(' ' + result.message);
71
+ }
72
+ console.log('');
73
+ }
74
+
75
+ // ── Step 3: Shell config ────────────────────────────────────────────────────
76
+
77
+ let addedShellConfig = false;
78
+
79
+ if (wantProxy) {
80
+ console.log(' [3/4] Add ANTHROPIC_BASE_URL to ~/' + rcFile + '?');
81
+ console.log(' Required for the proxy to intercept API calls.');
82
+ console.log('');
83
+ console.log(' 1) Yes, add it');
84
+ console.log(' 2) No, I\'ll do it manually');
85
+ console.log('');
86
+ const shellChoice = (await ask(' > ')).trim();
87
+ console.log('');
88
+
89
+ if (shellChoice !== '2') {
90
+ const config = getConfig();
91
+ const envLine = `export ANTHROPIC_BASE_URL=http://127.0.0.1:${config.port}`;
92
+ const existing = fs.existsSync(rcPath) ? fs.readFileSync(rcPath, 'utf8') : '';
93
+
94
+ if (existing.includes('ANTHROPIC_BASE_URL')) {
95
+ console.log(' ANTHROPIC_BASE_URL already in ~/' + rcFile);
96
+ } else {
97
+ fs.appendFileSync(rcPath, '\n# tokburn proxy\n' + envLine + '\n');
98
+ console.log(' Added to ~/' + rcFile);
99
+ addedShellConfig = true;
100
+ }
101
+ console.log('');
102
+ }
103
+ } else {
104
+ console.log(' [3/4] Skipping shell config (no proxy).');
105
+ console.log('');
106
+ }
107
+
108
+ // ── Step 4: Claude Code status line ─────────────────────────────────────────
109
+
110
+ if (hasClaudeCode) {
111
+ console.log(' [4/4] Configure Claude Code status line?');
112
+ console.log('');
113
+ console.log(' 1) Recommended model | ctx% | repo | limits | cost');
114
+ console.log(' 2) Minimal model | current rate limit');
115
+ console.log(' 3) Full everything including burn rate');
116
+ console.log(' 4) Custom pick your own modules');
117
+ console.log(' 5) Skip');
118
+ console.log('');
119
+ const ccChoice = (await ask(' > ')).trim();
120
+ console.log('');
121
+
122
+ let selectedModules = null;
123
+
124
+ if (ccChoice === '1' || ccChoice === '') {
125
+ const { PRESETS } = require('./statusline');
126
+ selectedModules = PRESETS.recommended;
127
+ } else if (ccChoice === '2') {
128
+ const { PRESETS } = require('./statusline');
129
+ selectedModules = PRESETS.minimal;
130
+ } else if (ccChoice === '3') {
131
+ const { PRESETS } = require('./statusline');
132
+ selectedModules = PRESETS.full;
133
+ } else if (ccChoice === '4') {
134
+ // Custom picker
135
+ const { MODULE_LIST, PRESETS } = require('./statusline');
136
+ const enabled = new Set(PRESETS.recommended); // start with recommended
137
+
138
+ let picking = true;
139
+ while (picking) {
140
+ console.log(' Status line modules:');
141
+ console.log('');
142
+ for (let i = 0; i < MODULE_LIST.length; i++) {
143
+ const m = MODULE_LIST[i];
144
+ const check = enabled.has(m.key) ? 'x' : ' ';
145
+ const num = String(i + 1);
146
+ const label = m.label.padEnd(22);
147
+ console.log(' [' + check + '] ' + num + '. ' + label + m.example);
148
+ }
149
+ console.log('');
150
+ console.log(' Toggle a number (1-' + MODULE_LIST.length + '), or press enter to confirm:');
151
+ const toggle = (await ask(' > ')).trim();
152
+
153
+ if (toggle === '') {
154
+ picking = false;
155
+ } else {
156
+ const idx = parseInt(toggle, 10) - 1;
157
+ if (idx >= 0 && idx < MODULE_LIST.length) {
158
+ const key = MODULE_LIST[idx].key;
159
+ if (enabled.has(key)) {
160
+ enabled.delete(key);
161
+ } else {
162
+ enabled.add(key);
163
+ }
164
+ }
165
+ console.log('');
166
+ }
167
+ }
168
+
169
+ selectedModules = MODULE_LIST.filter(m => enabled.has(m.key)).map(m => m.key);
170
+ console.log('');
171
+ }
172
+
173
+ if (selectedModules) {
174
+ // Save module selection to config
175
+ setConfig({ statusline_modules: selectedModules });
176
+
177
+ // Install status line script (Node.js version)
178
+ const srcScript = path.join(__dirname, 'statusline.js');
179
+ const destScript = path.join(claudeDir, 'tokburn-statusline.js');
180
+
181
+ if (fs.existsSync(srcScript)) {
182
+ fs.copyFileSync(srcScript, destScript);
183
+ fs.chmodSync(destScript, '755');
184
+ }
185
+
186
+ // Update Claude Code settings
187
+ let settings = {};
188
+ if (fs.existsSync(claudeSettings)) {
189
+ try {
190
+ settings = JSON.parse(fs.readFileSync(claudeSettings, 'utf8'));
191
+ } catch (_) {}
192
+ }
193
+
194
+ settings.statusLine = {
195
+ type: 'command',
196
+ command: destScript,
197
+ };
198
+
199
+ if (!fs.existsSync(claudeDir)) {
200
+ fs.mkdirSync(claudeDir, { recursive: true });
201
+ }
202
+ fs.writeFileSync(claudeSettings, JSON.stringify(settings, null, 2) + '\n');
203
+
204
+ console.log(' Status line configured with ' + selectedModules.length + ' modules.');
205
+
206
+ // Show preview
207
+ console.log('');
208
+ console.log(' Preview:');
209
+ const lineOne = [];
210
+ for (const mod of selectedModules) {
211
+ if (mod === 'current_limit') continue;
212
+ if (mod === 'weekly_limit') continue;
213
+ const { MODULE_LIST } = require('./statusline');
214
+ const info = MODULE_LIST.find(m => m.key === mod);
215
+ if (info) lineOne.push(info.example);
216
+ }
217
+ if (lineOne.length > 0) {
218
+ console.log(' ' + lineOne.join(' \u2502 '));
219
+ }
220
+ if (selectedModules.includes('current_limit')) {
221
+ const { MODULE_LIST } = require('./statusline');
222
+ const info = MODULE_LIST.find(m => m.key === 'current_limit');
223
+ console.log(' current ' + info.example);
224
+ }
225
+ if (selectedModules.includes('weekly_limit')) {
226
+ const { MODULE_LIST } = require('./statusline');
227
+ const info = MODULE_LIST.find(m => m.key === 'weekly_limit');
228
+ console.log(' weekly ' + info.example);
229
+ }
230
+ console.log('');
231
+ }
232
+ } else {
233
+ console.log(' [4/4] Claude Code not detected, skipping.');
234
+ console.log('');
235
+ }
236
+
237
+ // ── Save config ─────────────────────────────────────────────────────────────
238
+
239
+ setConfig({ plan: plan, limits: PLANS });
240
+
241
+ // ── Done ────────────────────────────────────────────────────────────────────
242
+
243
+ console.log(' ' + '\u2500'.repeat(35));
244
+ console.log(' Done. tokburn is ready.');
245
+ console.log('');
246
+ console.log(' Commands:');
247
+ console.log(' tokburn status check proxy + today\'s usage');
248
+ console.log(' tokburn today detailed breakdown by model');
249
+ console.log(' tokburn live real-time TUI dashboard');
250
+ console.log('');
251
+
252
+ if (addedShellConfig) {
253
+ console.log(' Run `source ~/' + rcFile + '` to activate, or open a new terminal.');
254
+ console.log('');
255
+ }
256
+
257
+ rl.close();
258
+ }
259
+
260
+ module.exports = { runInit };
package/package.json ADDED
@@ -0,0 +1,60 @@
1
+ {
2
+ "name": "tokburn",
3
+ "version": "0.1.0",
4
+ "description": "See exactly how fast you're burning tokens and money across Claude Code sessions",
5
+ "main": "cli.js",
6
+ "bin": {
7
+ "tokburn": "cli.js"
8
+ },
9
+ "files": [
10
+ "cli.js",
11
+ "proxy.js",
12
+ "tracker.js",
13
+ "store.js",
14
+ "display.js",
15
+ "costs.js",
16
+ "config.js",
17
+ "init.js",
18
+ "card.js",
19
+ "statusline.js",
20
+ "statusline.sh",
21
+ "README.md",
22
+ "LICENSE"
23
+ ],
24
+ "keywords": [
25
+ "claude",
26
+ "claude-code",
27
+ "anthropic",
28
+ "tokens",
29
+ "usage",
30
+ "cli",
31
+ "proxy",
32
+ "token-tracker",
33
+ "llm",
34
+ "ai",
35
+ "status-line"
36
+ ],
37
+ "scripts": {
38
+ "benchmark": "node test/benchmark.js",
39
+ "test": "node test/benchmark.js && node test/test-card.js",
40
+ "test:card": "node test/test-card.js",
41
+ "prepublishOnly": "npm test"
42
+ },
43
+ "repository": {
44
+ "type": "git",
45
+ "url": "git+https://github.com/patheonsceo/tokburn.git",
46
+ "directory": "tokburn-cli"
47
+ },
48
+ "homepage": "https://github.com/patheonsceo/tokburn#readme",
49
+ "bugs": {
50
+ "url": "https://github.com/patheonsceo/tokburn/issues"
51
+ },
52
+ "author": "pantheonsceo",
53
+ "license": "MIT",
54
+ "engines": {
55
+ "node": ">=18.0.0"
56
+ },
57
+ "dependencies": {
58
+ "commander": "^12.0.0"
59
+ }
60
+ }