@chongdashu/cc-statusline 1.3.2 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -61,12 +61,36 @@ function generateMockClaudeInput(config) {
61
61
  transcript_path: "/home/user/.claude/conversations/test.jsonl",
62
62
  cwd: "/home/user/projects/my-project",
63
63
  workspace: {
64
- current_dir: "/home/user/projects/my-project"
64
+ current_dir: "/home/user/projects/my-project",
65
+ project_dir: "/home/user/projects/my-project"
65
66
  },
66
67
  model: {
67
68
  id: "claude-opus-4-1-20250805",
68
69
  display_name: "Opus 4.1",
69
70
  version: "20250805"
71
+ },
72
+ version: "1.0.80",
73
+ output_style: {
74
+ name: "default"
75
+ },
76
+ cost: {
77
+ total_cost_usd: 0.42,
78
+ total_duration_ms: 18e4,
79
+ // 3 minutes
80
+ total_api_duration_ms: 2300,
81
+ total_lines_added: 156,
82
+ total_lines_removed: 23
83
+ },
84
+ context_window: {
85
+ total_input_tokens: 15234,
86
+ total_output_tokens: 4521,
87
+ context_window_size: 2e5,
88
+ current_usage: {
89
+ input_tokens: 8500,
90
+ output_tokens: 1200,
91
+ cache_creation_input_tokens: 5e3,
92
+ cache_read_input_tokens: 2e3
93
+ }
70
94
  }
71
95
  };
72
96
  }
@@ -310,9 +334,9 @@ async function collectConfiguration() {
310
334
  { name: "\u{1F916} Model Name & Version", value: "model", checked: true },
311
335
  { name: "\u{1F9E0} Context Remaining", value: "context", checked: true },
312
336
  { name: "\u{1F4B5} Usage & Cost", value: "usage", checked: true },
313
- { name: "\u231B Session Time Remaining", value: "session", checked: true },
314
337
  { name: "\u{1F4CA} Token Statistics", value: "tokens", checked: true },
315
- { name: "\u26A1 Burn Rate (tokens/min)", value: "burnrate", checked: true }
338
+ { name: "\u26A1 Burn Rate ($/hr & tokens/min)", value: "burnrate", checked: true },
339
+ { name: "\u231B Session Reset Time (requires ccusage)", value: "session", checked: false }
316
340
  ],
317
341
  validate: (answer) => {
318
342
  if (answer.length < 1) {
@@ -345,13 +369,13 @@ async function collectConfiguration() {
345
369
  default: "project"
346
370
  }
347
371
  ]);
