@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/CHANGELOG.md +184 -157
- package/CLAUDE.md +76 -76
- package/CONTRIBUTING.md +207 -207
- package/LICENSE +20 -20
- package/README.md +361 -373
- package/dist/index.js +102 -73
- package/dist/index.js.map +1 -1
- package/package.json +57 -57
- package/test/test-concurrent-locking.sh +54 -54
- package/test/test-installation.sh +335 -335
- package/test/test-statusline-with-lock.sh +67 -67
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:
|
|
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
|
-
#
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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.
|
|
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
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
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 [
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
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
|
-
|
|
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.
|
|
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);
|