@chongdashu/cc-statusline 1.2.2 โ†’ 1.2.4

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/CHANGELOG.md CHANGED
@@ -5,6 +5,39 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [Unreleased]
9
+
10
+ ### Added
11
+ - ๐Ÿ†• **Installation Location Choice** - Choose between global (`~/.claude`) or project-level (`./.claude`) installation
12
+ - ๐Ÿ”’ **Safe Installation** - Confirmation prompts before overwriting existing statusline.sh files
13
+ - ๐Ÿ›ก๏ธ **Settings Protection** - Smart settings.json updates that preserve existing configurations
14
+ - โš ๏ธ **Conflict Detection** - Warns when other statuslines are already configured
15
+ - โœ… **Better Error Handling** - Clear messages for cancelled installations and conflicts
16
+
17
+ ### Changed
18
+ - Installation prompt now includes location selection (global vs project)
19
+ - Default installation is project-level for safety
20
+ - Improved settings.json update logic to prevent accidental overwrites
21
+
22
+ ## [1.2.3] - 2025-08-20
23
+
24
+ ### Fixed
25
+ - ๐Ÿ”’ **Critical Process Spawning Fix** - Added file-based locking mechanism to prevent infinite ccusage process spawning
26
+ - โšก **Performance** - Implemented 3-second timeout for ccusage calls to prevent hanging
27
+ - ๐Ÿ›ก๏ธ **Stability** - Added PID tracking for stale lock detection and cleanup
28
+ - ๐Ÿ”ง **Cross-platform** - Multiple timeout strategies for Linux, macOS, and BSD compatibility
29
+
30
+ ### Contributors
31
+ - ๐Ÿ™ **Special thanks to [Jonathan Borgwing (@DevVig)](https://github.com/DevVig)** for identifying and implementing the critical process spawning fix ([#4](https://github.com/chongdashu/cc-statusline/pull/4))
32
+
33
+ ### Technical Details
34
+ - **File-based locking**: Uses `/tmp/ccusage_statusline.lock` directory as a mutex to ensure single execution
35
+ - **PID tracking**: Stores process ID in `/tmp/ccusage_statusline.pid` for stale lock detection
36
+ - **Graceful degradation**: Skips execution when locked instead of queuing (prevents pile-up)
37
+ - **Automatic cleanup**: Detects and removes stale locks from crashed processes using `kill -0`
38
+ - **Cross-platform timeouts**: Multiple timeout strategies (timeout/gtimeout/fallback) for all systems
39
+ - **Testing included**: Comprehensive test suite in `test/` directory to verify locking behavior
40
+
8
41
  ## [1.0.1] - 2025-08-13
9
42
 
10
43
  ### Added
package/README.md CHANGED
@@ -16,13 +16,17 @@
16
16
 
17
17
  ## โšก Quick Start
18
18
 
19
- **One command. Two questions. Custom statusline.**
19
+ **One command. Three questions. Custom statusline.**
20
20
 
21
21
  ```bash
22
22
  npx @chongdashu/cc-statusline@latest init
23
23
  ```
24
24
 
25
- That's it! Answer 2 simple questions, restart Claude Code, and enjoy your new statusline.
25
+ That's it! Answer a few simple questions, restart Claude Code, and enjoy your new statusline.
26
+
27
+ ### ๐Ÿ†• Installation Options (v1.2.4+)
28
+ - **๐Ÿ  Global Installation** (`~/.claude/`) - Use across all your projects
29
+ - **๐Ÿ“‚ Project Installation** (`./.claude/`) - Keep settings project-specific
26
30
 
27
31
  ## ๐ŸŽฏ Simple Setup
28
32
 
@@ -92,6 +96,12 @@ cc-statusline preview .claude/statusline.sh
92
96
  3. ๐Ÿ“Š **Shows** exactly what the output will look like
93
97
  4. โšก **Reports** performance metrics and functionality
94
98
 
99
+ ### Installation Safety Features (v1.2.4+)
100
+ - ๐Ÿ”’ **Safe Updates** - Never overwrites existing statuslines without confirmation
101
+ - ๐Ÿ›ก๏ธ **Settings Protection** - Preserves your existing settings.json configurations
102
+ - โš ๏ธ **Conflict Detection** - Warns when other statuslines are configured
103
+ - โœ… **Smart Defaults** - Project-level installation by default for safety
104
+
95
105
  ### Custom Installation
96
106
  ```bash
97
107
  # Generate to custom location
@@ -209,6 +219,45 @@ npm install && npm run build
209
219
 
210
220
  See our [Contributing Guide](CONTRIBUTING.md) for detailed information.
211
221
 
222
+ ## ๐Ÿงช Testing
223
+
224
+ ### Locking Mechanism Tests
225
+
226
+ The ccusage integration includes a robust file-based locking mechanism to prevent concurrent process spawning. Test this functionality:
227
+
228
+ **Single Test:**
229
+ ```bash
230
+ echo '{}' | ./test/test-statusline-with-lock.sh
231
+ ```
232
+
233
+ **Concurrent Test:**
234
+ ```bash
235
+ ./test/test-concurrent-locking.sh
236
+ ```
237
+
238
+ **Manual Concurrent Test:**
239
+ ```bash
240
+ # Spawn 10 concurrent processes
241
+ for i in {1..10}; do
242
+ echo '{}' | ./test/test-statusline-with-lock.sh &
243
+ done
244
+ ```
245
+
246
+ **Expected Behavior:**
247
+ - โœ… Only 1 process runs ccusage at a time
248
+ - โœ… Other processes skip gracefully (no pile-up)
249
+ - โœ… Lock files are properly cleaned up
250
+ - โœ… No hanging processes remain
251
+
252
+ **Verification Commands:**
253
+ ```bash
254
+ # Check for stale locks
255
+ ls /tmp/ccusage_statusline.* 2>/dev/null || echo "โœ… No locks remain"
256
+
257
+ # Monitor running processes
258
+ ps aux | grep ccusage | grep -v grep
259
+ ```
260
+
212
261
  ## ๐Ÿ“Š Stats
213
262
 
214
263
  <div align="center">
@@ -224,6 +273,22 @@ See our [Contributing Guide](CONTRIBUTING.md) for detailed information.
224
273
  - **[ccusage](https://github.com/ryoppippi/ccusage)** - Claude Code usage analytics (would not be possible with it!)
225
274
  - **[Claude Code](https://docs.anthropic.com/en/docs/claude-code)** - Official documentation
226
275
 
276
+ ## ๐Ÿ‘ฅ Contributors
277
+
278
+ We're grateful for all contributions that make cc-statusline better!
279
+
280
+ - **[Jonathan Borgwing (@DevVig)](https://github.com/DevVig)** - Critical performance fix for infinite ccusage process spawning ([#4](https://github.com/chongdashu/cc-statusline/pull/4))
281
+
282
+ ### How to Contribute
283
+
284
+ Want to see your name here? Check out our [Contributing Guide](CONTRIBUTING.md) and help make cc-statusline even better!
285
+
286
+ We welcome:
287
+ - ๐Ÿ› Bug fixes and performance improvements
288
+ - โœจ New features and enhancements
289
+ - ๐Ÿ“š Documentation improvements
290
+ - ๐Ÿงช Test coverage and quality assurance
291
+
227
292
  ## ๐Ÿ“ Changelog
228
293
 
229
294
  See [CHANGELOG.md](CHANGELOG.md) for detailed release history.
package/dist/index.js CHANGED
@@ -333,6 +333,16 @@ async function collectConfiguration() {
333
333
  name: "logging",
334
334
  message: "\n\u{1F4DD} Enable debug logging to .claude/statusline.log?",
335
335
  default: false
336
+ },
337
+ {
338
+ type: "list",
339
+ name: "installLocation",
340
+ message: "\n\u{1F4CD} Where would you like to install the statusline?",
341
+ choices: [
342
+ { name: "\u{1F3E0} Global (~/.claude) - Use across all projects", value: "global" },
343
+ { name: "\u{1F4C2} Project (./.claude) - Only for this project", value: "project" }
344
+ ],
345
+ default: "project"
336
346
  }
337
347
  ]);
338
348
  return {
@@ -343,7 +353,8 @@ async function collectConfiguration() {
343
353
  ccusageIntegration: true,
344
354
  // Always enabled since npx works
345
355
  logging: config.logging,
346
- customEmojis: false
356
+ customEmojis: false,
357
+ installLocation: config.installLocation
347
358
  };
348
359
  }
349
360
 
@@ -435,7 +446,59 @@ session_txt=""; session_pct=0; session_bar=""
435
446
  cost_usd=""; cost_per_hour=""; tpm=""; tot_tokens=""
436
447
 
437
448
  if command -v jq >/dev/null 2>&1; then
438
- blocks_output=$(npx ccusage@latest blocks --json 2>/dev/null || ccusage blocks --json 2>/dev/null)
449
+ # Implement file-based locking to prevent concurrent executions
450
+ LOCK_FILE="/tmp/ccusage_statusline.lock"
451
+ LOCK_PID_FILE="/tmp/ccusage_statusline.pid"
452
+
453
+ # Function to check if process is still running
454
+ is_process_running() {
455
+ local pid=$1
456
+ if [ -z "$pid" ]; then return 1; fi
457
+ # Check if process exists (works on Linux, macOS, BSD)
458
+ kill -0 "$pid" 2>/dev/null
459
+ }
460
+
461
+ # Try to acquire lock
462
+ if mkdir "$LOCK_FILE" 2>/dev/null; then
463
+ # Lock acquired, save our PID
464
+ echo $$ > "$LOCK_PID_FILE"
465
+
466
+ # Run ccusage with timeout
467
+ if command -v timeout >/dev/null 2>&1; then
468
+ blocks_output=$(timeout 3s npx ccusage@latest blocks --json 2>/dev/null || timeout 3s ccusage blocks --json 2>/dev/null)
469
+ elif command -v gtimeout >/dev/null 2>&1; then
470
+ # macOS with coreutils installed
471
+ blocks_output=$(gtimeout 3s npx ccusage@latest blocks --json 2>/dev/null || gtimeout 3s ccusage blocks --json 2>/dev/null)
472
+ else
473
+ # Fallback: use background process with sleep-based timeout
474
+ (npx ccusage@latest blocks --json 2>/dev/null || ccusage blocks --json 2>/dev/null) &
475
+ ccusage_pid=$!
476
+ sleep 3
477
+ if kill -0 "$ccusage_pid" 2>/dev/null; then
478
+ kill "$ccusage_pid" 2>/dev/null
479
+ blocks_output=""
480
+ else
481
+ wait "$ccusage_pid"
482
+ blocks_output=$(cat)
483
+ fi
484
+ fi
485
+
486
+ # Clean up lock
487
+ rm -f "$LOCK_PID_FILE" 2>/dev/null
488
+ rmdir "$LOCK_FILE" 2>/dev/null
489
+ else
490
+ # Lock exists, check if it's stale
491
+ if [ -f "$LOCK_PID_FILE" ]; then
492
+ old_pid=$(cat "$LOCK_PID_FILE" 2>/dev/null)
493
+ if ! is_process_running "$old_pid"; then
494
+ # Stale lock, clean it up
495
+ rm -f "$LOCK_PID_FILE" 2>/dev/null
496
+ rmdir "$LOCK_FILE" 2>/dev/null
497
+ fi
498
+ fi
499
+ # Skip ccusage call to prevent pile-up
500
+ blocks_output=""
501
+ fi
439
502
  if [ -n "$blocks_output" ]; then
440
503
  active_block=$(echo "$blocks_output" | jq -c '.blocks[] | select(.isActive == true)' 2>/dev/null | head -n1)
441
504
  if [ -n "$active_block" ]; then${config.showCost ? `
@@ -748,28 +811,68 @@ function validateConfig(config) {
748
811
  init_esm_shims();
749
812
  import { promises as fs } from "fs";
750
813
  import path2 from "path";
814
+ import os from "os";
815
+ import inquirer2 from "inquirer";
751
816
  async function installStatusline(script, outputPath, config) {
752
817
  try {
753
- const dir = path2.dirname(outputPath);
754
- await fs.mkdir(dir, { recursive: true });
755
- await fs.writeFile(outputPath, script, { mode: 493 });
756
- await updateSettingsJson(dir, path2.basename(outputPath));
818
+ const isGlobal = config.installLocation === "global";
819
+ const claudeDir = isGlobal ? path2.join(os.homedir(), ".claude") : "./.claude";
820
+ const scriptPath = path2.join(claudeDir, "statusline.sh");
821
+ await fs.mkdir(claudeDir, { recursive: true });
822
+ let shouldWrite = true;
823
+ try {
824
+ await fs.access(scriptPath);
825
+ const { confirmOverwrite } = await inquirer2.prompt([{
826
+ type: "confirm",
827
+ name: "confirmOverwrite",
828
+ message: `\u26A0\uFE0F ${isGlobal ? "Global" : "Project"} statusline.sh already exists. Overwrite?`,
829
+ default: false
830
+ }]);
831
+ shouldWrite = confirmOverwrite;
832
+ } catch {
833
+ }
834
+ if (shouldWrite) {
835
+ await fs.writeFile(scriptPath, script, { mode: 493 });
836
+ } else {
837
+ throw new Error("USER_CANCELLED_OVERWRITE");
838
+ }
839
+ await updateSettingsJson(claudeDir, "statusline.sh", isGlobal);
757
840
  } catch (error) {
758
841
  throw new Error(`Failed to install statusline: ${error instanceof Error ? error.message : String(error)}`);
759
842
  }
760
843
  }
761
- async function updateSettingsJson(claudeDir, scriptName) {
844
+ async function updateSettingsJson(claudeDir, scriptName, isGlobal) {
845
+ var _a;
762
846
  const settingsPath = path2.join(claudeDir, "settings.json");
763
847
  try {
764
848
  let settings = {};
849
+ let existingStatusLine = null;
765
850
  try {
766
851
  const settingsContent = await fs.readFile(settingsPath, "utf-8");
767
852
  settings = JSON.parse(settingsContent);
853
+ existingStatusLine = settings.statusLine;
768
854
  } catch {
769
855
  }
856
+ if (existingStatusLine && existingStatusLine.command) {
857
+ const isOurStatusline = (_a = existingStatusLine.command) == null ? void 0 : _a.includes("statusline.sh");
858
+ if (!isOurStatusline) {
859
+ const { confirmReplace } = await inquirer2.prompt([{
860
+ type: "confirm",
861
+ name: "confirmReplace",
862
+ message: `\u26A0\uFE0F ${isGlobal ? "Global" : "Project"} settings.json already has a statusLine configured (${existingStatusLine.command}). Replace it?`,
863
+ default: false
864
+ }]);
865
+ if (!confirmReplace) {
866
+ console.warn("\n\u26A0\uFE0F Statusline script was saved but settings.json was not updated.");
867
+ console.warn(" Your existing statusLine configuration was preserved.");
868
+ return;
869
+ }
870
+ }
871
+ }
872
+ const commandPath = isGlobal ? `~/.claude/${scriptName}` : `.claude/${scriptName}`;
770
873
  settings.statusLine = {
771
874
  type: "command",
772
- command: `.claude/${scriptName}`,
875
+ command: commandPath,
773
876
  padding: 0
774
877
  };
775
878
  await fs.writeFile(settingsPath, JSON.stringify(settings, null, 2));
@@ -783,6 +886,7 @@ async function updateSettingsJson(claudeDir, scriptName) {
783
886
  import chalk from "chalk";
784
887
  import ora from "ora";
785
888
  import path4 from "path";
889
+ import os2 from "os";
786
890
  async function initCommand(options) {
787
891
  try {
788
892
  const spinner = ora("Initializing statusline generator...").start();
@@ -811,7 +915,9 @@ async function initCommand(options) {
811
915
  console.log(chalk.gray("(Preview unavailable - will work when Claude Code runs it)"));
812
916
  }
813
917
  console.log(chalk.white("\u2501".repeat(60)));
814
- const outputPath = options.output || `./.claude/${filename}`;
918
+ const isGlobal = config.installLocation === "global";
919
+ const baseDir = isGlobal ? os2.homedir() : ".";
920
+ const outputPath = options.output || path4.join(baseDir, ".claude", filename);
815
921
  const resolvedPath = path4.resolve(outputPath);
816
922
  if (options.install !== false) {
817
923
  const installSpinner = ora("Installing statusline...").start();
@@ -820,20 +926,23 @@ async function initCommand(options) {
820
926
  installSpinner.succeed("\u2705 Statusline installed!");
821
927
  console.log(chalk.green("\n\u{1F389} Success! Your custom statusline is ready!"));
822
928
  console.log(chalk.cyan(`
823
- \u{1F4C1} Generated file: ${chalk.white(resolvedPath)}`));
929
+ \u{1F4C1} ${isGlobal ? "Global" : "Project"} installation complete: ${chalk.white(resolvedPath)}`));
824
930
  console.log(chalk.cyan("\nNext steps:"));
825
931
  console.log(chalk.white(" 1. Restart Claude Code to see your new statusline"));
826
932
  console.log(chalk.white(" 2. Usage statistics work via: npx ccusage@latest"));
827
933
  } catch (error) {
828
934
  installSpinner.fail("Failed to install statusline");
829
- if (error instanceof Error && error.message === "SETTINGS_UPDATE_FAILED") {
935
+ if (error instanceof Error && error.message === "USER_CANCELLED_OVERWRITE") {
936
+ console.log(chalk.yellow("\n\u26A0\uFE0F Installation cancelled. Existing statusline.sh was not overwritten."));
937
+ } else if (error instanceof Error && error.message === "SETTINGS_UPDATE_FAILED") {
938
+ const commandPath = isGlobal ? "~/.claude/statusline.sh" : ".claude/statusline.sh";
830
939
  console.log(chalk.yellow("\n\u26A0\uFE0F Settings.json could not be updated automatically."));
831
940
  console.log(chalk.cyan("\nManual Configuration Required:"));
832
- console.log(chalk.white("Add this to your .claude/settings.json file:"));
941
+ console.log(chalk.white(`Add this to your ${isGlobal ? "~/.claude" : ".claude"}/settings.json file:`));
833
942
  console.log(chalk.gray("\n{"));
834
943
  console.log(chalk.gray(' "statusLine": {'));
835
944
  console.log(chalk.gray(' "type": "command",'));
836
- console.log(chalk.gray(` "command": ".claude/statusline.sh",`));
945
+ console.log(chalk.gray(` "command": "${commandPath}",`));
837
946
  console.log(chalk.gray(' "padding": 0'));
838
947
  console.log(chalk.gray(" }"));
839
948
  console.log(chalk.gray("}"));
@@ -861,7 +970,7 @@ async function initCommand(options) {
861
970
  // src/index.ts
862
971
  import chalk3 from "chalk";
863
972
  var program = new Command();
864
- program.name("cc-statusline").description("Interactive CLI tool for generating custom Claude Code statuslines").version("1.0.0");
973
+ program.name("cc-statusline").description("Interactive CLI tool for generating custom Claude Code statuslines").version("1.2.3");
865
974
  program.command("init").description("Create a custom statusline with interactive prompts").option("-o, --output <path>", "Output path for statusline.sh", "./.claude/statusline.sh").option("--no-install", "Don't automatically install to .claude/statusline.sh").action(initCommand);
866
975
  program.command("preview").description("Preview existing statusline.sh with mock data").argument("<script-path>", "Path to statusline.sh file to preview").action(async (scriptPath) => {
867
976
  const { previewCommand: previewCommand2 } = await Promise.resolve().then(() => (init_preview(), preview_exports));
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../node_modules/tsup/assets/esm_shims.js","../src/utils/tester.ts","../src/cli/preview.ts","../src/index.ts","../src/cli/commands.ts","../src/cli/prompts.ts","../src/generators/bash-generator.ts","../src/features/colors.ts","../src/features/git.ts","../src/features/usage.ts","../src/utils/validator.ts","../src/utils/installer.ts"],"sourcesContent":["// Shim globals in esm bundle\nimport path from 'node:path'\nimport { fileURLToPath } from 'node:url'\n\nconst getFilename = () => fileURLToPath(import.meta.url)\nconst getDirname = () => path.dirname(getFilename())\n\nexport const __dirname = /* @__PURE__ */ getDirname()\nexport const __filename = /* @__PURE__ */ getFilename()\n","import { StatuslineConfig } from '../cli/prompts.js'\nimport { spawn } from 'child_process'\nimport { promises as fs } from 'fs'\nimport path from 'path'\n\nexport interface TestResult {\n success: boolean\n output: string\n error?: string\n executionTime: number\n}\n\nexport async function testStatuslineScript(script: string, mockData?: any): Promise<TestResult> {\n const startTime = Date.now()\n \n try {\n // Create temporary script file\n const tempDir = '/tmp'\n const scriptPath = path.join(tempDir, `statusline-test-${Date.now()}.sh`)\n \n await fs.writeFile(scriptPath, script, { mode: 0o755 })\n \n // Generate mock input if not provided\n const input = mockData || generateMockClaudeInput()\n \n // Execute script\n const result = await executeScript(scriptPath, JSON.stringify(input))\n \n // Cleanup\n await fs.unlink(scriptPath).catch(() => {}) // Ignore cleanup errors\n \n const executionTime = Date.now() - startTime\n \n return {\n success: result.success,\n output: result.output,\n error: result.error,\n executionTime\n }\n \n } catch (error) {\n return {\n success: false,\n output: '',\n error: error instanceof Error ? error.message : String(error),\n executionTime: Date.now() - startTime\n }\n }\n}\n\nexport function generateMockClaudeInput(config?: Partial<StatuslineConfig>): any {\n return {\n session_id: \"test-session-123\",\n transcript_path: \"/home/user/.claude/conversations/test.jsonl\",\n cwd: \"/home/user/projects/my-project\",\n workspace: {\n current_dir: \"/home/user/projects/my-project\"\n },\n model: {\n id: \"claude-opus-4-1-20250805\",\n display_name: \"Opus 4.1\",\n version: \"20250805\"\n }\n }\n}\n\nexport function generateMockCcusageOutput(): any {\n return {\n blocks: [\n {\n id: \"2025-08-13T08:00:00.000Z\",\n startTime: \"2025-08-13T08:00:00.000Z\",\n endTime: \"2025-08-13T13:00:00.000Z\",\n usageLimitResetTime: \"2025-08-13T13:00:00.000Z\",\n actualEndTime: \"2025-08-13T09:30:34.698Z\",\n isActive: true,\n isGap: false,\n entries: 12,\n tokenCounts: {\n inputTokens: 1250,\n outputTokens: 2830,\n cacheCreationInputTokens: 15000,\n cacheReadInputTokens: 45000\n },\n totalTokens: 64080,\n costUSD: 3.42,\n models: [\"claude-opus-4-1-20250805\"],\n burnRate: {\n tokensPerMinute: 850.5,\n tokensPerMinuteForIndicator: 850,\n costPerHour: 12.45\n },\n projection: {\n totalTokens: 128000,\n totalCost: 6.84,\n remainingMinutes: 210\n }\n }\n ]\n }\n}\n\nasync function executeScript(scriptPath: string, input: string): Promise<{ success: boolean, output: string, error?: string }> {\n return new Promise((resolve) => {\n const process = spawn('bash', [scriptPath], {\n stdio: ['pipe', 'pipe', 'pipe']\n })\n \n let stdout = ''\n let stderr = ''\n \n process.stdout.on('data', (data) => {\n stdout += data.toString()\n })\n \n process.stderr.on('data', (data) => {\n stderr += data.toString()\n })\n \n process.on('close', (code) => {\n resolve({\n success: code === 0,\n output: stdout.trim(),\n error: stderr.trim() || undefined\n })\n })\n \n process.on('error', (err) => {\n resolve({\n success: false,\n output: '',\n error: err.message\n })\n })\n \n // Send input and close stdin\n process.stdin.write(input)\n process.stdin.end()\n \n // Timeout after 5 seconds\n setTimeout(() => {\n process.kill()\n resolve({\n success: false,\n output: stdout,\n error: 'Script execution timed out (5s)'\n })\n }, 5000)\n })\n}\n\nexport function analyzeTestResult(result: TestResult, config: StatuslineConfig): {\n performance: 'excellent' | 'good' | 'slow' | 'timeout'\n hasRequiredFeatures: boolean\n issues: string[]\n suggestions: string[]\n} {\n const issues: string[] = []\n const suggestions: string[] = []\n \n // Performance analysis\n let performance: 'excellent' | 'good' | 'slow' | 'timeout'\n if (result.executionTime > 1000) {\n performance = 'timeout'\n issues.push('Script execution is very slow (>1s)')\n } else if (result.executionTime > 500) {\n performance = 'slow'\n issues.push('Script execution is slow (>500ms)')\n } else if (result.executionTime > 100) {\n performance = 'good'\n } else {\n performance = 'excellent'\n }\n \n // Feature validation\n let hasRequiredFeatures = true\n \n if (config.features.includes('directory') && !result.output.includes('projects')) {\n hasRequiredFeatures = false\n issues.push('Directory feature not working properly')\n }\n \n if (config.features.includes('model') && !result.output.includes('Opus')) {\n hasRequiredFeatures = false\n issues.push('Model feature not working properly')\n }\n \n if (config.features.includes('git') && config.ccusageIntegration && !result.output.includes('git')) {\n suggestions.push('Git integration may require actual git repository')\n }\n \n // Error analysis\n if (result.error) {\n issues.push(`Script errors: ${result.error}`)\n }\n \n if (!result.success) {\n issues.push('Script failed to execute successfully')\n }\n \n // Performance suggestions\n if (config.features.length > 6) {\n suggestions.push('Consider reducing number of features for better performance')\n }\n \n if (config.ccusageIntegration && result.executionTime > 200) {\n suggestions.push('ccusage integration may slow down statusline - consider caching')\n }\n \n return {\n performance,\n hasRequiredFeatures,\n issues,\n suggestions\n }\n}","import { StatuslineConfig } from './prompts.js'\nimport { generateBashStatusline } from '../generators/bash-generator.js'\nimport { testStatuslineScript, generateMockClaudeInput, analyzeTestResult } from '../utils/tester.js'\nimport { promises as fs } from 'fs'\nimport chalk from 'chalk'\nimport ora from 'ora'\n\nexport async function previewCommand(scriptPath: string): Promise<void> {\n console.log(chalk.cyan('๐Ÿ” Statusline Preview Mode\\n'))\n \n let script: string\n \n // Load existing statusline script\n try {\n const spinner = ora(`Loading statusline script from ${scriptPath}...`).start()\n script = await fs.readFile(scriptPath, 'utf-8')\n spinner.succeed('Script loaded!')\n \n // Try to extract config info from the script header\n const headerMatch = script.match(/# Theme: (\\w+) \\| Colors: (\\w+) \\| Features: ([^\\n]+)/i)\n if (headerMatch) {\n console.log(chalk.yellow('Detected Configuration:'))\n console.log(` Theme: ${headerMatch[1]}`)\n console.log(` Colors: ${headerMatch[2]}`) \n console.log(` Features: ${headerMatch[3]}\\n`)\n }\n \n // Extract generation info if available\n const generationMatch = script.match(/# Generated by cc-statusline.*\\n# Custom Claude Code statusline - Created: ([^\\n]+)/i)\n if (generationMatch) {\n console.log(chalk.gray(`Generated: ${generationMatch[1]}\\n`))\n }\n \n } catch (error) {\n console.error(chalk.red(`โŒ Failed to load script: ${error instanceof Error ? error.message : String(error)}`))\n return\n }\n \n // Test the script\n const testSpinner = ora('Testing statusline with mock data...').start()\n const mockInput = generateMockClaudeInput()\n \n console.log(chalk.gray('\\nMock Claude Code Input:'))\n console.log(chalk.gray(JSON.stringify(mockInput, null, 2)))\n \n const testResult = await testStatuslineScript(script, mockInput)\n \n if (testResult.success) {\n testSpinner.succeed(`Test completed in ${testResult.executionTime}ms`)\n \n console.log(chalk.green('\\nโœ… Statusline Output:'))\n console.log(chalk.white('โ”'.repeat(60)))\n console.log(testResult.output)\n console.log(chalk.white('โ”'.repeat(60)))\n \n // Basic performance analysis\n console.log(chalk.cyan(`\\n๐Ÿ“Š Performance: ${getPerformanceEmoji(getPerformanceLevel(testResult.executionTime))} ${getPerformanceLevel(testResult.executionTime)} (${testResult.executionTime}ms)`))\n \n // Basic output validation\n if (testResult.output.includes('๐Ÿ“') || testResult.output.includes('๐ŸŒฟ') || testResult.output.includes('๐Ÿค–')) {\n console.log(chalk.green('โœ… Statusline features appear to be working'))\n } else {\n console.log(chalk.yellow('โš ๏ธ Basic features may not be displaying correctly'))\n }\n \n } else {\n testSpinner.fail('Test failed')\n console.error(chalk.red(`\\nโŒ Error: ${testResult.error}`))\n if (testResult.output) {\n console.log(chalk.gray('\\nPartial output:'))\n console.log(testResult.output)\n }\n }\n \n console.log(chalk.green('\\nโœจ Preview complete! Use `cc-statusline init` to generate a new statusline.'))\n}\n\nfunction getPerformanceEmoji(performance: string): string {\n switch (performance) {\n case 'excellent': return '๐Ÿš€'\n case 'good': return 'โœ…'\n case 'slow': return 'โš ๏ธ'\n case 'timeout': return '๐ŸŒ'\n default: return 'โ“'\n }\n}\n\nfunction getPerformanceLevel(executionTime: number): string {\n if (executionTime > 1000) return 'timeout'\n if (executionTime > 500) return 'slow'\n if (executionTime > 100) return 'good'\n return 'excellent'\n}","import { Command } from 'commander'\nimport { initCommand } from './cli/commands.js'\nimport chalk from 'chalk'\n\nconst program = new Command()\n\nprogram\n .name('cc-statusline')\n .description('Interactive CLI tool for generating custom Claude Code statuslines')\n .version('1.0.0')\n\nprogram\n .command('init')\n .description('Create a custom statusline with interactive prompts')\n .option('-o, --output <path>', 'Output path for statusline.sh', './.claude/statusline.sh')\n .option('--no-install', 'Don\\'t automatically install to .claude/statusline.sh')\n .action(initCommand)\n\nprogram\n .command('preview')\n .description('Preview existing statusline.sh with mock data')\n .argument('<script-path>', 'Path to statusline.sh file to preview')\n .action(async (scriptPath) => {\n const { previewCommand } = await import('./cli/preview.js')\n await previewCommand(scriptPath)\n })\n\nprogram\n .command('test')\n .description('Test statusline with real Claude Code JSON input')\n .option('-c, --config <path>', 'Configuration file to test')\n .action(() => {\n console.log(chalk.yellow('Test command coming soon!'))\n })\n\n// Show help if no command provided\nif (!process.argv.slice(2).length) {\n program.outputHelp()\n}\n\nprogram.parse(process.argv)","import { collectConfiguration, displayConfigSummary } from './prompts.js'\nimport { generateBashStatusline } from '../generators/bash-generator.js'\nimport { validateConfig } from '../utils/validator.js'\nimport { installStatusline } from '../utils/installer.js'\nimport chalk from 'chalk'\nimport ora from 'ora'\nimport path from 'path'\n\ninterface InitOptions {\n output?: string\n install?: boolean\n}\n\nexport async function initCommand(options: InitOptions): Promise<void> {\n try {\n const spinner = ora('Initializing statusline generator...').start()\n await new Promise(resolve => setTimeout(resolve, 500)) // Brief pause for UX\n spinner.stop()\n\n // Collect user configuration\n const config = await collectConfiguration()\n \n // Validate configuration\n const validation = validateConfig(config)\n if (!validation.isValid) {\n console.error(chalk.red('โŒ Configuration validation failed:'))\n validation.errors.forEach(error => console.error(chalk.red(` โ€ข ${error}`)))\n process.exit(1)\n }\n\n // Generate statusline script\n const generationSpinner = ora('Generating statusline script...').start()\n \n const script = generateBashStatusline(config)\n const filename = 'statusline.sh'\n \n generationSpinner.succeed('Statusline script generated!')\n\n // Show preview of what it will look like\n console.log(chalk.cyan('\\nโœจ Your statusline will look like:'))\n console.log(chalk.white('โ”'.repeat(60)))\n \n // Generate preview using the test function\n const { testStatuslineScript, generateMockClaudeInput } = await import('../utils/tester.js')\n const mockInput = generateMockClaudeInput()\n const testResult = await testStatuslineScript(script, mockInput)\n \n if (testResult.success) {\n console.log(testResult.output)\n } else {\n console.log(chalk.gray('๐Ÿ“ ~/projects/my-app ๐ŸŒฟ main ๐Ÿค– Claude ๐Ÿ’ต $2.48 ($12.50/h)'))\n console.log(chalk.gray('(Preview unavailable - will work when Claude Code runs it)'))\n }\n \n console.log(chalk.white('โ”'.repeat(60)))\n\n // Determine output path\n const outputPath = options.output || `./.claude/${filename}`\n const resolvedPath = path.resolve(outputPath)\n\n // Install the statusline\n if (options.install !== false) {\n const installSpinner = ora('Installing statusline...').start()\n \n try {\n await installStatusline(script, resolvedPath, config)\n installSpinner.succeed('โœ… Statusline installed!')\n \n console.log(chalk.green('\\n๐ŸŽ‰ Success! Your custom statusline is ready!'))\n console.log(chalk.cyan(`\\n๐Ÿ“ Generated file: ${chalk.white(resolvedPath)}`))\n console.log(chalk.cyan('\\nNext steps:'))\n console.log(chalk.white(' 1. Restart Claude Code to see your new statusline'))\n console.log(chalk.white(' 2. Usage statistics work via: npx ccusage@latest'))\n \n } catch (error) {\n installSpinner.fail('Failed to install statusline')\n \n if (error instanceof Error && error.message === 'SETTINGS_UPDATE_FAILED') {\n console.log(chalk.yellow('\\nโš ๏ธ Settings.json could not be updated automatically.'))\n console.log(chalk.cyan('\\nManual Configuration Required:'))\n console.log(chalk.white('Add this to your .claude/settings.json file:'))\n console.log(chalk.gray('\\n{'))\n console.log(chalk.gray(' \"statusLine\": {'))\n console.log(chalk.gray(' \"type\": \"command\",'))\n console.log(chalk.gray(` \"command\": \".claude/statusline.sh\",`))\n console.log(chalk.gray(' \"padding\": 0'))\n console.log(chalk.gray(' }'))\n console.log(chalk.gray('}'))\n console.log(chalk.cyan(`\\n๐Ÿ“ Statusline script saved to: ${chalk.white(resolvedPath)}`))\n } else {\n console.error(chalk.red(`Error: ${error instanceof Error ? error.message : String(error)}`))\n console.log(chalk.cyan(`\\n๐Ÿ“ You can manually save the script to: ${chalk.white(resolvedPath)}`))\n }\n }\n } else {\n // Just display where to save it\n console.log(chalk.green('\\nโœ… Statusline generated successfully!'))\n console.log(chalk.cyan(`\\n๐Ÿ“ Save this script to: ${chalk.white(resolvedPath)}`))\n console.log(chalk.cyan('\\nThen restart Claude Code to see your new statusline.'))\n }\n\n } catch (error) {\n console.error(chalk.red('โŒ An error occurred:'))\n console.error(chalk.red(error instanceof Error ? error.message : String(error)))\n process.exit(1)\n }\n}","import inquirer from 'inquirer'\n\nexport interface StatuslineConfig {\n features: string[]\n runtime: 'bash' | 'python' | 'node'\n colors: boolean\n theme: 'minimal' | 'detailed' | 'compact'\n ccusageIntegration: boolean\n logging: boolean\n customEmojis: boolean\n}\n\nexport async function collectConfiguration(): Promise<StatuslineConfig> {\n console.log('๐Ÿš€ Welcome to cc-statusline! Let\\'s create your custom Claude Code statusline.\\n')\n console.log('โœจ All features are enabled by default. Use โ†‘/โ†“ arrows to navigate, SPACE to toggle, ENTER to continue.\\n')\n \n const config = await inquirer.prompt([\n {\n type: 'checkbox',\n name: 'features',\n message: 'Select statusline features (scroll down for more options):',\n choices: [\n { name: '๐Ÿ“ Working Directory', value: 'directory', checked: true },\n { name: '๐ŸŒฟ Git Branch', value: 'git', checked: true },\n { name: '๐Ÿค– Model Name & Version', value: 'model', checked: true },\n { name: '๐Ÿง  Context Remaining', value: 'context', checked: true },\n { name: '๐Ÿ’ต Usage & Cost', value: 'usage', checked: true },\n { name: 'โŒ› Session Time Remaining', value: 'session', checked: true },\n { name: '๐Ÿ“Š Token Statistics', value: 'tokens', checked: true },\n { name: 'โšก Burn Rate (tokens/min)', value: 'burnrate', checked: true }\n ],\n validate: (answer: string[]) => {\n if (answer.length < 1) {\n return 'You must choose at least one feature.'\n }\n return true\n },\n pageSize: 10\n },\n {\n type: 'confirm',\n name: 'colors',\n message: '\\n๐ŸŽจ Enable modern color scheme and emojis?',\n default: true\n },\n {\n type: 'confirm',\n name: 'logging',\n message: '\\n๐Ÿ“ Enable debug logging to .claude/statusline.log?',\n default: false\n }\n ])\n\n // Set intelligent defaults\n return {\n features: config.features,\n runtime: 'bash',\n colors: config.colors,\n theme: 'detailed',\n ccusageIntegration: true, // Always enabled since npx works\n logging: config.logging,\n customEmojis: false\n } as StatuslineConfig\n}\n\nexport function displayConfigSummary(config: StatuslineConfig): void {\n console.log('\\nโœ… Configuration Summary:')\n console.log(` Runtime: ${config.runtime}`)\n console.log(` Theme: ${config.theme}`)\n console.log(` Colors: ${config.colors ? 'โœ…' : 'โŒ'}`)\n console.log(` Features: ${config.features.join(', ')}`)\n \n if (config.ccusageIntegration) {\n console.log(' ๐Ÿ“Š ccusage integration enabled')\n }\n \n if (config.logging) {\n console.log(' ๐Ÿ“ Debug logging enabled')\n }\n \n console.log('')\n}","import { StatuslineConfig } from '../cli/prompts.js'\nimport { generateColorBashCode, generateBasicColors } from '../features/colors.js'\nimport { generateGitBashCode, generateGitDisplayCode, generateGitUtilities } from '../features/git.js'\nimport { generateUsageBashCode, generateUsageDisplayCode, generateUsageUtilities } from '../features/usage.js'\n\nexport function generateBashStatusline(config: StatuslineConfig): string {\n const hasGit = config.features.includes('git')\n const hasUsage = config.features.some(f => ['usage', 'session', 'tokens', 'burnrate'].includes(f))\n const hasDirectory = config.features.includes('directory')\n const hasModel = config.features.includes('model')\n const hasContext = config.features.includes('context')\n\n // Build usage feature config\n const usageConfig = {\n enabled: hasUsage && config.ccusageIntegration,\n showCost: config.features.includes('usage'),\n showTokens: config.features.includes('tokens'),\n showBurnRate: config.features.includes('burnrate'),\n showSession: config.features.includes('session'),\n showProgressBar: config.theme !== 'minimal' && config.features.includes('session')\n }\n\n // Build git feature config\n const gitConfig = {\n enabled: hasGit,\n showBranch: hasGit,\n showChanges: false, // Removed delta changes per user request\n compactMode: config.theme === 'compact'\n }\n\n const timestamp = new Date().toISOString()\n const script = `#!/bin/bash\n# Generated by cc-statusline (https://www.npmjs.com/package/@chongdashu/cc-statusline)\n# Custom Claude Code statusline - Created: ${timestamp}\n# Theme: ${config.theme} | Colors: ${config.colors} | Features: ${config.features.join(', ')}\n\n${config.logging ? generateLoggingCode() : ''}\ninput=$(cat)\n${generateColorBashCode({ enabled: config.colors, theme: config.theme })}\n${config.colors ? generateBasicColors() : ''}\n${hasUsage ? generateUsageUtilities() : ''}\n${hasGit ? generateGitUtilities() : ''}\n${generateBasicDataExtraction(hasDirectory, hasModel, hasContext)}\n${hasGit ? generateGitBashCode(gitConfig, config.colors) : ''}\n${hasContext ? generateContextBashCode(config.colors) : ''}\n${hasUsage ? generateUsageBashCode(usageConfig, config.colors) : ''}\n${config.logging ? generateLoggingOutput() : ''}\n${generateDisplaySection(config, gitConfig, usageConfig)}\n`\n\n return script.replace(/\\n\\n\\n+/g, '\\n\\n').trim() + '\\n'\n}\n\nfunction generateLoggingCode(): string {\n return `\nLOG_FILE=\"\\${HOME}/.claude/statusline.log\"\nTIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')\n\n# ---- logging ----\n{\n echo \"[$TIMESTAMP] Status line triggered with input:\"\n (echo \"$input\" | jq . 2>/dev/null) || echo \"$input\"\n echo \"---\"\n} >> \"$LOG_FILE\" 2>/dev/null\n`\n}\n\nfunction generateBasicDataExtraction(hasDirectory: boolean, hasModel: boolean, hasContext: boolean): string {\n return `\n# ---- basics ----\nif command -v jq >/dev/null 2>&1; then${hasDirectory ? `\n current_dir=$(echo \"$input\" | jq -r '.workspace.current_dir // .cwd // \"unknown\"' 2>/dev/null | sed \"s|^$HOME|~|g\")` : ''}${hasModel ? `\n model_name=$(echo \"$input\" | jq -r '.model.display_name // \"Claude\"' 2>/dev/null)\n model_version=$(echo \"$input\" | jq -r '.model.version // \"\"' 2>/dev/null)` : ''}${hasContext ? `\n session_id=$(echo \"$input\" | jq -r '.session_id // \"\"' 2>/dev/null)` : ''}\n cc_version=$(echo \"$input\" | jq -r '.version // \"\"' 2>/dev/null)\n output_style=$(echo \"$input\" | jq -r '.output_style.name // \"\"' 2>/dev/null)\nelse${hasDirectory ? `\n current_dir=\"unknown\"` : ''}${hasModel ? `\n model_name=\"Claude\"; model_version=\"\"` : ''}${hasContext ? `\n session_id=\"\"` : ''}\n cc_version=\"\"\n output_style=\"\"\nfi\n`\n}\n\nfunction generateContextBashCode(colors: boolean): string {\n return `\n# ---- context window calculation ----\ncontext_pct=\"\"\ncontext_color() { if [ \"$use_color\" -eq 1 ]; then printf '\\\\033[1;37m'; fi; } # default white\n\n# Determine max context based on model\nget_max_context() {\n local model_name=\"$1\"\n case \"$model_name\" in\n *\"Opus 4\"*|*\"opus 4\"*|*\"Opus\"*|*\"opus\"*)\n echo \"200000\" # 200K for all Opus versions\n ;;\n *\"Sonnet 4\"*|*\"sonnet 4\"*|*\"Sonnet 3.5\"*|*\"sonnet 3.5\"*|*\"Sonnet\"*|*\"sonnet\"*)\n echo \"200000\" # 200K for Sonnet 3.5+ and 4.x\n ;;\n *\"Haiku 3.5\"*|*\"haiku 3.5\"*|*\"Haiku 4\"*|*\"haiku 4\"*|*\"Haiku\"*|*\"haiku\"*)\n echo \"200000\" # 200K for modern Haiku\n ;;\n *\"Claude 3 Haiku\"*|*\"claude 3 haiku\"*)\n echo \"100000\" # 100K for original Claude 3 Haiku\n ;;\n *)\n echo \"200000\" # Default to 200K\n ;;\n esac\n}\n\nif [ -n \"$session_id\" ] && command -v jq >/dev/null 2>&1; then\n MAX_CONTEXT=$(get_max_context \"$model_name\")\n \n # Convert current dir to session file path\n project_dir=$(echo \"$current_dir\" | sed \"s|~|$HOME|g\" | sed 's|/|-|g' | sed 's|^-||')\n session_file=\"$HOME/.claude/projects/-\\${project_dir}/\\${session_id}.jsonl\"\n \n if [ -f \"$session_file\" ]; then\n # Get the latest input token count from the session file\n latest_tokens=$(tail -20 \"$session_file\" | jq -r 'select(.message.usage) | .message.usage | ((.input_tokens // 0) + (.cache_read_input_tokens // 0))' 2>/dev/null | tail -1)\n \n if [ -n \"$latest_tokens\" ] && [ \"$latest_tokens\" -gt 0 ]; then\n context_used_pct=$(( latest_tokens * 100 / MAX_CONTEXT ))\n context_remaining_pct=$(( 100 - context_used_pct ))\n \n # Set color based on remaining percentage\n if [ \"$context_remaining_pct\" -le 20 ]; then\n context_color() { if [ \"$use_color\" -eq 1 ]; then printf '\\\\033[38;5;203m'; fi; } # coral red\n elif [ \"$context_remaining_pct\" -le 40 ]; then\n context_color() { if [ \"$use_color\" -eq 1 ]; then printf '\\\\033[38;5;215m'; fi; } # peach\n else\n context_color() { if [ \"$use_color\" -eq 1 ]; then printf '\\\\033[38;5;158m'; fi; } # mint green\n fi\n \n context_pct=\"\\${context_remaining_pct}%\"\n fi\n fi\nfi\n`\n}\n\nfunction generateLoggingOutput(): string {\n return `\n# ---- log extracted data ----\n{\n echo \"[\\$TIMESTAMP] Extracted: dir=\\${current_dir:-}, model=\\${model_name:-}, version=\\${model_version:-}, git=\\${git_branch:-}, context=\\${context_pct:-}, cost=\\${cost_usd:-}, cost_ph=\\${cost_per_hour:-}, tokens=\\${tot_tokens:-}, tpm=\\${tpm:-}, session_pct=\\${session_pct:-}\"\n} >> \"$LOG_FILE\" 2>/dev/null\n`\n}\n\nfunction generateDisplaySection(config: StatuslineConfig, gitConfig: any, usageConfig: any): string {\n const emojis = config.colors && !config.customEmojis\n\n return `\n# ---- render statusline ----\n# Line 1: Core info (directory, git, model, claude code version, output style)\n${config.features.includes('directory') ? `printf '๐Ÿ“ %s%s%s' \"$(dir_color)\" \"$current_dir\" \"$(rst)\"` : ''}${gitConfig.enabled ? `\nif [ -n \"$git_branch\" ]; then\n printf ' ๐ŸŒฟ %s%s%s' \"$(git_color)\" \"$git_branch\" \"$(rst)\"\nfi` : ''}${config.features.includes('model') ? `\nprintf ' ๐Ÿค– %s%s%s' \"$(model_color)\" \"$model_name\" \"$(rst)\"\nif [ -n \"$model_version\" ] && [ \"$model_version\" != \"null\" ]; then\n printf ' ๐Ÿท๏ธ %s%s%s' \"$(version_color)\" \"$model_version\" \"$(rst)\"\nfi` : ''}\nif [ -n \"$cc_version\" ] && [ \"$cc_version\" != \"null\" ]; then\n printf ' ๐Ÿ“Ÿ %sv%s%s' \"$(cc_version_color)\" \"$cc_version\" \"$(rst)\"\nfi\nif [ -n \"$output_style\" ] && [ \"$output_style\" != \"null\" ]; then\n printf ' ๐ŸŽจ %s%s%s' \"$(style_color)\" \"$output_style\" \"$(rst)\"\nfi\n\n# Line 2: Context and session time\nline2=\"\"${config.features.includes('context') ? `\nif [ -n \"$context_pct\" ]; then\n context_bar=$(progress_bar \"$context_remaining_pct\" 10)\n line2=\"๐Ÿง  $(context_color)Context Remaining: \\${context_pct} [\\${context_bar}]$(rst)\"\nfi` : ''}${usageConfig.showSession ? `\nif [ -n \"$session_txt\" ]; then\n if [ -n \"$line2\" ]; then\n line2=\"$line2 โŒ› $(session_color)\\${session_txt}$(rst) $(session_color)[\\${session_bar}]$(rst)\"\n else\n line2=\"โŒ› $(session_color)\\${session_txt}$(rst) $(session_color)[\\${session_bar}]$(rst)\"\n fi\nfi` : ''}${config.features.includes('context') ? `\nif [ -z \"$line2\" ] && [ -z \"$context_pct\" ]; then\n line2=\"๐Ÿง  $(context_color)Context Remaining: TBD$(rst)\"\nfi` : ''}\n\n# Line 3: Cost and usage analytics\nline3=\"\"${usageConfig.showCost ? `\nif [ -n \"$cost_usd\" ] && [[ \"$cost_usd\" =~ ^[0-9.]+$ ]]; then${usageConfig.showBurnRate ? `\n if [ -n \"$cost_per_hour\" ] && [[ \"$cost_per_hour\" =~ ^[0-9.]+$ ]]; then\n cost_per_hour_formatted=$(printf '%.2f' \"$cost_per_hour\")\n line3=\"๐Ÿ’ฐ $(cost_color)\\\\$$(printf '%.2f' \\\\\"$cost_usd\\\\\")$(rst) ($(burn_color)\\\\$\\${cost_per_hour_formatted}/h$(rst))\"\n else\n line3=\"๐Ÿ’ฐ $(cost_color)\\\\$$(printf '%.2f' \\\\\"$cost_usd\\\\\")$(rst)\"\n fi` : `\n line3=\"๐Ÿ’ฐ $(cost_color)\\\\$$(printf '%.2f' \\\\\"$cost_usd\\\\\")$(rst)\"`}\nfi` : ''}${usageConfig.showTokens ? `\nif [ -n \"$tot_tokens\" ] && [[ \"$tot_tokens\" =~ ^[0-9]+$ ]]; then${usageConfig.showBurnRate ? `\n if [ -n \"$tpm\" ] && [[ \"$tpm\" =~ ^[0-9.]+$ ]]; then\n tpm_formatted=$(printf '%.0f' \"$tpm\")\n if [ -n \"$line3\" ]; then\n line3=\"$line3 ๐Ÿ“Š $(usage_color)\\${tot_tokens} tok (\\${tpm_formatted} tpm)$(rst)\"\n else\n line3=\"๐Ÿ“Š $(usage_color)\\${tot_tokens} tok (\\${tpm_formatted} tpm)$(rst)\"\n fi\n else\n if [ -n \"$line3\" ]; then\n line3=\"$line3 ๐Ÿ“Š $(usage_color)\\${tot_tokens} tok$(rst)\"\n else\n line3=\"๐Ÿ“Š $(usage_color)\\${tot_tokens} tok$(rst)\"\n fi\n fi` : `\n if [ -n \"$line3\" ]; then\n line3=\"$line3 ๐Ÿ“Š $(usage_color)\\${tot_tokens} tok$(rst)\"\n else\n line3=\"๐Ÿ“Š $(usage_color)\\${tot_tokens} tok$(rst)\"\n fi`}\nfi` : ''}\n\n# Print lines\nif [ -n \"$line2\" ]; then\n printf '\\\\n%s' \"$line2\"\nfi\nif [ -n \"$line3\" ]; then\n printf '\\\\n%s' \"$line3\"\nfi\nprintf '\\\\n'`\n}","export interface ColorConfig {\n enabled: boolean\n theme: 'minimal' | 'detailed' | 'compact'\n}\n\nexport function generateColorBashCode(config: ColorConfig): string {\n if (!config.enabled) {\n return `\n# ---- color helpers (disabled) ----\nuse_color=0\nC() { :; }\nRST() { :; }\n`\n }\n\n return `\n# ---- color helpers (force colors for Claude Code) ----\nuse_color=1\n[ -n \"$NO_COLOR\" ] && use_color=0\n\nC() { if [ \"$use_color\" -eq 1 ]; then printf '\\\\033[%sm' \"$1\"; fi; }\nRST() { if [ \"$use_color\" -eq 1 ]; then printf '\\\\033[0m'; fi; }\n`\n}\n\nexport function generateBasicColors(): string {\n return `\n# ---- modern sleek colors ----\ndir_color() { if [ \"$use_color\" -eq 1 ]; then printf '\\\\033[38;5;117m'; fi; } # sky blue\nmodel_color() { if [ \"$use_color\" -eq 1 ]; then printf '\\\\033[38;5;147m'; fi; } # light purple \nversion_color() { if [ \"$use_color\" -eq 1 ]; then printf '\\\\033[38;5;180m'; fi; } # soft yellow\ncc_version_color() { if [ \"$use_color\" -eq 1 ]; then printf '\\\\033[38;5;249m'; fi; } # light gray\nstyle_color() { if [ \"$use_color\" -eq 1 ]; then printf '\\\\033[38;5;245m'; fi; } # gray\nrst() { if [ \"$use_color\" -eq 1 ]; then printf '\\\\033[0m'; fi; }\n`\n}\n\nexport const COLOR_CODES = {\n // Basic colors\n BLACK: '30',\n RED: '31', \n GREEN: '32',\n YELLOW: '33',\n BLUE: '34',\n MAGENTA: '35',\n CYAN: '36',\n WHITE: '37',\n \n // Bright colors (bold)\n BRIGHT_BLACK: '1;30',\n BRIGHT_RED: '1;31',\n BRIGHT_GREEN: '1;32', \n BRIGHT_YELLOW: '1;33',\n BRIGHT_BLUE: '1;34',\n BRIGHT_MAGENTA: '1;35',\n BRIGHT_CYAN: '1;36',\n BRIGHT_WHITE: '1;37',\n \n // Reset\n RESET: '0'\n} as const\n\nexport function getThemeColors(theme: 'minimal' | 'detailed' | 'compact') {\n switch (theme) {\n case 'minimal':\n return {\n directory: COLOR_CODES.CYAN,\n git: COLOR_CODES.GREEN,\n model: COLOR_CODES.MAGENTA,\n usage: COLOR_CODES.YELLOW,\n session: COLOR_CODES.BLUE\n }\n case 'detailed':\n return {\n directory: COLOR_CODES.BRIGHT_CYAN,\n git: COLOR_CODES.BRIGHT_GREEN,\n model: COLOR_CODES.BRIGHT_MAGENTA,\n usage: COLOR_CODES.BRIGHT_YELLOW,\n session: COLOR_CODES.BRIGHT_BLUE\n }\n case 'compact':\n return {\n directory: COLOR_CODES.CYAN,\n git: COLOR_CODES.GREEN,\n model: COLOR_CODES.BLUE,\n usage: COLOR_CODES.YELLOW,\n session: COLOR_CODES.RED\n }\n }\n}","export interface GitFeature {\n enabled: boolean\n showBranch: boolean\n showChanges: boolean\n compactMode: boolean\n}\n\nexport function generateGitBashCode(config: GitFeature, colors: boolean): string {\n if (!config.enabled) return ''\n\n const colorCode = colors ? `\n# ---- git colors ----\ngit_color() { if [ \"$use_color\" -eq 1 ]; then printf '\\\\033[38;5;150m'; fi; } # soft green\nrst() { if [ \"$use_color\" -eq 1 ]; then printf '\\\\033[0m'; fi; }\n` : `\ngit_color() { :; }\nrst() { :; }\n`\n\n return `${colorCode}\n# ---- git ----\ngit_branch=\"\"\nif git rev-parse --git-dir >/dev/null 2>&1; then\n git_branch=$(git branch --show-current 2>/dev/null || git rev-parse --short HEAD 2>/dev/null)\nfi`\n}\n\nexport function generateGitDisplayCode(config: GitFeature, colors: boolean, emojis: boolean): string {\n if (!config.enabled) return ''\n\n const branchEmoji = emojis ? '๐ŸŒฟ' : 'git:'\n\n let displayCode = `\n# git display\nif [ -n \"$git_branch\" ]; then\n printf ' ${branchEmoji} %s%s%s' \"$(git_color)\" \"$git_branch\" \"$(rst)\"\nfi`\n\n return displayCode\n}\n\nexport function generateGitUtilities(): string {\n return `\n# git utilities\nnum_or_zero() { v=\"$1\"; [[ \"$v\" =~ ^[0-9]+$ ]] && echo \"$v\" || echo 0; }`\n}","export interface UsageFeature {\n enabled: boolean\n showCost: boolean\n showTokens: boolean\n showBurnRate: boolean\n showSession: boolean\n showProgressBar: boolean\n}\n\nexport function generateUsageBashCode(config: UsageFeature, colors: boolean): string {\n if (!config.enabled) return ''\n\n const colorCode = colors ? `\n# ---- usage colors ----\nusage_color() { if [ \"$use_color\" -eq 1 ]; then printf '\\\\033[38;5;189m'; fi; } # lavender\ncost_color() { if [ \"$use_color\" -eq 1 ]; then printf '\\\\033[38;5;222m'; fi; } # light gold\nburn_color() { if [ \"$use_color\" -eq 1 ]; then printf '\\\\033[38;5;220m'; fi; } # bright gold\nsession_color() { \n rem_pct=$(( 100 - session_pct ))\n if (( rem_pct <= 10 )); then SCLR='38;5;210' # light pink\n elif (( rem_pct <= 25 )); then SCLR='38;5;228' # light yellow \n else SCLR='38;5;194'; fi # light green\n if [ \"$use_color\" -eq 1 ]; then printf '\\\\033[%sm' \"$SCLR\"; fi\n}\n` : `\nusage_color() { :; }\ncost_color() { :; }\nburn_color() { :; }\nsession_color() { :; }\n`\n\n return `${colorCode}\n# ---- ccusage integration ----\nsession_txt=\"\"; session_pct=0; session_bar=\"\"\ncost_usd=\"\"; cost_per_hour=\"\"; tpm=\"\"; tot_tokens=\"\"\n\nif command -v jq >/dev/null 2>&1; then\n blocks_output=$(npx ccusage@latest blocks --json 2>/dev/null || ccusage blocks --json 2>/dev/null)\n if [ -n \"$blocks_output\" ]; then\n active_block=$(echo \"$blocks_output\" | jq -c '.blocks[] | select(.isActive == true)' 2>/dev/null | head -n1)\n if [ -n \"$active_block\" ]; then${config.showCost ? `\n cost_usd=$(echo \"$active_block\" | jq -r '.costUSD // empty')\n cost_per_hour=$(echo \"$active_block\" | jq -r '.burnRate.costPerHour // empty')` : ''}${config.showTokens ? `\n tot_tokens=$(echo \"$active_block\" | jq -r '.totalTokens // empty')` : ''}${config.showBurnRate ? `\n tpm=$(echo \"$active_block\" | jq -r '.burnRate.tokensPerMinute // empty')` : ''}${config.showSession || config.showProgressBar ? `\n \n # Session time calculation\n reset_time_str=$(echo \"$active_block\" | jq -r '.usageLimitResetTime // .endTime // empty')\n start_time_str=$(echo \"$active_block\" | jq -r '.startTime // empty')\n \n if [ -n \"$reset_time_str\" ] && [ -n \"$start_time_str\" ]; then\n start_sec=$(to_epoch \"$start_time_str\"); end_sec=$(to_epoch \"$reset_time_str\"); now_sec=$(date +%s)\n total=$(( end_sec - start_sec )); (( total<1 )) && total=1\n elapsed=$(( now_sec - start_sec )); (( elapsed<0 ))&&elapsed=0; (( elapsed>total ))&&elapsed=$total\n session_pct=$(( elapsed * 100 / total ))\n remaining=$(( end_sec - now_sec )); (( remaining<0 )) && remaining=0\n rh=$(( remaining / 3600 )); rm=$(( (remaining % 3600) / 60 ))\n end_hm=$(fmt_time_hm \"$end_sec\")${config.showSession ? `\n session_txt=\"$(printf '%dh %dm until reset at %s (%d%%)' \"$rh\" \"$rm\" \"$end_hm\" \"$session_pct\")\"` : ''}${config.showProgressBar ? `\n session_bar=$(progress_bar \"$session_pct\" 10)` : ''}\n fi` : ''}\n fi\n fi\nfi`\n}\n\nexport function generateUsageUtilities(): string {\n return `\n# ---- time helpers ----\nto_epoch() {\n ts=\"$1\"\n if command -v gdate >/dev/null 2>&1; then gdate -d \"$ts\" +%s 2>/dev/null && return; fi\n date -u -j -f \"%Y-%m-%dT%H:%M:%S%z\" \"\\${ts/Z/+0000}\" +%s 2>/dev/null && return\n python3 - \"$ts\" <<'PY' 2>/dev/null\nimport sys, datetime\ns=sys.argv[1].replace('Z','+00:00')\nprint(int(datetime.datetime.fromisoformat(s).timestamp()))\nPY\n}\n\nfmt_time_hm() {\n epoch=\"$1\"\n if date -r 0 +%s >/dev/null 2>&1; then date -r \"$epoch\" +\"%H:%M\"; else date -d \"@$epoch\" +\"%H:%M\"; fi\n}\n\nprogress_bar() {\n pct=\"\\${1:-0}\"; width=\"\\${2:-10}\"\n [[ \"$pct\" =~ ^[0-9]+$ ]] || pct=0; ((pct<0))&&pct=0; ((pct>100))&&pct=100\n filled=$(( pct * width / 100 )); empty=$(( width - filled ))\n printf '%*s' \"$filled\" '' | tr ' ' '='\n printf '%*s' \"$empty\" '' | tr ' ' '-'\n}`\n}\n\nexport function generateUsageDisplayCode(config: UsageFeature, colors: boolean, emojis: boolean): string {\n if (!config.enabled) return ''\n\n let displayCode = ''\n\n if (config.showSession) {\n const sessionEmoji = emojis ? 'โŒ›' : 'session:'\n displayCode += `\n# session time\nif [ -n \"$session_txt\" ]; then\n printf ' ${sessionEmoji} %s%s%s' \"$(session_color)\" \"$session_txt\" \"$(rst)\"${config.showProgressBar ? `\n printf ' %s[%s]%s' \"$(session_color)\" \"$session_bar\" \"$(rst)\"` : ''}\nfi`\n }\n\n if (config.showCost) {\n const costEmoji = emojis ? '๐Ÿ’ต' : '$'\n displayCode += `\n# cost\nif [ -n \"$cost_usd\" ] && [[ \"$cost_usd\" =~ ^[0-9.]+$ ]]; then\n if [ -n \"$cost_per_hour\" ] && [[ \"$cost_per_hour\" =~ ^[0-9.]+$ ]]; then\n printf ' ${costEmoji} %s$%.2f ($%.2f/h)%s' \"$(cost_color)\" \"$cost_usd\" \"$cost_per_hour\" \"$(rst)\"\n else\n printf ' ${costEmoji} %s$%.2f%s' \"$(cost_color)\" \"$cost_usd\" \"$(rst)\"\n fi\nfi`\n }\n\n if (config.showTokens) {\n const tokenEmoji = emojis ? '๐Ÿ“Š' : 'tok:'\n displayCode += `\n# tokens\nif [ -n \"$tot_tokens\" ] && [[ \"$tot_tokens\" =~ ^[0-9]+$ ]]; then\n if [ -n \"$tpm\" ] && [[ \"$tpm\" =~ ^[0-9.]+$ ]] && ${config.showBurnRate ? 'true' : 'false'}; then\n printf ' ${tokenEmoji} %s%s tok (%.0f tpm)%s' \"$(usage_color)\" \"$tot_tokens\" \"$tpm\" \"$(rst)\"\n else\n printf ' ${tokenEmoji} %s%s tok%s' \"$(usage_color)\" \"$tot_tokens\" \"$(rst)\"\n fi\nfi`\n }\n\n return displayCode\n}","import { StatuslineConfig } from '../cli/prompts.js'\n\nexport interface ValidationResult {\n isValid: boolean\n errors: string[]\n warnings: string[]\n}\n\nexport function validateConfig(config: StatuslineConfig): ValidationResult {\n const errors: string[] = []\n const warnings: string[] = []\n\n // Validate features\n if (!config.features || config.features.length === 0) {\n errors.push('At least one display feature must be selected')\n }\n\n // Validate runtime\n if (!['bash', 'python', 'node'].includes(config.runtime)) {\n errors.push(`Invalid runtime: ${config.runtime}`)\n }\n\n // Validate theme\n if (!['minimal', 'detailed', 'compact'].includes(config.theme)) {\n errors.push(`Invalid theme: ${config.theme}`)\n }\n\n // Check for usage features without ccusage integration\n const usageFeatures = ['usage', 'session', 'tokens', 'burnrate']\n const hasUsageFeatures = config.features.some(f => usageFeatures.includes(f))\n \n if (hasUsageFeatures && !config.ccusageIntegration) {\n warnings.push('Usage features selected but ccusage integration is disabled. Some features may not work properly.')\n }\n\n // Warn about performance with many features\n if (config.features.length > 5) {\n warnings.push('Many features selected. This may impact statusline performance.')\n }\n\n // Validate color/emoji consistency\n if (config.customEmojis && !config.colors) {\n warnings.push('Custom emojis enabled but colors disabled. Visual distinction may be limited.')\n }\n\n return {\n isValid: errors.length === 0,\n errors,\n warnings\n }\n}\n\nexport function validateDependencies(): {\n jq: boolean\n git: boolean\n ccusage: boolean\n python?: boolean\n node?: boolean\n} {\n // This would check system dependencies\n // For now, return placeholder\n return {\n jq: true, // Would check: command -v jq >/dev/null 2>&1\n git: true, // Would check: command -v git >/dev/null 2>&1\n ccusage: false, // Would check: command -v ccusage >/dev/null 2>&1\n python: true, // Would check: command -v python3 >/dev/null 2>&1\n node: true // Would check: command -v node >/dev/null 2>&1\n }\n}","import { StatuslineConfig } from '../cli/prompts.js'\nimport { promises as fs } from 'fs'\nimport path from 'path'\n\nexport async function installStatusline(\n script: string,\n outputPath: string,\n config: StatuslineConfig\n): Promise<void> {\n try {\n // Ensure the directory exists\n const dir = path.dirname(outputPath)\n await fs.mkdir(dir, { recursive: true })\n\n // Write the script\n await fs.writeFile(outputPath, script, { mode: 0o755 })\n\n // Update .claude/settings.json if it exists\n await updateSettingsJson(dir, path.basename(outputPath))\n\n // Note: statusline-config.json removed per user feedback - not needed\n // The statusline script contains all necessary configuration info\n\n } catch (error) {\n throw new Error(`Failed to install statusline: ${error instanceof Error ? error.message : String(error)}`)\n }\n}\n\nasync function updateSettingsJson(claudeDir: string, scriptName: string): Promise<void> {\n const settingsPath = path.join(claudeDir, 'settings.json')\n \n try {\n let settings: any = {}\n \n // Try to read existing settings\n try {\n const settingsContent = await fs.readFile(settingsPath, 'utf-8')\n settings = JSON.parse(settingsContent)\n } catch {\n // File doesn't exist or invalid JSON, start fresh\n }\n\n // Update statusLine configuration\n settings.statusLine = {\n type: 'command',\n command: `.claude/${scriptName}`,\n padding: 0\n }\n\n // Write updated settings\n await fs.writeFile(settingsPath, JSON.stringify(settings, null, 2))\n \n } catch (error) {\n // Settings update failed, but don't fail the entire installation\n console.warn(`Warning: Could not update settings.json: ${error instanceof Error ? error.message : String(error)}`)\n throw new Error('SETTINGS_UPDATE_FAILED') // Signal that manual config is needed\n }\n}\n\nexport async function checkClaudeCodeSetup(): Promise<{\n hasClaudeDir: boolean\n hasSettings: boolean\n currentStatusline?: string\n}> {\n const claudeDir = './.claude'\n const settingsPath = path.join(claudeDir, 'settings.json')\n \n try {\n const dirExists = await fs.access(claudeDir).then(() => true).catch(() => false)\n const settingsExists = await fs.access(settingsPath).then(() => true).catch(() => false)\n \n let currentStatusline: string | undefined\n \n if (settingsExists) {\n try {\n const settings = JSON.parse(await fs.readFile(settingsPath, 'utf-8'))\n currentStatusline = settings.statusLine?.command\n } catch {\n // Ignore JSON parse errors\n }\n }\n \n return {\n hasClaudeDir: dirExists,\n hasSettings: settingsExists,\n currentStatusline\n }\n } catch {\n return {\n hasClaudeDir: false,\n hasSettings: false\n }\n }\n}"],"mappings":"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","names":["fs","path","process","fs","chalk","ora","path","path","testStatuslineScript","generateMockClaudeInput","chalk","previewCommand"]}
1
+ {"version":3,"sources":["../node_modules/tsup/assets/esm_shims.js","../src/utils/tester.ts","../src/cli/preview.ts","../src/index.ts","../src/cli/commands.ts","../src/cli/prompts.ts","../src/generators/bash-generator.ts","../src/features/colors.ts","../src/features/git.ts","../src/features/usage.ts","../src/utils/validator.ts","../src/utils/installer.ts"],"sourcesContent":["// Shim globals in esm bundle\nimport path from 'node:path'\nimport { fileURLToPath } from 'node:url'\n\nconst getFilename = () => fileURLToPath(import.meta.url)\nconst getDirname = () => path.dirname(getFilename())\n\nexport const __dirname = /* @__PURE__ */ getDirname()\nexport const __filename = /* @__PURE__ */ getFilename()\n","import { StatuslineConfig } from '../cli/prompts.js'\nimport { spawn } from 'child_process'\nimport { promises as fs } from 'fs'\nimport path from 'path'\n\nexport interface TestResult {\n success: boolean\n output: string\n error?: string\n executionTime: number\n}\n\nexport async function testStatuslineScript(script: string, mockData?: any): Promise<TestResult> {\n const startTime = Date.now()\n \n try {\n // Create temporary script file\n const tempDir = '/tmp'\n const scriptPath = path.join(tempDir, `statusline-test-${Date.now()}.sh`)\n \n await fs.writeFile(scriptPath, script, { mode: 0o755 })\n \n // Generate mock input if not provided\n const input = mockData || generateMockClaudeInput()\n \n // Execute script\n const result = await executeScript(scriptPath, JSON.stringify(input))\n \n // Cleanup\n await fs.unlink(scriptPath).catch(() => {}) // Ignore cleanup errors\n \n const executionTime = Date.now() - startTime\n \n return {\n success: result.success,\n output: result.output,\n error: result.error,\n executionTime\n }\n \n } catch (error) {\n return {\n success: false,\n output: '',\n error: error instanceof Error ? error.message : String(error),\n executionTime: Date.now() - startTime\n }\n }\n}\n\nexport function generateMockClaudeInput(config?: Partial<StatuslineConfig>): any {\n return {\n session_id: \"test-session-123\",\n transcript_path: \"/home/user/.claude/conversations/test.jsonl\",\n cwd: \"/home/user/projects/my-project\",\n workspace: {\n current_dir: \"/home/user/projects/my-project\"\n },\n model: {\n id: \"claude-opus-4-1-20250805\",\n display_name: \"Opus 4.1\",\n version: \"20250805\"\n }\n }\n}\n\nexport function generateMockCcusageOutput(): any {\n return {\n blocks: [\n {\n id: \"2025-08-13T08:00:00.000Z\",\n startTime: \"2025-08-13T08:00:00.000Z\",\n endTime: \"2025-08-13T13:00:00.000Z\",\n usageLimitResetTime: \"2025-08-13T13:00:00.000Z\",\n actualEndTime: \"2025-08-13T09:30:34.698Z\",\n isActive: true,\n isGap: false,\n entries: 12,\n tokenCounts: {\n inputTokens: 1250,\n outputTokens: 2830,\n cacheCreationInputTokens: 15000,\n cacheReadInputTokens: 45000\n },\n totalTokens: 64080,\n costUSD: 3.42,\n models: [\"claude-opus-4-1-20250805\"],\n burnRate: {\n tokensPerMinute: 850.5,\n tokensPerMinuteForIndicator: 850,\n costPerHour: 12.45\n },\n projection: {\n totalTokens: 128000,\n totalCost: 6.84,\n remainingMinutes: 210\n }\n }\n ]\n }\n}\n\nasync function executeScript(scriptPath: string, input: string): Promise<{ success: boolean, output: string, error?: string }> {\n return new Promise((resolve) => {\n const process = spawn('bash', [scriptPath], {\n stdio: ['pipe', 'pipe', 'pipe']\n })\n \n let stdout = ''\n let stderr = ''\n \n process.stdout.on('data', (data) => {\n stdout += data.toString()\n })\n \n process.stderr.on('data', (data) => {\n stderr += data.toString()\n })\n \n process.on('close', (code) => {\n resolve({\n success: code === 0,\n output: stdout.trim(),\n error: stderr.trim() || undefined\n })\n })\n \n process.on('error', (err) => {\n resolve({\n success: false,\n output: '',\n error: err.message\n })\n })\n \n // Send input and close stdin\n process.stdin.write(input)\n process.stdin.end()\n \n // Timeout after 5 seconds\n setTimeout(() => {\n process.kill()\n resolve({\n success: false,\n output: stdout,\n error: 'Script execution timed out (5s)'\n })\n }, 5000)\n })\n}\n\nexport function analyzeTestResult(result: TestResult, config: StatuslineConfig): {\n performance: 'excellent' | 'good' | 'slow' | 'timeout'\n hasRequiredFeatures: boolean\n issues: string[]\n suggestions: string[]\n} {\n const issues: string[] = []\n const suggestions: string[] = []\n \n // Performance analysis\n let performance: 'excellent' | 'good' | 'slow' | 'timeout'\n if (result.executionTime > 1000) {\n performance = 'timeout'\n issues.push('Script execution is very slow (>1s)')\n } else if (result.executionTime > 500) {\n performance = 'slow'\n issues.push('Script execution is slow (>500ms)')\n } else if (result.executionTime > 100) {\n performance = 'good'\n } else {\n performance = 'excellent'\n }\n \n // Feature validation\n let hasRequiredFeatures = true\n \n if (config.features.includes('directory') && !result.output.includes('projects')) {\n hasRequiredFeatures = false\n issues.push('Directory feature not working properly')\n }\n \n if (config.features.includes('model') && !result.output.includes('Opus')) {\n hasRequiredFeatures = false\n issues.push('Model feature not working properly')\n }\n \n if (config.features.includes('git') && config.ccusageIntegration && !result.output.includes('git')) {\n suggestions.push('Git integration may require actual git repository')\n }\n \n // Error analysis\n if (result.error) {\n issues.push(`Script errors: ${result.error}`)\n }\n \n if (!result.success) {\n issues.push('Script failed to execute successfully')\n }\n \n // Performance suggestions\n if (config.features.length > 6) {\n suggestions.push('Consider reducing number of features for better performance')\n }\n \n if (config.ccusageIntegration && result.executionTime > 200) {\n suggestions.push('ccusage integration may slow down statusline - consider caching')\n }\n \n return {\n performance,\n hasRequiredFeatures,\n issues,\n suggestions\n }\n}","import { StatuslineConfig } from './prompts.js'\nimport { generateBashStatusline } from '../generators/bash-generator.js'\nimport { testStatuslineScript, generateMockClaudeInput, analyzeTestResult } from '../utils/tester.js'\nimport { promises as fs } from 'fs'\nimport chalk from 'chalk'\nimport ora from 'ora'\n\nexport async function previewCommand(scriptPath: string): Promise<void> {\n console.log(chalk.cyan('๐Ÿ” Statusline Preview Mode\\n'))\n \n let script: string\n \n // Load existing statusline script\n try {\n const spinner = ora(`Loading statusline script from ${scriptPath}...`).start()\n script = await fs.readFile(scriptPath, 'utf-8')\n spinner.succeed('Script loaded!')\n \n // Try to extract config info from the script header\n const headerMatch = script.match(/# Theme: (\\w+) \\| Colors: (\\w+) \\| Features: ([^\\n]+)/i)\n if (headerMatch) {\n console.log(chalk.yellow('Detected Configuration:'))\n console.log(` Theme: ${headerMatch[1]}`)\n console.log(` Colors: ${headerMatch[2]}`) \n console.log(` Features: ${headerMatch[3]}\\n`)\n }\n \n // Extract generation info if available\n const generationMatch = script.match(/# Generated by cc-statusline.*\\n# Custom Claude Code statusline - Created: ([^\\n]+)/i)\n if (generationMatch) {\n console.log(chalk.gray(`Generated: ${generationMatch[1]}\\n`))\n }\n \n } catch (error) {\n console.error(chalk.red(`โŒ Failed to load script: ${error instanceof Error ? error.message : String(error)}`))\n return\n }\n \n // Test the script\n const testSpinner = ora('Testing statusline with mock data...').start()\n const mockInput = generateMockClaudeInput()\n \n console.log(chalk.gray('\\nMock Claude Code Input:'))\n console.log(chalk.gray(JSON.stringify(mockInput, null, 2)))\n \n const testResult = await testStatuslineScript(script, mockInput)\n \n if (testResult.success) {\n testSpinner.succeed(`Test completed in ${testResult.executionTime}ms`)\n \n console.log(chalk.green('\\nโœ… Statusline Output:'))\n console.log(chalk.white('โ”'.repeat(60)))\n console.log(testResult.output)\n console.log(chalk.white('โ”'.repeat(60)))\n \n // Basic performance analysis\n console.log(chalk.cyan(`\\n๐Ÿ“Š Performance: ${getPerformanceEmoji(getPerformanceLevel(testResult.executionTime))} ${getPerformanceLevel(testResult.executionTime)} (${testResult.executionTime}ms)`))\n \n // Basic output validation\n if (testResult.output.includes('๐Ÿ“') || testResult.output.includes('๐ŸŒฟ') || testResult.output.includes('๐Ÿค–')) {\n console.log(chalk.green('โœ… Statusline features appear to be working'))\n } else {\n console.log(chalk.yellow('โš ๏ธ Basic features may not be displaying correctly'))\n }\n \n } else {\n testSpinner.fail('Test failed')\n console.error(chalk.red(`\\nโŒ Error: ${testResult.error}`))\n if (testResult.output) {\n console.log(chalk.gray('\\nPartial output:'))\n console.log(testResult.output)\n }\n }\n \n console.log(chalk.green('\\nโœจ Preview complete! Use `cc-statusline init` to generate a new statusline.'))\n}\n\nfunction getPerformanceEmoji(performance: string): string {\n switch (performance) {\n case 'excellent': return '๐Ÿš€'\n case 'good': return 'โœ…'\n case 'slow': return 'โš ๏ธ'\n case 'timeout': return '๐ŸŒ'\n default: return 'โ“'\n }\n}\n\nfunction getPerformanceLevel(executionTime: number): string {\n if (executionTime > 1000) return 'timeout'\n if (executionTime > 500) return 'slow'\n if (executionTime > 100) return 'good'\n return 'excellent'\n}","import { Command } from 'commander'\nimport { initCommand } from './cli/commands.js'\nimport chalk from 'chalk'\n\nconst program = new Command()\n\nprogram\n .name('cc-statusline')\n .description('Interactive CLI tool for generating custom Claude Code statuslines')\n .version('1.2.3')\n\nprogram\n .command('init')\n .description('Create a custom statusline with interactive prompts')\n .option('-o, --output <path>', 'Output path for statusline.sh', './.claude/statusline.sh')\n .option('--no-install', 'Don\\'t automatically install to .claude/statusline.sh')\n .action(initCommand)\n\nprogram\n .command('preview')\n .description('Preview existing statusline.sh with mock data')\n .argument('<script-path>', 'Path to statusline.sh file to preview')\n .action(async (scriptPath) => {\n const { previewCommand } = await import('./cli/preview.js')\n await previewCommand(scriptPath)\n })\n\nprogram\n .command('test')\n .description('Test statusline with real Claude Code JSON input')\n .option('-c, --config <path>', 'Configuration file to test')\n .action(() => {\n console.log(chalk.yellow('Test command coming soon!'))\n })\n\n// Show help if no command provided\nif (!process.argv.slice(2).length) {\n program.outputHelp()\n}\n\nprogram.parse(process.argv)","import { collectConfiguration, displayConfigSummary } from './prompts.js'\nimport { generateBashStatusline } from '../generators/bash-generator.js'\nimport { validateConfig } from '../utils/validator.js'\nimport { installStatusline } from '../utils/installer.js'\nimport chalk from 'chalk'\nimport ora from 'ora'\nimport path from 'path'\nimport os from 'os'\n\ninterface InitOptions {\n output?: string\n install?: boolean\n}\n\nexport async function initCommand(options: InitOptions): Promise<void> {\n try {\n const spinner = ora('Initializing statusline generator...').start()\n await new Promise(resolve => setTimeout(resolve, 500)) // Brief pause for UX\n spinner.stop()\n\n // Collect user configuration\n const config = await collectConfiguration()\n \n // Validate configuration\n const validation = validateConfig(config)\n if (!validation.isValid) {\n console.error(chalk.red('โŒ Configuration validation failed:'))\n validation.errors.forEach(error => console.error(chalk.red(` โ€ข ${error}`)))\n process.exit(1)\n }\n\n // Generate statusline script\n const generationSpinner = ora('Generating statusline script...').start()\n \n const script = generateBashStatusline(config)\n const filename = 'statusline.sh'\n \n generationSpinner.succeed('Statusline script generated!')\n\n // Show preview of what it will look like\n console.log(chalk.cyan('\\nโœจ Your statusline will look like:'))\n console.log(chalk.white('โ”'.repeat(60)))\n \n // Generate preview using the test function\n const { testStatuslineScript, generateMockClaudeInput } = await import('../utils/tester.js')\n const mockInput = generateMockClaudeInput()\n const testResult = await testStatuslineScript(script, mockInput)\n \n if (testResult.success) {\n console.log(testResult.output)\n } else {\n console.log(chalk.gray('๐Ÿ“ ~/projects/my-app ๐ŸŒฟ main ๐Ÿค– Claude ๐Ÿ’ต $2.48 ($12.50/h)'))\n console.log(chalk.gray('(Preview unavailable - will work when Claude Code runs it)'))\n }\n \n console.log(chalk.white('โ”'.repeat(60)))\n\n // Determine output path based on installation location\n const isGlobal = config.installLocation === 'global'\n const baseDir = isGlobal ? os.homedir() : '.'\n const outputPath = options.output || path.join(baseDir, '.claude', filename)\n const resolvedPath = path.resolve(outputPath)\n\n // Install the statusline\n if (options.install !== false) {\n const installSpinner = ora('Installing statusline...').start()\n \n try {\n await installStatusline(script, resolvedPath, config)\n installSpinner.succeed('โœ… Statusline installed!')\n \n console.log(chalk.green('\\n๐ŸŽ‰ Success! Your custom statusline is ready!'))\n console.log(chalk.cyan(`\\n๐Ÿ“ ${isGlobal ? 'Global' : 'Project'} installation complete: ${chalk.white(resolvedPath)}`))\n console.log(chalk.cyan('\\nNext steps:'))\n console.log(chalk.white(' 1. Restart Claude Code to see your new statusline'))\n console.log(chalk.white(' 2. Usage statistics work via: npx ccusage@latest'))\n \n } catch (error) {\n installSpinner.fail('Failed to install statusline')\n \n if (error instanceof Error && error.message === 'USER_CANCELLED_OVERWRITE') {\n console.log(chalk.yellow('\\nโš ๏ธ Installation cancelled. Existing statusline.sh was not overwritten.'))\n } else if (error instanceof Error && error.message === 'SETTINGS_UPDATE_FAILED') {\n const commandPath = isGlobal ? '~/.claude/statusline.sh' : '.claude/statusline.sh'\n console.log(chalk.yellow('\\nโš ๏ธ Settings.json could not be updated automatically.'))\n console.log(chalk.cyan('\\nManual Configuration Required:'))\n console.log(chalk.white(`Add this to your ${isGlobal ? '~/.claude' : '.claude'}/settings.json file:`))\n console.log(chalk.gray('\\n{'))\n console.log(chalk.gray(' \"statusLine\": {'))\n console.log(chalk.gray(' \"type\": \"command\",'))\n console.log(chalk.gray(` \"command\": \"${commandPath}\",`))\n console.log(chalk.gray(' \"padding\": 0'))\n console.log(chalk.gray(' }'))\n console.log(chalk.gray('}'))\n console.log(chalk.cyan(`\\n๐Ÿ“ Statusline script saved to: ${chalk.white(resolvedPath)}`))\n } else {\n console.error(chalk.red(`Error: ${error instanceof Error ? error.message : String(error)}`))\n console.log(chalk.cyan(`\\n๐Ÿ“ You can manually save the script to: ${chalk.white(resolvedPath)}`))\n }\n }\n } else {\n // Just display where to save it\n console.log(chalk.green('\\nโœ… Statusline generated successfully!'))\n console.log(chalk.cyan(`\\n๐Ÿ“ Save this script to: ${chalk.white(resolvedPath)}`))\n console.log(chalk.cyan('\\nThen restart Claude Code to see your new statusline.'))\n }\n\n } catch (error) {\n console.error(chalk.red('โŒ An error occurred:'))\n console.error(chalk.red(error instanceof Error ? error.message : String(error)))\n process.exit(1)\n }\n}","import inquirer from 'inquirer'\n\nexport interface StatuslineConfig {\n features: string[]\n runtime: 'bash' | 'python' | 'node'\n colors: boolean\n theme: 'minimal' | 'detailed' | 'compact'\n ccusageIntegration: boolean\n logging: boolean\n customEmojis: boolean\n installLocation?: 'global' | 'project'\n}\n\nexport async function collectConfiguration(): Promise<StatuslineConfig> {\n console.log('๐Ÿš€ Welcome to cc-statusline! Let\\'s create your custom Claude Code statusline.\\n')\n console.log('โœจ All features are enabled by default. Use โ†‘/โ†“ arrows to navigate, SPACE to toggle, ENTER to continue.\\n')\n \n const config = await inquirer.prompt([\n {\n type: 'checkbox',\n name: 'features',\n message: 'Select statusline features (scroll down for more options):',\n choices: [\n { name: '๐Ÿ“ Working Directory', value: 'directory', checked: true },\n { name: '๐ŸŒฟ Git Branch', value: 'git', checked: true },\n { name: '๐Ÿค– Model Name & Version', value: 'model', checked: true },\n { name: '๐Ÿง  Context Remaining', value: 'context', checked: true },\n { name: '๐Ÿ’ต Usage & Cost', value: 'usage', checked: true },\n { name: 'โŒ› Session Time Remaining', value: 'session', checked: true },\n { name: '๐Ÿ“Š Token Statistics', value: 'tokens', checked: true },\n { name: 'โšก Burn Rate (tokens/min)', value: 'burnrate', checked: true }\n ],\n validate: (answer: string[]) => {\n if (answer.length < 1) {\n return 'You must choose at least one feature.'\n }\n return true\n },\n pageSize: 10\n },\n {\n type: 'confirm',\n name: 'colors',\n message: '\\n๐ŸŽจ Enable modern color scheme and emojis?',\n default: true\n },\n {\n type: 'confirm',\n name: 'logging',\n message: '\\n๐Ÿ“ Enable debug logging to .claude/statusline.log?',\n default: false\n },\n {\n type: 'list',\n name: 'installLocation',\n message: '\\n๐Ÿ“ Where would you like to install the statusline?',\n choices: [\n { name: '๐Ÿ  Global (~/.claude) - Use across all projects', value: 'global' },\n { name: '๐Ÿ“‚ Project (./.claude) - Only for this project', value: 'project' }\n ],\n default: 'project'\n }\n ])\n\n // Set intelligent defaults\n return {\n features: config.features,\n runtime: 'bash',\n colors: config.colors,\n theme: 'detailed',\n ccusageIntegration: true, // Always enabled since npx works\n logging: config.logging,\n customEmojis: false,\n installLocation: config.installLocation\n } as StatuslineConfig\n}\n\nexport function displayConfigSummary(config: StatuslineConfig): void {\n console.log('\\nโœ… Configuration Summary:')\n console.log(` Runtime: ${config.runtime}`)\n console.log(` Theme: ${config.theme}`)\n console.log(` Colors: ${config.colors ? 'โœ…' : 'โŒ'}`)\n console.log(` Features: ${config.features.join(', ')}`)\n \n if (config.ccusageIntegration) {\n console.log(' ๐Ÿ“Š ccusage integration enabled')\n }\n \n if (config.logging) {\n console.log(' ๐Ÿ“ Debug logging enabled')\n }\n \n console.log('')\n}","import { StatuslineConfig } from '../cli/prompts.js'\nimport { generateColorBashCode, generateBasicColors } from '../features/colors.js'\nimport { generateGitBashCode, generateGitDisplayCode, generateGitUtilities } from '../features/git.js'\nimport { generateUsageBashCode, generateUsageDisplayCode, generateUsageUtilities } from '../features/usage.js'\n\nexport function generateBashStatusline(config: StatuslineConfig): string {\n const hasGit = config.features.includes('git')\n const hasUsage = config.features.some(f => ['usage', 'session', 'tokens', 'burnrate'].includes(f))\n const hasDirectory = config.features.includes('directory')\n const hasModel = config.features.includes('model')\n const hasContext = config.features.includes('context')\n\n // Build usage feature config\n const usageConfig = {\n enabled: hasUsage && config.ccusageIntegration,\n showCost: config.features.includes('usage'),\n showTokens: config.features.includes('tokens'),\n showBurnRate: config.features.includes('burnrate'),\n showSession: config.features.includes('session'),\n showProgressBar: config.theme !== 'minimal' && config.features.includes('session')\n }\n\n // Build git feature config\n const gitConfig = {\n enabled: hasGit,\n showBranch: hasGit,\n showChanges: false, // Removed delta changes per user request\n compactMode: config.theme === 'compact'\n }\n\n const timestamp = new Date().toISOString()\n const script = `#!/bin/bash\n# Generated by cc-statusline (https://www.npmjs.com/package/@chongdashu/cc-statusline)\n# Custom Claude Code statusline - Created: ${timestamp}\n# Theme: ${config.theme} | Colors: ${config.colors} | Features: ${config.features.join(', ')}\n\n${config.logging ? generateLoggingCode() : ''}\ninput=$(cat)\n${generateColorBashCode({ enabled: config.colors, theme: config.theme })}\n${config.colors ? generateBasicColors() : ''}\n${hasUsage ? generateUsageUtilities() : ''}\n${hasGit ? generateGitUtilities() : ''}\n${generateBasicDataExtraction(hasDirectory, hasModel, hasContext)}\n${hasGit ? generateGitBashCode(gitConfig, config.colors) : ''}\n${hasContext ? generateContextBashCode(config.colors) : ''}\n${hasUsage ? generateUsageBashCode(usageConfig, config.colors) : ''}\n${config.logging ? generateLoggingOutput() : ''}\n${generateDisplaySection(config, gitConfig, usageConfig)}\n`\n\n return script.replace(/\\n\\n\\n+/g, '\\n\\n').trim() + '\\n'\n}\n\nfunction generateLoggingCode(): string {\n return `\nLOG_FILE=\"\\${HOME}/.claude/statusline.log\"\nTIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')\n\n# ---- logging ----\n{\n echo \"[$TIMESTAMP] Status line triggered with input:\"\n (echo \"$input\" | jq . 2>/dev/null) || echo \"$input\"\n echo \"---\"\n} >> \"$LOG_FILE\" 2>/dev/null\n`\n}\n\nfunction generateBasicDataExtraction(hasDirectory: boolean, hasModel: boolean, hasContext: boolean): string {\n return `\n# ---- basics ----\nif command -v jq >/dev/null 2>&1; then${hasDirectory ? `\n current_dir=$(echo \"$input\" | jq -r '.workspace.current_dir // .cwd // \"unknown\"' 2>/dev/null | sed \"s|^$HOME|~|g\")` : ''}${hasModel ? `\n model_name=$(echo \"$input\" | jq -r '.model.display_name // \"Claude\"' 2>/dev/null)\n model_version=$(echo \"$input\" | jq -r '.model.version // \"\"' 2>/dev/null)` : ''}${hasContext ? `\n session_id=$(echo \"$input\" | jq -r '.session_id // \"\"' 2>/dev/null)` : ''}\n cc_version=$(echo \"$input\" | jq -r '.version // \"\"' 2>/dev/null)\n output_style=$(echo \"$input\" | jq -r '.output_style.name // \"\"' 2>/dev/null)\nelse${hasDirectory ? `\n current_dir=\"unknown\"` : ''}${hasModel ? `\n model_name=\"Claude\"; model_version=\"\"` : ''}${hasContext ? `\n session_id=\"\"` : ''}\n cc_version=\"\"\n output_style=\"\"\nfi\n`\n}\n\nfunction generateContextBashCode(colors: boolean): string {\n return `\n# ---- context window calculation ----\ncontext_pct=\"\"\ncontext_color() { if [ \"$use_color\" -eq 1 ]; then printf '\\\\033[1;37m'; fi; } # default white\n\n# Determine max context based on model\nget_max_context() {\n local model_name=\"$1\"\n case \"$model_name\" in\n *\"Opus 4\"*|*\"opus 4\"*|*\"Opus\"*|*\"opus\"*)\n echo \"200000\" # 200K for all Opus versions\n ;;\n *\"Sonnet 4\"*|*\"sonnet 4\"*|*\"Sonnet 3.5\"*|*\"sonnet 3.5\"*|*\"Sonnet\"*|*\"sonnet\"*)\n echo \"200000\" # 200K for Sonnet 3.5+ and 4.x\n ;;\n *\"Haiku 3.5\"*|*\"haiku 3.5\"*|*\"Haiku 4\"*|*\"haiku 4\"*|*\"Haiku\"*|*\"haiku\"*)\n echo \"200000\" # 200K for modern Haiku\n ;;\n *\"Claude 3 Haiku\"*|*\"claude 3 haiku\"*)\n echo \"100000\" # 100K for original Claude 3 Haiku\n ;;\n *)\n echo \"200000\" # Default to 200K\n ;;\n esac\n}\n\nif [ -n \"$session_id\" ] && command -v jq >/dev/null 2>&1; then\n MAX_CONTEXT=$(get_max_context \"$model_name\")\n \n # Convert current dir to session file path\n project_dir=$(echo \"$current_dir\" | sed \"s|~|$HOME|g\" | sed 's|/|-|g' | sed 's|^-||')\n session_file=\"$HOME/.claude/projects/-\\${project_dir}/\\${session_id}.jsonl\"\n \n if [ -f \"$session_file\" ]; then\n # Get the latest input token count from the session file\n latest_tokens=$(tail -20 \"$session_file\" | jq -r 'select(.message.usage) | .message.usage | ((.input_tokens // 0) + (.cache_read_input_tokens // 0))' 2>/dev/null | tail -1)\n \n if [ -n \"$latest_tokens\" ] && [ \"$latest_tokens\" -gt 0 ]; then\n context_used_pct=$(( latest_tokens * 100 / MAX_CONTEXT ))\n context_remaining_pct=$(( 100 - context_used_pct ))\n \n # Set color based on remaining percentage\n if [ \"$context_remaining_pct\" -le 20 ]; then\n context_color() { if [ \"$use_color\" -eq 1 ]; then printf '\\\\033[38;5;203m'; fi; } # coral red\n elif [ \"$context_remaining_pct\" -le 40 ]; then\n context_color() { if [ \"$use_color\" -eq 1 ]; then printf '\\\\033[38;5;215m'; fi; } # peach\n else\n context_color() { if [ \"$use_color\" -eq 1 ]; then printf '\\\\033[38;5;158m'; fi; } # mint green\n fi\n \n context_pct=\"\\${context_remaining_pct}%\"\n fi\n fi\nfi\n`\n}\n\nfunction generateLoggingOutput(): string {\n return `\n# ---- log extracted data ----\n{\n echo \"[\\$TIMESTAMP] Extracted: dir=\\${current_dir:-}, model=\\${model_name:-}, version=\\${model_version:-}, git=\\${git_branch:-}, context=\\${context_pct:-}, cost=\\${cost_usd:-}, cost_ph=\\${cost_per_hour:-}, tokens=\\${tot_tokens:-}, tpm=\\${tpm:-}, session_pct=\\${session_pct:-}\"\n} >> \"$LOG_FILE\" 2>/dev/null\n`\n}\n\nfunction generateDisplaySection(config: StatuslineConfig, gitConfig: any, usageConfig: any): string {\n const emojis = config.colors && !config.customEmojis\n\n return `\n# ---- render statusline ----\n# Line 1: Core info (directory, git, model, claude code version, output style)\n${config.features.includes('directory') ? `printf '๐Ÿ“ %s%s%s' \"$(dir_color)\" \"$current_dir\" \"$(rst)\"` : ''}${gitConfig.enabled ? `\nif [ -n \"$git_branch\" ]; then\n printf ' ๐ŸŒฟ %s%s%s' \"$(git_color)\" \"$git_branch\" \"$(rst)\"\nfi` : ''}${config.features.includes('model') ? `\nprintf ' ๐Ÿค– %s%s%s' \"$(model_color)\" \"$model_name\" \"$(rst)\"\nif [ -n \"$model_version\" ] && [ \"$model_version\" != \"null\" ]; then\n printf ' ๐Ÿท๏ธ %s%s%s' \"$(version_color)\" \"$model_version\" \"$(rst)\"\nfi` : ''}\nif [ -n \"$cc_version\" ] && [ \"$cc_version\" != \"null\" ]; then\n printf ' ๐Ÿ“Ÿ %sv%s%s' \"$(cc_version_color)\" \"$cc_version\" \"$(rst)\"\nfi\nif [ -n \"$output_style\" ] && [ \"$output_style\" != \"null\" ]; then\n printf ' ๐ŸŽจ %s%s%s' \"$(style_color)\" \"$output_style\" \"$(rst)\"\nfi\n\n# Line 2: Context and session time\nline2=\"\"${config.features.includes('context') ? `\nif [ -n \"$context_pct\" ]; then\n context_bar=$(progress_bar \"$context_remaining_pct\" 10)\n line2=\"๐Ÿง  $(context_color)Context Remaining: \\${context_pct} [\\${context_bar}]$(rst)\"\nfi` : ''}${usageConfig.showSession ? `\nif [ -n \"$session_txt\" ]; then\n if [ -n \"$line2\" ]; then\n line2=\"$line2 โŒ› $(session_color)\\${session_txt}$(rst) $(session_color)[\\${session_bar}]$(rst)\"\n else\n line2=\"โŒ› $(session_color)\\${session_txt}$(rst) $(session_color)[\\${session_bar}]$(rst)\"\n fi\nfi` : ''}${config.features.includes('context') ? `\nif [ -z \"$line2\" ] && [ -z \"$context_pct\" ]; then\n line2=\"๐Ÿง  $(context_color)Context Remaining: TBD$(rst)\"\nfi` : ''}\n\n# Line 3: Cost and usage analytics\nline3=\"\"${usageConfig.showCost ? `\nif [ -n \"$cost_usd\" ] && [[ \"$cost_usd\" =~ ^[0-9.]+$ ]]; then${usageConfig.showBurnRate ? `\n if [ -n \"$cost_per_hour\" ] && [[ \"$cost_per_hour\" =~ ^[0-9.]+$ ]]; then\n cost_per_hour_formatted=$(printf '%.2f' \"$cost_per_hour\")\n line3=\"๐Ÿ’ฐ $(cost_color)\\\\$$(printf '%.2f' \\\\\"$cost_usd\\\\\")$(rst) ($(burn_color)\\\\$\\${cost_per_hour_formatted}/h$(rst))\"\n else\n line3=\"๐Ÿ’ฐ $(cost_color)\\\\$$(printf '%.2f' \\\\\"$cost_usd\\\\\")$(rst)\"\n fi` : `\n line3=\"๐Ÿ’ฐ $(cost_color)\\\\$$(printf '%.2f' \\\\\"$cost_usd\\\\\")$(rst)\"`}\nfi` : ''}${usageConfig.showTokens ? `\nif [ -n \"$tot_tokens\" ] && [[ \"$tot_tokens\" =~ ^[0-9]+$ ]]; then${usageConfig.showBurnRate ? `\n if [ -n \"$tpm\" ] && [[ \"$tpm\" =~ ^[0-9.]+$ ]]; then\n tpm_formatted=$(printf '%.0f' \"$tpm\")\n if [ -n \"$line3\" ]; then\n line3=\"$line3 ๐Ÿ“Š $(usage_color)\\${tot_tokens} tok (\\${tpm_formatted} tpm)$(rst)\"\n else\n line3=\"๐Ÿ“Š $(usage_color)\\${tot_tokens} tok (\\${tpm_formatted} tpm)$(rst)\"\n fi\n else\n if [ -n \"$line3\" ]; then\n line3=\"$line3 ๐Ÿ“Š $(usage_color)\\${tot_tokens} tok$(rst)\"\n else\n line3=\"๐Ÿ“Š $(usage_color)\\${tot_tokens} tok$(rst)\"\n fi\n fi` : `\n if [ -n \"$line3\" ]; then\n line3=\"$line3 ๐Ÿ“Š $(usage_color)\\${tot_tokens} tok$(rst)\"\n else\n line3=\"๐Ÿ“Š $(usage_color)\\${tot_tokens} tok$(rst)\"\n fi`}\nfi` : ''}\n\n# Print lines\nif [ -n \"$line2\" ]; then\n printf '\\\\n%s' \"$line2\"\nfi\nif [ -n \"$line3\" ]; then\n printf '\\\\n%s' \"$line3\"\nfi\nprintf '\\\\n'`\n}","export interface ColorConfig {\n enabled: boolean\n theme: 'minimal' | 'detailed' | 'compact'\n}\n\nexport function generateColorBashCode(config: ColorConfig): string {\n if (!config.enabled) {\n return `\n# ---- color helpers (disabled) ----\nuse_color=0\nC() { :; }\nRST() { :; }\n`\n }\n\n return `\n# ---- color helpers (force colors for Claude Code) ----\nuse_color=1\n[ -n \"$NO_COLOR\" ] && use_color=0\n\nC() { if [ \"$use_color\" -eq 1 ]; then printf '\\\\033[%sm' \"$1\"; fi; }\nRST() { if [ \"$use_color\" -eq 1 ]; then printf '\\\\033[0m'; fi; }\n`\n}\n\nexport function generateBasicColors(): string {\n return `\n# ---- modern sleek colors ----\ndir_color() { if [ \"$use_color\" -eq 1 ]; then printf '\\\\033[38;5;117m'; fi; } # sky blue\nmodel_color() { if [ \"$use_color\" -eq 1 ]; then printf '\\\\033[38;5;147m'; fi; } # light purple \nversion_color() { if [ \"$use_color\" -eq 1 ]; then printf '\\\\033[38;5;180m'; fi; } # soft yellow\ncc_version_color() { if [ \"$use_color\" -eq 1 ]; then printf '\\\\033[38;5;249m'; fi; } # light gray\nstyle_color() { if [ \"$use_color\" -eq 1 ]; then printf '\\\\033[38;5;245m'; fi; } # gray\nrst() { if [ \"$use_color\" -eq 1 ]; then printf '\\\\033[0m'; fi; }\n`\n}\n\nexport const COLOR_CODES = {\n // Basic colors\n BLACK: '30',\n RED: '31', \n GREEN: '32',\n YELLOW: '33',\n BLUE: '34',\n MAGENTA: '35',\n CYAN: '36',\n WHITE: '37',\n \n // Bright colors (bold)\n BRIGHT_BLACK: '1;30',\n BRIGHT_RED: '1;31',\n BRIGHT_GREEN: '1;32', \n BRIGHT_YELLOW: '1;33',\n BRIGHT_BLUE: '1;34',\n BRIGHT_MAGENTA: '1;35',\n BRIGHT_CYAN: '1;36',\n BRIGHT_WHITE: '1;37',\n \n // Reset\n RESET: '0'\n} as const\n\nexport function getThemeColors(theme: 'minimal' | 'detailed' | 'compact') {\n switch (theme) {\n case 'minimal':\n return {\n directory: COLOR_CODES.CYAN,\n git: COLOR_CODES.GREEN,\n model: COLOR_CODES.MAGENTA,\n usage: COLOR_CODES.YELLOW,\n session: COLOR_CODES.BLUE\n }\n case 'detailed':\n return {\n directory: COLOR_CODES.BRIGHT_CYAN,\n git: COLOR_CODES.BRIGHT_GREEN,\n model: COLOR_CODES.BRIGHT_MAGENTA,\n usage: COLOR_CODES.BRIGHT_YELLOW,\n session: COLOR_CODES.BRIGHT_BLUE\n }\n case 'compact':\n return {\n directory: COLOR_CODES.CYAN,\n git: COLOR_CODES.GREEN,\n model: COLOR_CODES.BLUE,\n usage: COLOR_CODES.YELLOW,\n session: COLOR_CODES.RED\n }\n }\n}","export interface GitFeature {\n enabled: boolean\n showBranch: boolean\n showChanges: boolean\n compactMode: boolean\n}\n\nexport function generateGitBashCode(config: GitFeature, colors: boolean): string {\n if (!config.enabled) return ''\n\n const colorCode = colors ? `\n# ---- git colors ----\ngit_color() { if [ \"$use_color\" -eq 1 ]; then printf '\\\\033[38;5;150m'; fi; } # soft green\nrst() { if [ \"$use_color\" -eq 1 ]; then printf '\\\\033[0m'; fi; }\n` : `\ngit_color() { :; }\nrst() { :; }\n`\n\n return `${colorCode}\n# ---- git ----\ngit_branch=\"\"\nif git rev-parse --git-dir >/dev/null 2>&1; then\n git_branch=$(git branch --show-current 2>/dev/null || git rev-parse --short HEAD 2>/dev/null)\nfi`\n}\n\nexport function generateGitDisplayCode(config: GitFeature, colors: boolean, emojis: boolean): string {\n if (!config.enabled) return ''\n\n const branchEmoji = emojis ? '๐ŸŒฟ' : 'git:'\n\n let displayCode = `\n# git display\nif [ -n \"$git_branch\" ]; then\n printf ' ${branchEmoji} %s%s%s' \"$(git_color)\" \"$git_branch\" \"$(rst)\"\nfi`\n\n return displayCode\n}\n\nexport function generateGitUtilities(): string {\n return `\n# git utilities\nnum_or_zero() { v=\"$1\"; [[ \"$v\" =~ ^[0-9]+$ ]] && echo \"$v\" || echo 0; }`\n}","export interface UsageFeature {\n enabled: boolean\n showCost: boolean\n showTokens: boolean\n showBurnRate: boolean\n showSession: boolean\n showProgressBar: boolean\n}\n\nexport function generateUsageBashCode(config: UsageFeature, colors: boolean): string {\n if (!config.enabled) return ''\n\n const colorCode = colors ? `\n# ---- usage colors ----\nusage_color() { if [ \"$use_color\" -eq 1 ]; then printf '\\\\033[38;5;189m'; fi; } # lavender\ncost_color() { if [ \"$use_color\" -eq 1 ]; then printf '\\\\033[38;5;222m'; fi; } # light gold\nburn_color() { if [ \"$use_color\" -eq 1 ]; then printf '\\\\033[38;5;220m'; fi; } # bright gold\nsession_color() { \n rem_pct=$(( 100 - session_pct ))\n if (( rem_pct <= 10 )); then SCLR='38;5;210' # light pink\n elif (( rem_pct <= 25 )); then SCLR='38;5;228' # light yellow \n else SCLR='38;5;194'; fi # light green\n if [ \"$use_color\" -eq 1 ]; then printf '\\\\033[%sm' \"$SCLR\"; fi\n}\n` : `\nusage_color() { :; }\ncost_color() { :; }\nburn_color() { :; }\nsession_color() { :; }\n`\n\n return `${colorCode}\n# ---- ccusage integration ----\nsession_txt=\"\"; session_pct=0; session_bar=\"\"\ncost_usd=\"\"; cost_per_hour=\"\"; tpm=\"\"; tot_tokens=\"\"\n\nif command -v jq >/dev/null 2>&1; then\n # Implement file-based locking to prevent concurrent executions\n LOCK_FILE=\"/tmp/ccusage_statusline.lock\"\n LOCK_PID_FILE=\"/tmp/ccusage_statusline.pid\"\n \n # Function to check if process is still running\n is_process_running() {\n local pid=$1\n if [ -z \"$pid\" ]; then return 1; fi\n # Check if process exists (works on Linux, macOS, BSD)\n kill -0 \"$pid\" 2>/dev/null\n }\n \n # Try to acquire lock\n if mkdir \"$LOCK_FILE\" 2>/dev/null; then\n # Lock acquired, save our PID\n echo $$ > \"$LOCK_PID_FILE\"\n \n # Run ccusage with timeout\n if command -v timeout >/dev/null 2>&1; then\n blocks_output=$(timeout 3s npx ccusage@latest blocks --json 2>/dev/null || timeout 3s ccusage blocks --json 2>/dev/null)\n elif command -v gtimeout >/dev/null 2>&1; then\n # macOS with coreutils installed\n blocks_output=$(gtimeout 3s npx ccusage@latest blocks --json 2>/dev/null || gtimeout 3s ccusage blocks --json 2>/dev/null)\n else\n # Fallback: use background process with sleep-based timeout\n (npx ccusage@latest blocks --json 2>/dev/null || ccusage blocks --json 2>/dev/null) &\n ccusage_pid=$!\n sleep 3\n if kill -0 \"$ccusage_pid\" 2>/dev/null; then\n kill \"$ccusage_pid\" 2>/dev/null\n blocks_output=\"\"\n else\n wait \"$ccusage_pid\"\n blocks_output=$(cat)\n fi\n fi\n \n # Clean up lock\n rm -f \"$LOCK_PID_FILE\" 2>/dev/null\n rmdir \"$LOCK_FILE\" 2>/dev/null\n else\n # Lock exists, check if it's stale\n if [ -f \"$LOCK_PID_FILE\" ]; then\n old_pid=$(cat \"$LOCK_PID_FILE\" 2>/dev/null)\n if ! is_process_running \"$old_pid\"; then\n # Stale lock, clean it up\n rm -f \"$LOCK_PID_FILE\" 2>/dev/null\n rmdir \"$LOCK_FILE\" 2>/dev/null\n fi\n fi\n # Skip ccusage call to prevent pile-up\n blocks_output=\"\"\n fi\n if [ -n \"$blocks_output\" ]; then\n active_block=$(echo \"$blocks_output\" | jq -c '.blocks[] | select(.isActive == true)' 2>/dev/null | head -n1)\n if [ -n \"$active_block\" ]; then${config.showCost ? `\n cost_usd=$(echo \"$active_block\" | jq -r '.costUSD // empty')\n cost_per_hour=$(echo \"$active_block\" | jq -r '.burnRate.costPerHour // empty')` : ''}${config.showTokens ? `\n tot_tokens=$(echo \"$active_block\" | jq -r '.totalTokens // empty')` : ''}${config.showBurnRate ? `\n tpm=$(echo \"$active_block\" | jq -r '.burnRate.tokensPerMinute // empty')` : ''}${config.showSession || config.showProgressBar ? `\n \n # Session time calculation\n reset_time_str=$(echo \"$active_block\" | jq -r '.usageLimitResetTime // .endTime // empty')\n start_time_str=$(echo \"$active_block\" | jq -r '.startTime // empty')\n \n if [ -n \"$reset_time_str\" ] && [ -n \"$start_time_str\" ]; then\n start_sec=$(to_epoch \"$start_time_str\"); end_sec=$(to_epoch \"$reset_time_str\"); now_sec=$(date +%s)\n total=$(( end_sec - start_sec )); (( total<1 )) && total=1\n elapsed=$(( now_sec - start_sec )); (( elapsed<0 ))&&elapsed=0; (( elapsed>total ))&&elapsed=$total\n session_pct=$(( elapsed * 100 / total ))\n remaining=$(( end_sec - now_sec )); (( remaining<0 )) && remaining=0\n rh=$(( remaining / 3600 )); rm=$(( (remaining % 3600) / 60 ))\n end_hm=$(fmt_time_hm \"$end_sec\")${config.showSession ? `\n session_txt=\"$(printf '%dh %dm until reset at %s (%d%%)' \"$rh\" \"$rm\" \"$end_hm\" \"$session_pct\")\"` : ''}${config.showProgressBar ? `\n session_bar=$(progress_bar \"$session_pct\" 10)` : ''}\n fi` : ''}\n fi\n fi\nfi`\n}\n\nexport function generateUsageUtilities(): string {\n return `\n# ---- time helpers ----\nto_epoch() {\n ts=\"$1\"\n if command -v gdate >/dev/null 2>&1; then gdate -d \"$ts\" +%s 2>/dev/null && return; fi\n date -u -j -f \"%Y-%m-%dT%H:%M:%S%z\" \"\\${ts/Z/+0000}\" +%s 2>/dev/null && return\n python3 - \"$ts\" <<'PY' 2>/dev/null\nimport sys, datetime\ns=sys.argv[1].replace('Z','+00:00')\nprint(int(datetime.datetime.fromisoformat(s).timestamp()))\nPY\n}\n\nfmt_time_hm() {\n epoch=\"$1\"\n if date -r 0 +%s >/dev/null 2>&1; then date -r \"$epoch\" +\"%H:%M\"; else date -d \"@$epoch\" +\"%H:%M\"; fi\n}\n\nprogress_bar() {\n pct=\"\\${1:-0}\"; width=\"\\${2:-10}\"\n [[ \"$pct\" =~ ^[0-9]+$ ]] || pct=0; ((pct<0))&&pct=0; ((pct>100))&&pct=100\n filled=$(( pct * width / 100 )); empty=$(( width - filled ))\n printf '%*s' \"$filled\" '' | tr ' ' '='\n printf '%*s' \"$empty\" '' | tr ' ' '-'\n}`\n}\n\nexport function generateUsageDisplayCode(config: UsageFeature, colors: boolean, emojis: boolean): string {\n if (!config.enabled) return ''\n\n let displayCode = ''\n\n if (config.showSession) {\n const sessionEmoji = emojis ? 'โŒ›' : 'session:'\n displayCode += `\n# session time\nif [ -n \"$session_txt\" ]; then\n printf ' ${sessionEmoji} %s%s%s' \"$(session_color)\" \"$session_txt\" \"$(rst)\"${config.showProgressBar ? `\n printf ' %s[%s]%s' \"$(session_color)\" \"$session_bar\" \"$(rst)\"` : ''}\nfi`\n }\n\n if (config.showCost) {\n const costEmoji = emojis ? '๐Ÿ’ต' : '$'\n displayCode += `\n# cost\nif [ -n \"$cost_usd\" ] && [[ \"$cost_usd\" =~ ^[0-9.]+$ ]]; then\n if [ -n \"$cost_per_hour\" ] && [[ \"$cost_per_hour\" =~ ^[0-9.]+$ ]]; then\n printf ' ${costEmoji} %s$%.2f ($%.2f/h)%s' \"$(cost_color)\" \"$cost_usd\" \"$cost_per_hour\" \"$(rst)\"\n else\n printf ' ${costEmoji} %s$%.2f%s' \"$(cost_color)\" \"$cost_usd\" \"$(rst)\"\n fi\nfi`\n }\n\n if (config.showTokens) {\n const tokenEmoji = emojis ? '๐Ÿ“Š' : 'tok:'\n displayCode += `\n# tokens\nif [ -n \"$tot_tokens\" ] && [[ \"$tot_tokens\" =~ ^[0-9]+$ ]]; then\n if [ -n \"$tpm\" ] && [[ \"$tpm\" =~ ^[0-9.]+$ ]] && ${config.showBurnRate ? 'true' : 'false'}; then\n printf ' ${tokenEmoji} %s%s tok (%.0f tpm)%s' \"$(usage_color)\" \"$tot_tokens\" \"$tpm\" \"$(rst)\"\n else\n printf ' ${tokenEmoji} %s%s tok%s' \"$(usage_color)\" \"$tot_tokens\" \"$(rst)\"\n fi\nfi`\n }\n\n return displayCode\n}","import { StatuslineConfig } from '../cli/prompts.js'\n\nexport interface ValidationResult {\n isValid: boolean\n errors: string[]\n warnings: string[]\n}\n\nexport function validateConfig(config: StatuslineConfig): ValidationResult {\n const errors: string[] = []\n const warnings: string[] = []\n\n // Validate features\n if (!config.features || config.features.length === 0) {\n errors.push('At least one display feature must be selected')\n }\n\n // Validate runtime\n if (!['bash', 'python', 'node'].includes(config.runtime)) {\n errors.push(`Invalid runtime: ${config.runtime}`)\n }\n\n // Validate theme\n if (!['minimal', 'detailed', 'compact'].includes(config.theme)) {\n errors.push(`Invalid theme: ${config.theme}`)\n }\n\n // Check for usage features without ccusage integration\n const usageFeatures = ['usage', 'session', 'tokens', 'burnrate']\n const hasUsageFeatures = config.features.some(f => usageFeatures.includes(f))\n \n if (hasUsageFeatures && !config.ccusageIntegration) {\n warnings.push('Usage features selected but ccusage integration is disabled. Some features may not work properly.')\n }\n\n // Warn about performance with many features\n if (config.features.length > 5) {\n warnings.push('Many features selected. This may impact statusline performance.')\n }\n\n // Validate color/emoji consistency\n if (config.customEmojis && !config.colors) {\n warnings.push('Custom emojis enabled but colors disabled. Visual distinction may be limited.')\n }\n\n return {\n isValid: errors.length === 0,\n errors,\n warnings\n }\n}\n\nexport function validateDependencies(): {\n jq: boolean\n git: boolean\n ccusage: boolean\n python?: boolean\n node?: boolean\n} {\n // This would check system dependencies\n // For now, return placeholder\n return {\n jq: true, // Would check: command -v jq >/dev/null 2>&1\n git: true, // Would check: command -v git >/dev/null 2>&1\n ccusage: false, // Would check: command -v ccusage >/dev/null 2>&1\n python: true, // Would check: command -v python3 >/dev/null 2>&1\n node: true // Would check: command -v node >/dev/null 2>&1\n }\n}","import { StatuslineConfig } from '../cli/prompts.js'\nimport { promises as fs } from 'fs'\nimport path from 'path'\nimport os from 'os'\nimport inquirer from 'inquirer'\n\nexport async function installStatusline(\n script: string,\n outputPath: string,\n config: StatuslineConfig\n): Promise<void> {\n try {\n // Determine the target directory based on install location\n const isGlobal = config.installLocation === 'global'\n const claudeDir = isGlobal ? path.join(os.homedir(), '.claude') : './.claude'\n const scriptPath = path.join(claudeDir, 'statusline.sh')\n \n // Ensure the directory exists\n await fs.mkdir(claudeDir, { recursive: true })\n\n // Check if statusline.sh already exists\n let shouldWrite = true\n try {\n await fs.access(scriptPath)\n // File exists, ask for confirmation\n const { confirmOverwrite } = await inquirer.prompt([{\n type: 'confirm',\n name: 'confirmOverwrite',\n message: `โš ๏ธ ${isGlobal ? 'Global' : 'Project'} statusline.sh already exists. Overwrite?`,\n default: false\n }])\n shouldWrite = confirmOverwrite\n } catch {\n // File doesn't exist, proceed\n }\n\n if (shouldWrite) {\n // Write the script\n await fs.writeFile(scriptPath, script, { mode: 0o755 })\n } else {\n throw new Error('USER_CANCELLED_OVERWRITE')\n }\n\n // Update settings.json safely\n await updateSettingsJson(claudeDir, 'statusline.sh', isGlobal)\n\n // Note: statusline-config.json removed per user feedback - not needed\n // The statusline script contains all necessary configuration info\n\n } catch (error) {\n throw new Error(`Failed to install statusline: ${error instanceof Error ? error.message : String(error)}`)\n }\n}\n\nasync function updateSettingsJson(claudeDir: string, scriptName: string, isGlobal: boolean): Promise<void> {\n const settingsPath = path.join(claudeDir, 'settings.json')\n \n try {\n let settings: any = {}\n let existingStatusLine: any = null\n \n // Try to read existing settings\n try {\n const settingsContent = await fs.readFile(settingsPath, 'utf-8')\n settings = JSON.parse(settingsContent)\n existingStatusLine = settings.statusLine\n } catch {\n // File doesn't exist or invalid JSON, start fresh\n }\n\n // Check if statusLine already exists\n if (existingStatusLine && existingStatusLine.command) {\n // Only update if it's a statusline.sh command or user confirms\n const isOurStatusline = existingStatusLine.command?.includes('statusline.sh')\n \n if (!isOurStatusline) {\n // There's a different statusline configured, ask user\n const { confirmReplace } = await inquirer.prompt([{\n type: 'confirm',\n name: 'confirmReplace',\n message: `โš ๏ธ ${isGlobal ? 'Global' : 'Project'} settings.json already has a statusLine configured (${existingStatusLine.command}). Replace it?`,\n default: false\n }])\n \n if (!confirmReplace) {\n console.warn('\\nโš ๏ธ Statusline script was saved but settings.json was not updated.')\n console.warn(' Your existing statusLine configuration was preserved.')\n return\n }\n }\n }\n\n // Update statusLine configuration\n const commandPath = isGlobal ? `~/.claude/${scriptName}` : `.claude/${scriptName}`\n settings.statusLine = {\n type: 'command',\n command: commandPath,\n padding: 0\n }\n\n // Write updated settings\n await fs.writeFile(settingsPath, JSON.stringify(settings, null, 2))\n \n } catch (error) {\n // Settings update failed, but don't fail the entire installation\n console.warn(`Warning: Could not update settings.json: ${error instanceof Error ? error.message : String(error)}`)\n throw new Error('SETTINGS_UPDATE_FAILED') // Signal that manual config is needed\n }\n}\n\nexport async function checkClaudeCodeSetup(): Promise<{\n hasClaudeDir: boolean\n hasSettings: boolean\n currentStatusline?: string\n}> {\n const claudeDir = './.claude'\n const settingsPath = path.join(claudeDir, 'settings.json')\n \n try {\n const dirExists = await fs.access(claudeDir).then(() => true).catch(() => false)\n const settingsExists = await fs.access(settingsPath).then(() => true).catch(() => false)\n \n let currentStatusline: string | undefined\n \n if (settingsExists) {\n try {\n const settings = JSON.parse(await fs.readFile(settingsPath, 'utf-8'))\n currentStatusline = settings.statusLine?.command\n } catch {\n // Ignore JSON parse errors\n }\n }\n \n return {\n hasClaudeDir: dirExists,\n hasSettings: settingsExists,\n currentStatusline\n }\n } catch {\n return {\n hasClaudeDir: false,\n hasSettings: false\n }\n }\n}"],"mappings":"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","names":["fs","path","process","fs","chalk","ora","path","inquirer","path","os","testStatuslineScript","generateMockClaudeInput","chalk","previewCommand"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chongdashu/cc-statusline",
3
- "version": "1.2.2",
3
+ "version": "1.2.4",
4
4
  "description": "Interactive CLI tool for generating custom Claude Code statuslines",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -0,0 +1,55 @@
1
+ #!/bin/bash
2
+ # Comprehensive test for concurrent ccusage locking mechanism
3
+ #
4
+ # This script tests the locking mechanism under various scenarios:
5
+ # 1. Concurrent execution test
6
+ # 2. Stale lock cleanup test
7
+ # 3. Performance verification
8
+ #
9
+ # Usage: ./test/test-concurrent-locking.sh
10
+
11
+ set -euo pipefail
12
+
13
+ TEST_DIR="$(dirname "$0")"
14
+ STATUSLINE_TEST="$TEST_DIR/test-statusline-with-lock.sh"
15
+
16
+ echo "๐Ÿงช Testing ccusage locking mechanism..."
17
+ echo
18
+
19
+ # Test 1: Concurrent execution
20
+ echo "๐Ÿ“‹ Test 1: Concurrent execution (10 processes)"
21
+ echo "Expected: Only 1 process runs ccusage, others skip gracefully"
22
+ echo
23
+
24
+ start_time=$(date +%s)
25
+ for i in {1..10}; do
26
+ echo '{}' | "$STATUSLINE_TEST" &
27
+ done
28
+ wait
29
+ end_time=$(date +%s)
30
+ duration=$((end_time - start_time))
31
+
32
+ echo
33
+ echo "โœ… Test 1 completed in ${duration}s"
34
+ echo
35
+
36
+ # Test 2: Lock cleanup verification
37
+ echo "๐Ÿ“‹ Test 2: Verifying lock cleanup"
38
+ LOCK_EXISTS=$(ls /tmp/ccusage_statusline.lock 2>/dev/null || echo "")
39
+ PID_EXISTS=$(ls /tmp/ccusage_statusline.pid 2>/dev/null || echo "")
40
+
41
+ if [ -z "$LOCK_EXISTS" ] && [ -z "$PID_EXISTS" ]; then
42
+ echo "โœ… Lock files properly cleaned up"
43
+ else
44
+ echo "โŒ Lock files still exist:"
45
+ [ -n "$LOCK_EXISTS" ] && echo " - Lock directory: $LOCK_EXISTS"
46
+ [ -n "$PID_EXISTS" ] && echo " - PID file: $PID_EXISTS"
47
+ fi
48
+
49
+ echo
50
+ echo "๐ŸŽ‰ All tests completed!"
51
+ echo
52
+ echo "๐Ÿ’ก Manual verification:"
53
+ echo " 1. Check that only 1 'Running ccusage...' message appeared in stderr"
54
+ echo " 2. Verify multiple 'Skipped - lock held by' messages appeared"
55
+ echo " 3. Confirm no hanging processes with: ps aux | grep ccusage"
@@ -0,0 +1,336 @@
1
+ #!/bin/bash
2
+
3
+ # Test script for cc-statusline installation scenarios
4
+ # Tests both global and project-level installations
5
+
6
+ # Colors for output
7
+ GREEN='\033[0;32m'
8
+ RED='\033[0;31m'
9
+ YELLOW='\033[1;33m'
10
+ CYAN='\033[0;36m'
11
+ GRAY='\033[0;90m'
12
+ NC='\033[0m' # No Color
13
+
14
+ # Test configuration
15
+ TEST_DIR="$(dirname "$0")/test-workspace"
16
+ FAKE_HOME="$TEST_DIR/fake-home"
17
+ FAKE_PROJECT="$TEST_DIR/fake-project"
18
+ FAKE_GLOBAL_CLAUDE="$FAKE_HOME/.claude"
19
+ FAKE_PROJECT_CLAUDE="$FAKE_PROJECT/.claude"
20
+
21
+ # Counter for tests
22
+ TESTS_RUN=0
23
+ TESTS_PASSED=0
24
+ TESTS_FAILED=0
25
+
26
+ # Helper functions
27
+ setup_test_env() {
28
+ echo -e "${CYAN}Setting up test environment...${NC}"
29
+ rm -rf "$TEST_DIR" 2>/dev/null
30
+ mkdir -p "$FAKE_HOME"
31
+ mkdir -p "$FAKE_PROJECT"
32
+ }
33
+
34
+ cleanup_test_env() {
35
+ echo -e "${GRAY}Cleaning up test environment...${NC}"
36
+ rm -rf "$TEST_DIR" 2>/dev/null
37
+ }
38
+
39
+ test_scenario() {
40
+ local scenario_name="$1"
41
+ local test_function="$2"
42
+
43
+ echo -e "\n${CYAN}Testing: $scenario_name${NC}"
44
+ TESTS_RUN=$((TESTS_RUN + 1))
45
+
46
+ # Clean environment for each test
47
+ rm -rf "$FAKE_GLOBAL_CLAUDE" 2>/dev/null
48
+ rm -rf "$FAKE_PROJECT_CLAUDE" 2>/dev/null
49
+
50
+ # Run the test
51
+ if $test_function; then
52
+ echo -e "${GREEN}โœ“ PASSED${NC}"
53
+ TESTS_PASSED=$((TESTS_PASSED + 1))
54
+ else
55
+ echo -e "${RED}โœ— FAILED${NC}"
56
+ TESTS_FAILED=$((TESTS_FAILED + 1))
57
+ fi
58
+ }
59
+
60
+ assert_file_exists() {
61
+ local file="$1"
62
+ local description="$2"
63
+
64
+ if [ -f "$file" ]; then
65
+ echo -e " ${GREEN}โœ“${NC} $description exists"
66
+ return 0
67
+ else
68
+ echo -e " ${RED}โœ—${NC} $description does not exist"
69
+ return 1
70
+ fi
71
+ }
72
+
73
+ assert_file_not_exists() {
74
+ local file="$1"
75
+ local description="$2"
76
+
77
+ if [ ! -f "$file" ]; then
78
+ echo -e " ${GREEN}โœ“${NC} $description does not exist"
79
+ return 0
80
+ else
81
+ echo -e " ${RED}โœ—${NC} $description exists (unexpected)"
82
+ return 1
83
+ fi
84
+ }
85
+
86
+ assert_file_contains() {
87
+ local file="$1"
88
+ local content="$2"
89
+ local description="$3"
90
+
91
+ if grep -q "$content" "$file" 2>/dev/null; then
92
+ echo -e " ${GREEN}โœ“${NC} $description"
93
+ return 0
94
+ else
95
+ echo -e " ${RED}โœ—${NC} $description not found"
96
+ return 1
97
+ fi
98
+ }
99
+
100
+ assert_json_field() {
101
+ local file="$1"
102
+ local field="$2"
103
+ local expected="$3"
104
+ local description="$4"
105
+
106
+ if [ -f "$file" ]; then
107
+ local actual=$(jq -r "$field" "$file" 2>/dev/null)
108
+ if [ "$actual" = "$expected" ]; then
109
+ echo -e " ${GREEN}โœ“${NC} $description: $expected"
110
+ return 0
111
+ else
112
+ echo -e " ${RED}โœ—${NC} $description: expected '$expected', got '$actual'"
113
+ return 1
114
+ fi
115
+ else
116
+ echo -e " ${RED}โœ—${NC} $description: file does not exist"
117
+ return 1
118
+ fi
119
+ }
120
+
121
+ # Test scenarios
122
+
123
+ test_no_files_global() {
124
+ echo -e "${GRAY} Scenario: No files exist, installing globally${NC}"
125
+
126
+ # Create the directory
127
+ mkdir -p "$FAKE_GLOBAL_CLAUDE"
128
+
129
+ # Simulate installation
130
+ echo '#!/bin/bash' > "$FAKE_GLOBAL_CLAUDE/statusline.sh"
131
+ echo 'echo "test statusline"' >> "$FAKE_GLOBAL_CLAUDE/statusline.sh"
132
+ chmod +x "$FAKE_GLOBAL_CLAUDE/statusline.sh"
133
+
134
+ # Create settings.json
135
+ cat > "$FAKE_GLOBAL_CLAUDE/settings.json" <<EOF
136
+ {
137
+ "statusLine": {
138
+ "type": "command",
139
+ "command": "~/.claude/statusline.sh",
140
+ "padding": 0
141
+ }
142
+ }
143
+ EOF
144
+
145
+ # Verify
146
+ assert_file_exists "$FAKE_GLOBAL_CLAUDE/statusline.sh" "Global statusline.sh" && \
147
+ assert_file_exists "$FAKE_GLOBAL_CLAUDE/settings.json" "Global settings.json" && \
148
+ assert_json_field "$FAKE_GLOBAL_CLAUDE/settings.json" ".statusLine.command" "~/.claude/statusline.sh" "statusLine command"
149
+ }
150
+
151
+ test_no_files_project() {
152
+ echo -e "${GRAY} Scenario: No files exist, installing in project${NC}"
153
+
154
+ # Create the directory
155
+ mkdir -p "$FAKE_PROJECT_CLAUDE"
156
+
157
+ # Simulate installation
158
+ echo '#!/bin/bash' > "$FAKE_PROJECT_CLAUDE/statusline.sh"
159
+ echo 'echo "test statusline"' >> "$FAKE_PROJECT_CLAUDE/statusline.sh"
160
+ chmod +x "$FAKE_PROJECT_CLAUDE/statusline.sh"
161
+
162
+ # Create settings.json
163
+ cat > "$FAKE_PROJECT_CLAUDE/settings.json" <<EOF
164
+ {
165
+ "statusLine": {
166
+ "type": "command",
167
+ "command": ".claude/statusline.sh",
168
+ "padding": 0
169
+ }
170
+ }
171
+ EOF
172
+
173
+ # Verify
174
+ assert_file_exists "$FAKE_PROJECT_CLAUDE/statusline.sh" "Project statusline.sh" && \
175
+ assert_file_exists "$FAKE_PROJECT_CLAUDE/settings.json" "Project settings.json" && \
176
+ assert_json_field "$FAKE_PROJECT_CLAUDE/settings.json" ".statusLine.command" ".claude/statusline.sh" "statusLine command"
177
+ }
178
+
179
+ test_statusline_exists_global() {
180
+ echo -e "${GRAY} Scenario: statusline.sh exists, no settings.json (global)${NC}"
181
+
182
+ # Create existing statusline
183
+ mkdir -p "$FAKE_GLOBAL_CLAUDE"
184
+ echo '#!/bin/bash' > "$FAKE_GLOBAL_CLAUDE/statusline.sh"
185
+ echo 'echo "old statusline"' >> "$FAKE_GLOBAL_CLAUDE/statusline.sh"
186
+ chmod +x "$FAKE_GLOBAL_CLAUDE/statusline.sh"
187
+
188
+ # Simulate installation (would prompt for overwrite, we assume no)
189
+ # So statusline.sh should remain unchanged
190
+
191
+ # Create settings.json (would be created regardless)
192
+ cat > "$FAKE_GLOBAL_CLAUDE/settings.json" <<EOF
193
+ {
194
+ "statusLine": {
195
+ "type": "command",
196
+ "command": "~/.claude/statusline.sh",
197
+ "padding": 0
198
+ }
199
+ }
200
+ EOF
201
+
202
+ # Verify
203
+ assert_file_exists "$FAKE_GLOBAL_CLAUDE/statusline.sh" "Global statusline.sh" && \
204
+ assert_file_contains "$FAKE_GLOBAL_CLAUDE/statusline.sh" "old statusline" "Original statusline preserved" && \
205
+ assert_file_exists "$FAKE_GLOBAL_CLAUDE/settings.json" "Global settings.json"
206
+ }
207
+
208
+ test_both_exist_global() {
209
+ echo -e "${GRAY} Scenario: Both files exist (global)${NC}"
210
+
211
+ # Create existing files
212
+ mkdir -p "$FAKE_GLOBAL_CLAUDE"
213
+ echo '#!/bin/bash' > "$FAKE_GLOBAL_CLAUDE/statusline.sh"
214
+ echo 'echo "old statusline"' >> "$FAKE_GLOBAL_CLAUDE/statusline.sh"
215
+ chmod +x "$FAKE_GLOBAL_CLAUDE/statusline.sh"
216
+
217
+ cat > "$FAKE_GLOBAL_CLAUDE/settings.json" <<EOF
218
+ {
219
+ "statusLine": {
220
+ "type": "command",
221
+ "command": "~/.claude/statusline.sh",
222
+ "padding": 0
223
+ },
224
+ "otherSetting": "preserved"
225
+ }
226
+ EOF
227
+
228
+ # Simulate installation (would prompt for overwrite, we assume no)
229
+ # Both files should remain unchanged
230
+
231
+ # Verify
232
+ assert_file_exists "$FAKE_GLOBAL_CLAUDE/statusline.sh" "Global statusline.sh" && \
233
+ assert_file_contains "$FAKE_GLOBAL_CLAUDE/statusline.sh" "old statusline" "Original statusline preserved" && \
234
+ assert_file_exists "$FAKE_GLOBAL_CLAUDE/settings.json" "Global settings.json" && \
235
+ assert_json_field "$FAKE_GLOBAL_CLAUDE/settings.json" ".otherSetting" "preserved" "Other settings preserved"
236
+ }
237
+
238
+ test_different_statusline_configured() {
239
+ echo -e "${GRAY} Scenario: Different statusline configured (project)${NC}"
240
+
241
+ # Create settings with different statusline
242
+ mkdir -p "$FAKE_PROJECT_CLAUDE"
243
+ cat > "$FAKE_PROJECT_CLAUDE/settings.json" <<EOF
244
+ {
245
+ "statusLine": {
246
+ "type": "command",
247
+ "command": "custom-statusline.sh",
248
+ "padding": 2
249
+ },
250
+ "otherSetting": "preserved"
251
+ }
252
+ EOF
253
+
254
+ # Simulate installation
255
+ echo '#!/bin/bash' > "$FAKE_PROJECT_CLAUDE/statusline.sh"
256
+ echo 'echo "new statusline"' >> "$FAKE_PROJECT_CLAUDE/statusline.sh"
257
+ chmod +x "$FAKE_PROJECT_CLAUDE/statusline.sh"
258
+
259
+ # Settings should be preserved (not overwritten)
260
+
261
+ # Verify
262
+ assert_file_exists "$FAKE_PROJECT_CLAUDE/statusline.sh" "Project statusline.sh" && \
263
+ assert_json_field "$FAKE_PROJECT_CLAUDE/settings.json" ".statusLine.command" "custom-statusline.sh" "Custom statusLine preserved" && \
264
+ assert_json_field "$FAKE_PROJECT_CLAUDE/settings.json" ".otherSetting" "preserved" "Other settings preserved"
265
+ }
266
+
267
+ test_create_in_empty_directory() {
268
+ echo -e "${GRAY} Scenario: Create .claude directory if it doesn't exist${NC}"
269
+
270
+ # Don't create directory beforehand
271
+ rm -rf "$FAKE_PROJECT_CLAUDE" 2>/dev/null
272
+
273
+ # Simulate installation
274
+ mkdir -p "$FAKE_PROJECT_CLAUDE"
275
+ echo '#!/bin/bash' > "$FAKE_PROJECT_CLAUDE/statusline.sh"
276
+ echo 'echo "test statusline"' >> "$FAKE_PROJECT_CLAUDE/statusline.sh"
277
+ chmod +x "$FAKE_PROJECT_CLAUDE/statusline.sh"
278
+
279
+ cat > "$FAKE_PROJECT_CLAUDE/settings.json" <<EOF
280
+ {
281
+ "statusLine": {
282
+ "type": "command",
283
+ "command": ".claude/statusline.sh",
284
+ "padding": 0
285
+ }
286
+ }
287
+ EOF
288
+
289
+ # Verify
290
+ assert_file_exists "$FAKE_PROJECT_CLAUDE/statusline.sh" "Project statusline.sh" && \
291
+ assert_file_exists "$FAKE_PROJECT_CLAUDE/settings.json" "Project settings.json"
292
+ }
293
+
294
+ # Main test execution
295
+ main() {
296
+ echo -e "${CYAN}================================${NC}"
297
+ echo -e "${CYAN}CC-Statusline Installation Tests${NC}"
298
+ echo -e "${CYAN}================================${NC}"
299
+
300
+ # Check for jq dependency
301
+ if ! command -v jq &> /dev/null; then
302
+ echo -e "${YELLOW}Warning: jq not found. Some tests may fail.${NC}"
303
+ echo -e "${GRAY}Install with: apt-get install jq (Ubuntu) or brew install jq (macOS)${NC}"
304
+ fi
305
+
306
+ setup_test_env
307
+
308
+ # Run all test scenarios
309
+ test_scenario "No files exist (Global)" test_no_files_global
310
+ test_scenario "No files exist (Project)" test_no_files_project
311
+ test_scenario "statusline.sh exists, no settings.json (Global)" test_statusline_exists_global
312
+ test_scenario "Both files exist (Global)" test_both_exist_global
313
+ test_scenario "Different statusline configured (Project)" test_different_statusline_configured
314
+ test_scenario "Create .claude directory if doesn't exist" test_create_in_empty_directory
315
+
316
+ # Summary
317
+ echo -e "\n${CYAN}================================${NC}"
318
+ echo -e "${CYAN}Test Summary${NC}"
319
+ echo -e "${CYAN}================================${NC}"
320
+ echo -e "Total: $TESTS_RUN"
321
+ echo -e "${GREEN}Passed: $TESTS_PASSED${NC}"
322
+ echo -e "${RED}Failed: $TESTS_FAILED${NC}"
323
+
324
+ cleanup_test_env
325
+
326
+ if [ $TESTS_FAILED -gt 0 ]; then
327
+ echo -e "\n${RED}Some tests failed!${NC}"
328
+ exit 1
329
+ else
330
+ echo -e "\n${GREEN}All tests passed!${NC}"
331
+ exit 0
332
+ fi
333
+ }
334
+
335
+ # Run the tests
336
+ main
@@ -0,0 +1,67 @@
1
+ #!/bin/bash
2
+ # Test script for ccusage process locking mechanism
3
+ #
4
+ # This script simulates the file-based locking behavior implemented in the
5
+ # ccusage integration to prevent concurrent process spawning.
6
+ #
7
+ # Usage: echo '{}' | ./test/test-statusline-with-lock.sh
8
+ #
9
+ # To test concurrent execution:
10
+ # for i in {1..10}; do echo '{}' | ./test/test-statusline-with-lock.sh & done
11
+ #
12
+ # Expected behavior:
13
+ # - Only one process should run ccusage at a time
14
+ # - Other processes should skip execution gracefully
15
+ # - No process pile-up or resource leaks should occur
16
+
17
+ set -euo pipefail
18
+
19
+ # Mock JSON input handling
20
+ json_input=$(cat)
21
+
22
+ # Implement file-based locking to prevent concurrent executions
23
+ LOCK_FILE="/tmp/ccusage_statusline.lock"
24
+ LOCK_PID_FILE="/tmp/ccusage_statusline.pid"
25
+
26
+ # Function to check if process is still running
27
+ is_process_running() {
28
+ local pid=$1
29
+ if [ -z "$pid" ]; then return 1; fi
30
+ kill -0 "$pid" 2>/dev/null
31
+ }
32
+
33
+ # Try to acquire lock
34
+ if mkdir "$LOCK_FILE" 2>/dev/null; then
35
+ # Lock acquired, save our PID
36
+ echo $$ > "$LOCK_PID_FILE"
37
+
38
+ # Run ccusage with timeout (simulated with echo for testing)
39
+ echo "[$$] Running ccusage..." >&2
40
+
41
+ # Simulate ccusage work
42
+ sleep 0.5
43
+
44
+ # Generate mock output
45
+ echo "๐Ÿ“ ~/test ๐ŸŒฟ main ๐Ÿค– Claude [simulated from $$]"
46
+
47
+ # Clean up lock
48
+ rm -f "$LOCK_PID_FILE" 2>/dev/null
49
+ rmdir "$LOCK_FILE" 2>/dev/null
50
+ else
51
+ # Lock exists, check if it's stale
52
+ if [ -f "$LOCK_PID_FILE" ]; then
53
+ old_pid=$(cat "$LOCK_PID_FILE" 2>/dev/null)
54
+ if ! is_process_running "$old_pid"; then
55
+ # Stale lock, clean it up
56
+ echo "[$$] Cleaning stale lock from $old_pid" >&2
57
+ rm -f "$LOCK_PID_FILE" 2>/dev/null
58
+ rmdir "$LOCK_FILE" 2>/dev/null
59
+ else
60
+ echo "[$$] Skipped - lock held by $old_pid" >&2
61
+ fi
62
+ else
63
+ echo "[$$] Skipped - lock exists" >&2
64
+ fi
65
+ # Return cached/default output when skipped
66
+ echo "๐Ÿ“ ~/test ๐ŸŒฟ main ๐Ÿค– Claude [cached]"
67
+ fi