372
+ const needsCcusage = config.features.includes("session");
348
373
  return {
349
374
  features: config.features,
350
375
  runtime: "bash",
351
376
  colors: config.colors,
352
377
  theme: "detailed",
353
- ccusageIntegration: true,
354
- // Always enabled since npx works
378
+ ccusageIntegration: needsCcusage,
355
379
  logging: config.logging,
356
380
  customEmojis: false,
357
381
  installLocation: config.installLocation
@@ -440,60 +464,82 @@ cost_color() { :; }
440
464
  burn_color() { :; }
441
465
  session_color() { :; }
442
466
  `;
467
+ const needsCcusage = config.showSession || config.showProgressBar;
443
468
  return `${colorCode}
444
469
  # ---- cost and usage extraction ----
445
470
  session_txt=""; session_pct=0; session_bar=""
446
471
  cost_usd=""; cost_per_hour=""; tpm=""; tot_tokens=""
447
472
 
448
- # Extract cost data from Claude Code input
473
+ # Extract cost and token data from Claude Code's native input
449
474
  if [ "$HAS_JQ" -eq 1 ]; then
450
- # Get cost data from Claude Code's input
475
+ # Cost data
451
476
  cost_usd=$(echo "$input" | jq -r '.cost.total_cost_usd // empty' 2>/dev/null)
452
477
  total_duration_ms=$(echo "$input" | jq -r '.cost.total_duration_ms // empty' 2>/dev/null)
453
-
478
+
454
479
  # Calculate burn rate ($/hour) from cost and duration
455
480
  if [ -n "$cost_usd" ] && [ -n "$total_duration_ms" ] && [ "$total_duration_ms" -gt 0 ]; then
456
- # Convert ms to hours and calculate rate
457
481
  cost_per_hour=$(echo "$cost_usd $total_duration_ms" | awk '{printf "%.2f", $1 * 3600000 / $2}')
458
482
  fi
483
+ ${config.showTokens ? `
484
+ # Token data from native context_window (no ccusage needed)
485
+ input_tokens=$(echo "$input" | jq -r '.context_window.total_input_tokens // 0' 2>/dev/null)
486
+ output_tokens=$(echo "$input" | jq -r '.context_window.total_output_tokens // 0' 2>/dev/null)
487
+
488
+ if [ "$input_tokens" != "null" ] && [ "$output_tokens" != "null" ]; then
489
+ tot_tokens=$(( input_tokens + output_tokens ))
490
+ [ "$tot_tokens" -eq 0 ] && tot_tokens=""
491
+ fi` : ""}
492
+ ${config.showBurnRate && config.showTokens ? `
493
+ # Calculate tokens per minute from native data
494
+ if [ -n "$tot_tokens" ] && [ -n "$total_duration_ms" ] && [ "$total_duration_ms" -gt 0 ]; then
495
+ # Convert ms to minutes and calculate rate
496
+ tpm=$(echo "$tot_tokens $total_duration_ms" | awk '{if ($2 > 0) printf "%.0f", $1 * 60000 / $2; else print ""}')
497
+ fi` : ""}
459
498
  else
460
499
  # Bash fallback for cost extraction
461
500
  cost_usd=$(echo "$input" | grep -o '"total_cost_usd"[[:space:]]*:[[:space:]]*[0-9.]*' | sed 's/.*:[[:space:]]*\\([0-9.]*\\).*/\\1/')
462
- total_duration_ms=$(echo "$input" | grep -o '"total_duration_ms"[[:space:]]*:[[:space:]]*[0-9]*' | sed 's/.*:[[:space:]]*\\([0-9]*\\).*/\\1/')
463
-
501
+ total_duration_ms=$(echo "$input" | grep -o '"total_duration_ms"[[:space:]]*:[[:space:]]*[0-9]*' | sed 's/.*:[[:space:]]*\\([0-9]*\\).*/\\1/')
502
+
464
503
  # Calculate burn rate ($/hour) from cost and duration
465
504
  if [ -n "$cost_usd" ] && [ -n "$total_duration_ms" ] && [ "$total_duration_ms" -gt 0 ]; then
466
- # Convert ms to hours and calculate rate
467
505
  cost_per_hour=$(echo "$cost_usd $total_duration_ms" | awk '{printf "%.2f", $1 * 3600000 / $2}')
468
506
  fi
469
- fi
507
+ ${config.showTokens ? `
508
+ # Token data from native context_window (bash fallback)
509
+ input_tokens=$(echo "$input" | grep -o '"total_input_tokens"[[:space:]]*:[[:space:]]*[0-9]*' | sed 's/.*:[[:space:]]*\\([0-9]*\\).*/\\1/')
510
+ output_tokens=$(echo "$input" | grep -o '"total_output_tokens"[[:space:]]*:[[:space:]]*[0-9]*' | sed 's/.*:[[:space:]]*\\([0-9]*\\).*/\\1/')
470
511
 
471
- # Get token data and session info from ccusage if available
512
+ if [ -n "$input_tokens" ] && [ -n "$output_tokens" ]; then
513
+ tot_tokens=$(( input_tokens + output_tokens ))
514
+ [ "$tot_tokens" -eq 0 ] && tot_tokens=""
515
+ fi` : ""}
516
+ ${config.showBurnRate && config.showTokens ? `
517
+ # Calculate tokens per minute from native data
518
+ if [ -n "$tot_tokens" ] && [ -n "$total_duration_ms" ] && [ "$total_duration_ms" -gt 0 ]; then
519
+ tpm=$(echo "$tot_tokens $total_duration_ms" | awk '{if ($2 > 0) printf "%.0f", $1 * 60000 / $2; else print ""}')
520
+ fi` : ""}
521
+ fi
522
+ ${needsCcusage ? `
523
+ # Session reset time requires ccusage (only feature that needs external tool)
472
524
  if command -v ccusage >/dev/null 2>&1 && [ "$HAS_JQ" -eq 1 ]; then
473
525
  blocks_output=""
474
-
475
- # Try ccusage with timeout for token data and session info
526
+
527
+ # Try ccusage with timeout
476
528
  if command -v timeout >/dev/null 2>&1; then
477
529
  blocks_output=$(timeout 5s ccusage blocks --json 2>/dev/null)
478
530
  elif command -v gtimeout >/dev/null 2>&1; then
479
- # macOS with coreutils installed
480
531
  blocks_output=$(gtimeout 5s ccusage blocks --json 2>/dev/null)
481
532
  else
482
- # No timeout available, run directly (ccusage should be fast)
483
533
  blocks_output=$(ccusage blocks --json 2>/dev/null)
484
534
  fi
535
+
485
536
  if [ -n "$blocks_output" ]; then
486
537
  active_block=$(echo "$blocks_output" | jq -c '.blocks[] | select(.isActive == true)' 2>/dev/null | head -n1)
487
- if [ -n "$active_block" ]; then${config.showTokens ? `
488
- # Get token count from ccusage
489
- tot_tokens=$(echo "$active_block" | jq -r '.totalTokens // empty')` : ""}${config.showBurnRate && config.showTokens ? `
490
- # Get tokens per minute from ccusage
491
- tpm=$(echo "$active_block" | jq -r '.burnRate.tokensPerMinute // empty')` : ""}${config.showSession || config.showProgressBar ? `
492
-
538
+ if [ -n "$active_block" ]; then
493
539
  # Session time calculation from ccusage
494
540
  reset_time_str=$(echo "$active_block" | jq -r '.usageLimitResetTime // .endTime // empty')
495
541
  start_time_str=$(echo "$active_block" | jq -r '.startTime // empty')
496
-
542
+
497
543
  if [ -n "$reset_time_str" ] && [ -n "$start_time_str" ]; then
498
544
  start_sec=$(to_epoch "$start_time_str"); end_sec=$(to_epoch "$reset_time_str"); now_sec=$(date +%s)
499
545
  total=$(( end_sec - start_sec )); (( total<1 )) && total=1
@@ -504,10 +550,10 @@ if command -v ccusage >/dev/null 2>&1 && [ "$HAS_JQ" -eq 1 ]; then
504
550
  end_hm=$(fmt_time_hm "$end_sec")${config.showSession ? `
505
551
  session_txt="$(printf '%dh %dm until reset at %s (%d%%)' "$rh" "$rm" "$end_hm" "$session_pct")"` : ""}${config.showProgressBar ? `
506
552
  session_bar=$(progress_bar "$session_pct" 10)` : ""}
507
- fi` : ""}
553
+ fi
508
554
  fi
509
555
  fi
510
- fi`;
556
+ fi` : ""}`;
511
557
  }
512
558
  function generateUsageUtilities() {
513
559
  return `
@@ -538,7 +584,7 @@ progress_bar() {
538
584
  }
539
585
 
540
586
  // src/generators/bash-generator.ts
541
- var VERSION = "1.3.2";
587
+ var VERSION = "1.4.0";
542
588
  function generateBashStatusline(config) {
543
589
  const hasGit = config.features.includes("git");
544
590
  const hasUsage = config.features.some((f) => ["usage", "session", "tokens", "burnrate"].includes(f));
@@ -568,6 +614,12 @@ function generateBashStatusline(config) {
568
614
  STATUSLINE_VERSION="${VERSION}"
569
615
 
570
616
  input=$(cat)
617
+
618
+ # ---- check jq availability ----
619
+ HAS_JQ=0
620
+ if command -v jq >/dev/null 2>&1; then
621
+ HAS_JQ=1
622
+ fi
571
623
  ${config.logging ? generateLoggingCode() : ""}
572
624
  ${generateColorBashCode({ enabled: config.colors, theme: config.theme })}
573
625
  ${config.colors ? generateBasicColors() : ""}
@@ -589,12 +641,6 @@ SCRIPT_DIR="$(cd "$(dirname "\${BASH_SOURCE[0]}")" && pwd)"
589
641
  LOG_FILE="\${SCRIPT_DIR}/statusline.log"
590
642
  TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')
591
643
 
592
- # ---- check jq availability ----
593
- HAS_JQ=0
594
- if command -v jq >/dev/null 2>&1; then
595
- HAS_JQ=1
596
- fi
597
-
598
644
  # ---- logging ----
599
645
  {
600
646
  echo "[$TIMESTAMP] Status line triggered (cc-statusline v\${STATUSLINE_VERSION})"
@@ -685,47 +731,28 @@ fi
685
731
  }
686
732
  function generateContextBashCode(colors) {
687
733
  return `
688
- # ---- context window calculation ----
734
+ # ---- context window calculation (native) ----
689
735
  context_pct=""
736
+ context_remaining_pct=""
690
737
  context_color() { if [ "$use_color" -eq 1 ]; then printf '\\033[1;37m'; fi; } # default white
691
738
 
692
- # Determine max context based on model
693
- get_max_context() {
694
- local model_name="$1"
695
- case "$model_name" in
696
- *"Opus 4"*|*"opus 4"*|*"Opus"*|*"opus"*)
697
- echo "200000" # 200K for all Opus versions
698
- ;;
699
- *"Sonnet 4"*|*"sonnet 4"*|*"Sonnet 3.5"*|*"sonnet 3.5"*|*"Sonnet"*|*"sonnet"*)
700
- echo "200000" # 200K for Sonnet 3.5+ and 4.x
701
- ;;
702
- *"Haiku 3.5"*|*"haiku 3.5"*|*"Haiku 4"*|*"haiku 4"*|*"Haiku"*|*"haiku"*)
703
- echo "200000" # 200K for modern Haiku
704
- ;;
705
- *"Claude 3 Haiku"*|*"claude 3 haiku"*)
706
- echo "100000" # 100K for original Claude 3 Haiku
707
- ;;
708
- *)
709
- echo "200000" # Default to 200K
710
- ;;
711
- esac
712
- }
739
+ if [ "$HAS_JQ" -eq 1 ]; then
740
+ # Get context window size and current usage from native Claude Code input
741
+ CONTEXT_SIZE=$(echo "$input" | jq -r '.context_window.context_window_size // 200000' 2>/dev/null)
742
+ USAGE=$(echo "$input" | jq '.context_window.current_usage' 2>/dev/null)
713
743
 
