@winston.wan/burn-your-money 2.0.4 → 2.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/install.js CHANGED
@@ -2,6 +2,8 @@ const fs = require('fs');
2
2
  const path = require('path');
3
3
  const os = require('os');
4
4
  const { execSync } = require('child_process');
5
+ const https = require('https');
6
+ const { createWriteStream, existsSync, mkdirSync } = fs;
5
7
 
6
8
  // ANSI colors for console output
7
9
  const colors = {
@@ -34,36 +36,119 @@ function getHomeDir() {
34
36
  return os.homedir();
35
37
  }
36
38
 
37
- function checkDependencies() {
39
+ // Download file following redirects
40
+ function downloadFile(url, destPath) {
41
+ return new Promise((resolve, reject) => {
42
+ const download = (url) => {
43
+ https.get(url, (response) => {
44
+ // Handle redirects (301, 302, 307, 308)
45
+ if ([301, 302, 307, 308].includes(response.statusCode)) {
46
+ const redirectUrl = response.headers.location;
47
+ if (redirectUrl) {
48
+ download(redirectUrl);
49
+ return;
50
+ }
51
+ }
52
+
53
+ // Handle errors
54
+ if (response.statusCode !== 200) {
55
+ reject(new Error(`Failed to download: HTTP ${response.statusCode}`));
56
+ return;
57
+ }
58
+
59
+ const file = createWriteStream(destPath);
60
+ response.pipe(file);
61
+
62
+ file.on('finish', () => {
63
+ file.close();
64
+ resolve();
65
+ });
66
+
67
+ file.on('error', (err) => {
68
+ fs.unlink(destPath, () => {});
69
+ reject(err);
70
+ });
71
+ }).on('error', (err) => {
72
+ reject(err);
73
+ });
74
+ };
75
+
76
+ download(url);
77
+ });
78
+ }
79
+
80
+ // Install jq for Windows by downloading binary
81
+ async function installJqWindows() {
82
+ const home = getHomeDir();
83
+ const binDir = path.join(home, '.claude', 'bin');
84
+
85
+ // Create bin directory
86
+ if (!existsSync(binDir)) {
87
+ mkdirSync(binDir, { recursive: true });
88
+ }
89
+
90
+ const jqPath = path.join(binDir, 'jq.exe');
91
+ const jqUrl = 'https://github.com/jqlang/jq/releases/latest/download/jq-windows-amd64.exe';
92
+
93
+ log(`Downloading jq to ${jqPath}...`, colors.cyan);
94
+
95
+ try {
96
+ await downloadFile(jqUrl, jqPath);
97
+ success("jq downloaded successfully!");
98
+ return jqPath;
99
+ } catch (err) {
100
+ error(`Failed to download jq: ${err.message}`);
101
+ throw err;
102
+ }
103
+ }
104
+
105
+ // Install jq for macOS/Linux
106
+ function installJqUnix() {
107
+ try {
108
+ log("Attempting to install jq via brew...", colors.cyan);
109
+ execSync('brew install jq', { stdio: 'inherit' });
110
+ success("jq installed successfully!");
111
+ return 'jq';
112
+ } catch (brewError) {
113
+ try {
114
+ log("Attempting to install jq via apt...", colors.cyan);
115
+ execSync('sudo apt-get install -y jq', { stdio: 'inherit' });
116
+ success("jq installed successfully!");
117
+ return 'jq';
118
+ } catch (aptError) {
119
+ throw new Error("Could not auto-install jq. Please install manually: brew install jq or sudo apt-get install jq");
120
+ }
121
+ }
122
+ }
123
+
124
+ async function checkDependencies() {
38
125
  log("Checking dependencies...", colors.cyan);
39
126
 
127
+ let jqPath = 'jq'; // Default to system jq
128
+
40
129
  // Check for jq
41
130
  try {
42
131
  execSync('jq --version', { stdio: 'ignore' });
43
132
  success("jq is installed");
44
133
  } catch (e) {
45
- warning("jq not found! Attempting valid auto-installation...");
134
+ warning("jq not found! Attempting auto-installation...");
46
135
 
47
136
  try {
48
137
  if (os.platform() === 'win32') {
49
- log("Attempting to install jq via winget...", colors.cyan);
50
- // silent install might need admin, but let's try
51
- execSync('winget install jqlang.jq --accept-source-agreements --accept-package-agreements', { stdio: 'inherit' });
52
- success("jq installed successfully!");
138
+ jqPath = await installJqWindows();
53
139
  } else if (os.platform() === 'darwin') {
54
- log("Attempting to install jq via brew...", colors.cyan);
55
- execSync('brew install jq', { stdio: 'inherit' });
56
- success("jq installed successfully!");
140
+ installJqUnix();
57
141
  } else {
58
- // Linux: too many variants (apt, dnf, pacman...), just warn
59
- throw new Error("Linux auto-install not supported");
142
+ // Linux
143
+ installJqUnix();
60
144
  }
61
145
  } catch (installError) {
62
146
  error("Auto-installation failed.");
63
147
  log(" Please install jq manually:", colors.yellow);
64
- log(" Windows: winget install jqlang.jq", colors.yellow);
148
+ log(" Windows: Run installer again (will download automatically)", colors.yellow);
65
149
  log(" macOS: brew install jq", colors.yellow);
66
150
  log(" Linux: sudo apt-get install jq", colors.yellow);
151
+ throw installError;
67
152
  }
68
153
  }
69
154
 
@@ -74,6 +159,8 @@ function checkDependencies() {
74
159
  } catch (e) {
75
160
  warning("Claude Code not found in PATH.");
76
161
  }
162
+
163
+ return jqPath;
77
164
  }
78
165
 
79
166
  function createDirectories() {
@@ -120,7 +207,7 @@ function installScripts() {
120
207
  });
121
208
  }
122
209
 
123
- function configureSettings() {
210
+ function configureSettings(jqPath) {
124
211
  log("Configuring Claude Code...", colors.cyan);
125
212
  const home = getHomeDir();
126
213
  const settingsFile = path.join(home, '.claude', 'settings.json');
@@ -134,18 +221,35 @@ function configureSettings() {
134
221
  }
135
222
  }
136
223
 
137
- // Update statusLine config
138
- // We use forward slashes for paths in settings.json even on Windows for consistency in JSON
139
- // accessing existing wsl path if needed? No, standard path is fine.
140
- // The previous implementation used `~/.claude/statusline.sh`.
141
- // Claude might process `~`? Let's stick to what the bash script did: `~/.claude/statusline.sh`
142
-
143
224
  // Configure command based on platform
144
225
  let commandEnv = "~/.claude/statusline.sh";
145
226
 
146
227
  if (os.platform() === 'win32') {
147
- // On Windows, expand home directory and ensure forward slashes
148
- const scriptPath = path.join(home, '.claude', 'statusline.sh').replace(/\\/g, '/');
228
+ // Get the raw Windows path first
229
+ const scriptPathWindows = path.join(home, '.claude', 'statusline.sh');
230
+
231
+ // Convert Windows jq path to Git Bash path format
232
+ // C:\Users\... -> /c/Users/... (lowercase drive letter)
233
+ let jqPathForBash = 'jq';
234
+ if (jqPath && jqPath !== 'jq' && /^[A-Z]:/.test(jqPath)) {
235
+ jqPathForBash = jqPath.replace(/^([A-Z]):\\/, (match, drive) => `/${drive.toLowerCase()}/`).replace(/\\/g, '/');
236
+ }
237
+
238
+ // Also convert scriptPath to Git Bash format for sourcing
239
+ const scriptPathGitBash = scriptPathWindows.replace(/^([A-Z]):\\/, (match, drive) => `/${drive.toLowerCase()}/`).replace(/\\/g, '/');
240
+
241
+ // Create environment variable for jq path in the script
242
+ const wrapperScriptPath = path.join(home, '.claude', 'statusline-wrapper.sh');
243
+ const wrapperContent = `#!/bin/bash
244
+ # Export JQ_PATH for the scripts to use
245
+ export JQ_PATH="${jqPathForBash}"
246
+ # Source the statusline script to preserve environment
247
+ . "${scriptPathGitBash}"
248
+ `;
249
+ fs.writeFileSync(wrapperScriptPath, wrapperContent);
250
+ if (os.platform() !== 'win32') {
251
+ fs.chmodSync(wrapperScriptPath, '755');
252
+ }
149
253
 
150
254
  // Robust bash detection: try Git Bash first, then fallback to 'bash'
151
255
  let bashPath = 'bash';
@@ -176,7 +280,18 @@ function configureSettings() {
176
280
  }
177
281
  }
178
282
 
179
- commandEnv = `${bashPath} "${scriptPath}"`;
283
+ const wrapperPathGitBash = wrapperScriptPath.replace(/^([A-Z]):\\/, (match, drive) => `/${drive.toLowerCase()}/`).replace(/\\/g, '/');
284
+ commandEnv = `${bashPath} "${wrapperPathGitBash}"`;
285
+ } else {
286
+ // On Unix, just set the JQ_PATH in the wrapper script
287
+ const wrapperScriptPath = path.join(home, '.claude', 'statusline-wrapper.sh');
288
+ const wrapperContent = `#!/bin/bash
289
+ export JQ_PATH="${jqPath}"
290
+ exec ~/.claude/statusline.sh
291
+ `;
292
+ fs.writeFileSync(wrapperScriptPath, wrapperContent);
293
+ fs.chmodSync(wrapperScriptPath, '755');
294
+ commandEnv = "bash ~/.claude/statusline-wrapper.sh";
180
295
  }
181
296
 
182
297
  settings.statusLine = {
@@ -190,16 +305,130 @@ function configureSettings() {
190
305
  } catch (e) {
191
306
  error(`Failed to update settings.json: ${e.message}`);
192
307
  }
308
+
309
+ // 安装自定义命令文件到 ~/.claude/commands/
310
+ log("Installing custom commands...", colors.cyan);
311
+ const commandsDir = path.join(home, '.claude', 'commands');
312
+
313
+ // 确保 commands 目录存在
314
+ if (!fs.existsSync(commandsDir)) {
315
+ fs.mkdirSync(commandsDir, { recursive: true });
316
+ }
317
+
318
+ const commands = [
319
+ {
320
+ name: 'burn-your-money-stats.md',
321
+ content: `---
322
+ name: burn-your-money-stats
323
+ description: 📊 查看 token 使用趋势图
324
+ ---
325
+
326
+ # Token 使用趋势图
327
+
328
+ 请执行以下 bash 命令查看过去 7 天的 token 使用趋势图:
329
+
330
+ \\\`\\\`\\\`bash
331
+ bash ~/.claude/scripts/token-history.sh chart
332
+ \\\`\\\`\\\`
333
+ `
334
+ },
335
+ {
336
+ name: 'burn-your-money-today.md',
337
+ content: `---
338
+ name: burn-your-money-today
339
+ description: 📅 查看今日 token 使用情况
340
+ ---
341
+
342
+ # 今日 Token 使用情况
343
+
344
+ 请执行以下 bash 命令查看今日的 token 使用量:
345
+
346
+ \\\`\\\`\\\`bash
347
+ bash ~/.claude/scripts/token-history.sh today_tokens
348
+ \\\`\\\`\\\`
349
+ `
350
+ },
351
+ {
352
+ name: 'burn-your-money-week.md',
353
+ content: `---
354
+ name: burn-your-money-week
355
+ description: 📆 查看本周 token 使用情况
356
+ ---
357
+
358
+ # 本周 Token 使用情况
359
+
360
+ 请执行以下 bash 命令查看本周的 token 使用量:
361
+
362
+ \\\`\\\`\\\`bash
363
+ bash ~/.claude/scripts/token-history.sh week_tokens
364
+ \\\`\\\`\\\`
365
+ `
366
+ },
367
+ {
368
+ name: 'burn-your-money-month.md',
369
+ content: `---
370
+ name: burn-your-money-month
371
+ description: 🗓️ 查看本月 token 使用情况
372
+ ---
373
+
374
+ # 本月 Token 使用情况
375
+
376
+ 请执行以下 bash 命令查看本月的 token 使用量:
377
+
378
+ \\\`\\\`\\\`bash
379
+ bash ~/.claude/scripts/token-history.sh month_tokens
380
+ \\\`\\\`\\\`
381
+ `
382
+ },
383
+ {
384
+ name: 'burn-your-money-export.md',
385
+ content: `---
386
+ name: burn-your-money-export
387
+ description: 💾 导出 token 数据(JSON)
388
+ ---
389
+
390
+ # 导出 Token 数据
391
+
392
+ 请执行以下 bash 命令导出 token 数据为 JSON 格式:
393
+
394
+ \\\`\\\`\\\`bash
395
+ bash ~/.claude/scripts/token-history.sh export json
396
+ \\\`\\\`\\\`
397
+ `
398
+ },
399
+ {
400
+ name: 'burn-your-money-export-csv.md',
401
+ content: `---
402
+ name: burn-your-money-export-csv
403
+ description: 📄 导出 token 数据(CSV)
404
+ ---
405
+
406
+ # 导出 Token 数据 (CSV)
407
+
408
+ 请执行以下 bash 命令导出 token 数据为 CSV 格式:
409
+
410
+ \\\`\\\`\\\`bash
411
+ bash ~/.claude/scripts/token-history.sh export csv
412
+ \\\`\\\`\\\`
413
+ `
414
+ }
415
+ ];
416
+
417
+ commands.forEach(cmd => {
418
+ const cmdPath = path.join(commandsDir, cmd.name);
419
+ fs.writeFileSync(cmdPath, cmd.content);
420
+ success(`Installed ${cmd.name}`);
421
+ });
193
422
  }
194
423
 
195
- function main() {
424
+ async function main() {
196
425
  log("\n💸 Burn Your Money - Installer\n", colors.purple);
197
426
 
198
427
  try {
199
- checkDependencies();
428
+ const jqPath = await checkDependencies();
200
429
  createDirectories();
201
430
  installScripts();
202
- configureSettings();
431
+ configureSettings(jqPath);
203
432
 
204
433
  log("\n✅ Installation complete!", colors.green);
205
434
  log("Please restart Claude Code to see the status bar.\n", colors.cyan);
package/package.json CHANGED
@@ -1,13 +1,14 @@
1
1
  {
2
2
  "name": "@winston.wan/burn-your-money",
3
- "version": "2.0.4",
3
+ "version": "2.1.0",
4
4
  "description": "💸 Burn Your Money - 实时显示 Claude Code 的 token 消耗,看着你的钱包燃烧!",
5
5
  "main": "src/statusline.sh",
6
6
  "bin": {
7
7
  "burn-your-money": "./bin/burn-your-money"
8
8
  },
9
9
  "scripts": {
10
- "install": "node install.js",
10
+ "postinstall": "node install.js",
11
+ "uninstall": "node uninstall.js",
11
12
  "test": "bash ./test.sh"
12
13
  },
13
14
  "repository": {