714
- if [ -n "$session_id" ] && [ "$HAS_JQ" -eq 1 ]; then
715
- MAX_CONTEXT=$(get_max_context "$model_name")
716
-
717
- # Convert current dir to session file path
718
- project_dir=$(echo "$current_dir" | sed "s|~|$HOME|g" | sed 's|/|-|g' | sed 's|^-||')
719
- session_file="$HOME/.claude/projects/-\${project_dir}/\${session_id}.jsonl"
720
-
721
- if [ -f "$session_file" ]; then
722
- # Get the latest input token count from the session file
723
- 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)
724
-
725
- if [ -n "$latest_tokens" ] && [ "$latest_tokens" -gt 0 ]; then
726
- context_used_pct=$(( latest_tokens * 100 / MAX_CONTEXT ))
744
+ if [ "$USAGE" != "null" ] && [ -n "$USAGE" ]; then
745
+ # Calculate current context from current_usage fields
746
+ # Formula: input_tokens + cache_creation_input_tokens + cache_read_input_tokens
747
+ CURRENT_TOKENS=$(echo "$USAGE" | jq '(.input_tokens // 0) + (.cache_creation_input_tokens // 0) + (.cache_read_input_tokens // 0)' 2>/dev/null)
748
+
749
+ if [ -n "$CURRENT_TOKENS" ] && [ "$CURRENT_TOKENS" -gt 0 ] 2>/dev/null; then
750
+ context_used_pct=$(( CURRENT_TOKENS * 100 / CONTEXT_SIZE ))
727
751
  context_remaining_pct=$(( 100 - context_used_pct ))
728
-
752
+ # Clamp to valid range
753
+ (( context_remaining_pct < 0 )) && context_remaining_pct=0
754
+ (( context_remaining_pct > 100 )) && context_remaining_pct=100
755
+
729
756
  # Set color based on remaining percentage
730
757
  if [ "$context_remaining_pct" -le 20 ]; then
731
758
  context_color() { if [ "$use_color" -eq 1 ]; then printf '\\033[38;5;203m'; fi; } # coral red
@@ -734,7 +761,7 @@ if [ -n "$session_id" ] && [ "$HAS_JQ" -eq 1 ]; then
734
761
  else
735
762
  context_color() { if [ "$use_color" -eq 1 ]; then printf '\\033[38;5;158m'; fi; } # mint green
736
763
  fi
737
-
764
+
738
765
  context_pct="\${context_remaining_pct}%"
739
766
  fi
740
767
  fi
@@ -1072,7 +1099,9 @@ async function initCommand(options) {
1072
1099
  \u{1F4C1} ${isGlobal ? "Global" : "Project"} installation complete: ${chalk.white(resolvedPath)}`));
1073
1100
  console.log(chalk.cyan("\nNext steps:"));
1074
1101
  console.log(chalk.white(" 1. Restart Claude Code to see your new statusline"));
1075
- console.log(chalk.white(" 2. Usage statistics work via: npx ccusage@latest"));
1102
+ if (config.features.includes("session")) {
1103
+ console.log(chalk.white(" 2. Session reset time requires ccusage: npx ccusage@latest"));
1104
+ }
1076
1105
  } catch (error) {
1077
1106
  console.log(chalk.red("\n\u274C Failed to install statusline"));
1078
1107
  if (error instanceof Error && error.message === "USER_CANCELLED_OVERWRITE") {
@@ -1112,7 +1141,7 @@ async function initCommand(options) {
1112
1141
 
1113
1142
  // src/index.ts
1114
1143
  import chalk3 from "chalk";
1115
- var VERSION2 = "1.3.2";
1144
+ var VERSION2 = "1.4.0";
1116
1145
  var program = new Command();
1117
1146
  program.name("cc-statusline").description("Interactive CLI tool for generating custom Claude Code statuslines").version(VERSION2);
1118
1147
  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);