claude-limitline 1.1.0 → 1.2.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/README.md +11 -3
- package/dist/index.js +135 -52
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -111,13 +111,15 @@ echo '{"model":{"id":"claude-opus-4-5-20251101"}}' | npx claude-limitline
|
|
|
111
111
|
|
|
112
112
|
## Configuration
|
|
113
113
|
|
|
114
|
-
Create a
|
|
114
|
+
Create a `claude-limitline.json` file in your Claude config directory (`~/.claude/claude-limitline.json`) or `.claude-limitline.json` in your current working directory:
|
|
115
115
|
|
|
116
116
|
```json
|
|
117
117
|
{
|
|
118
118
|
"display": {
|
|
119
119
|
"style": "powerline",
|
|
120
|
-
"useNerdFonts": true
|
|
120
|
+
"useNerdFonts": true,
|
|
121
|
+
"compactMode": "auto",
|
|
122
|
+
"compactWidth": 80
|
|
121
123
|
},
|
|
122
124
|
"directory": {
|
|
123
125
|
"enabled": true
|
|
@@ -144,7 +146,9 @@ Create a `.claude-limitline.json` file in your home directory (`~/.claude-limitl
|
|
|
144
146
|
"pollInterval": 15,
|
|
145
147
|
"warningThreshold": 80
|
|
146
148
|
},
|
|
147
|
-
"theme": "dark"
|
|
149
|
+
"theme": "dark",
|
|
150
|
+
"segmentOrder": ["directory", "git", "model", "block", "weekly"],
|
|
151
|
+
"showTrend": true
|
|
148
152
|
}
|
|
149
153
|
```
|
|
150
154
|
|
|
@@ -153,6 +157,8 @@ Create a `.claude-limitline.json` file in your home directory (`~/.claude-limitl
|
|
|
153
157
|
| Option | Description | Default |
|
|
154
158
|
|--------|-------------|---------|
|
|
155
159
|
| `display.useNerdFonts` | Use Nerd Font symbols for powerline | `true` |
|
|
160
|
+
| `display.compactMode` | `"auto"`, `"always"`, or `"never"` | `"auto"` |
|
|
161
|
+
| `display.compactWidth` | Terminal width threshold for compact mode | `80` |
|
|
156
162
|
| `directory.enabled` | Show repository/directory name | `true` |
|
|
157
163
|
| `git.enabled` | Show git branch with dirty indicator | `true` |
|
|
158
164
|
| `model.enabled` | Show Claude model name | `true` |
|
|
@@ -167,6 +173,8 @@ Create a `.claude-limitline.json` file in your home directory (`~/.claude-limitl
|
|
|
167
173
|
| `budget.pollInterval` | Minutes between API calls | `15` |
|
|
168
174
|
| `budget.warningThreshold` | Percentage to trigger warning color | `80` |
|
|
169
175
|
| `theme` | Color theme name | `"dark"` |
|
|
176
|
+
| `segmentOrder` | Array to customize segment order | `["directory", "git", "model", "block", "weekly"]` |
|
|
177
|
+
| `showTrend` | Show ↑↓ arrows for usage changes | `true` |
|
|
170
178
|
|
|
171
179
|
### Available Themes
|
|
172
180
|
|
package/dist/index.js
CHANGED
|
@@ -18,7 +18,9 @@ function debug(...args) {
|
|
|
18
18
|
var DEFAULT_CONFIG = {
|
|
19
19
|
display: {
|
|
20
20
|
style: "powerline",
|
|
21
|
-
useNerdFonts: true
|
|
21
|
+
useNerdFonts: true,
|
|
22
|
+
compactMode: "auto",
|
|
23
|
+
compactWidth: 80
|
|
22
24
|
},
|
|
23
25
|
directory: {
|
|
24
26
|
enabled: true
|
|
@@ -45,7 +47,9 @@ var DEFAULT_CONFIG = {
|
|
|
45
47
|
pollInterval: 15,
|
|
46
48
|
warningThreshold: 80
|
|
47
49
|
},
|
|
48
|
-
theme: "dark"
|
|
50
|
+
theme: "dark",
|
|
51
|
+
segmentOrder: ["directory", "git", "model", "block", "weekly"],
|
|
52
|
+
showTrend: true
|
|
49
53
|
};
|
|
50
54
|
|
|
51
55
|
// src/config/loader.ts
|
|
@@ -65,8 +69,7 @@ function deepMerge(target, source) {
|
|
|
65
69
|
function loadConfig() {
|
|
66
70
|
const configPaths = [
|
|
67
71
|
path.join(process.cwd(), ".claude-limitline.json"),
|
|
68
|
-
path.join(os.homedir(), ".claude-limitline.json")
|
|
69
|
-
path.join(os.homedir(), ".config", "claude-limitline", "config.json")
|
|
72
|
+
path.join(os.homedir(), ".claude", "claude-limitline.json")
|
|
70
73
|
];
|
|
71
74
|
for (const configPath of configPaths) {
|
|
72
75
|
try {
|
|
@@ -264,8 +267,31 @@ async function fetchUsageFromAPI(token) {
|
|
|
264
267
|
}
|
|
265
268
|
}
|
|
266
269
|
var cachedUsage = null;
|
|
270
|
+
var previousUsage = null;
|
|
267
271
|
var cacheTimestamp = 0;
|
|
268
272
|
var cachedToken = null;
|
|
273
|
+
function getUsageTrend() {
|
|
274
|
+
const result = {
|
|
275
|
+
fiveHourTrend: null,
|
|
276
|
+
sevenDayTrend: null
|
|
277
|
+
};
|
|
278
|
+
if (!cachedUsage || !previousUsage) {
|
|
279
|
+
return result;
|
|
280
|
+
}
|
|
281
|
+
if (cachedUsage.fiveHour && previousUsage.fiveHour) {
|
|
282
|
+
const diff = cachedUsage.fiveHour.percentUsed - previousUsage.fiveHour.percentUsed;
|
|
283
|
+
if (diff > 0.5) result.fiveHourTrend = "up";
|
|
284
|
+
else if (diff < -0.5) result.fiveHourTrend = "down";
|
|
285
|
+
else result.fiveHourTrend = "same";
|
|
286
|
+
}
|
|
287
|
+
if (cachedUsage.sevenDay && previousUsage.sevenDay) {
|
|
288
|
+
const diff = cachedUsage.sevenDay.percentUsed - previousUsage.sevenDay.percentUsed;
|
|
289
|
+
if (diff > 0.5) result.sevenDayTrend = "up";
|
|
290
|
+
else if (diff < -0.5) result.sevenDayTrend = "down";
|
|
291
|
+
else result.sevenDayTrend = "same";
|
|
292
|
+
}
|
|
293
|
+
return result;
|
|
294
|
+
}
|
|
269
295
|
async function getRealtimeUsage(pollIntervalMinutes = 15) {
|
|
270
296
|
const now = Date.now();
|
|
271
297
|
const cacheAgeMs = now - cacheTimestamp;
|
|
@@ -283,6 +309,7 @@ async function getRealtimeUsage(pollIntervalMinutes = 15) {
|
|
|
283
309
|
}
|
|
284
310
|
const usage = await fetchUsageFromAPI(cachedToken);
|
|
285
311
|
if (usage) {
|
|
312
|
+
previousUsage = cachedUsage;
|
|
286
313
|
cachedUsage = usage;
|
|
287
314
|
cacheTimestamp = now;
|
|
288
315
|
debug("Refreshed realtime usage cache");
|
|
@@ -423,10 +450,12 @@ var SYMBOLS = {
|
|
|
423
450
|
left: "\uE0B2",
|
|
424
451
|
branch: "\uE0A0",
|
|
425
452
|
separator: "\uE0B1",
|
|
426
|
-
|
|
427
|
-
//
|
|
428
|
-
|
|
429
|
-
//
|
|
453
|
+
model: "\u2731",
|
|
454
|
+
// Heavy asterisk ✱
|
|
455
|
+
block_cost: "\u25EB",
|
|
456
|
+
// White square with vertical bisecting line ◫
|
|
457
|
+
weekly_cost: "\u25CB",
|
|
458
|
+
// Circle ○
|
|
430
459
|
progress_full: "\u2588",
|
|
431
460
|
// Full block
|
|
432
461
|
progress_empty: "\u2591"
|
|
@@ -437,6 +466,7 @@ var TEXT_SYMBOLS = {
|
|
|
437
466
|
left: "<",
|
|
438
467
|
branch: "",
|
|
439
468
|
separator: "|",
|
|
469
|
+
model: "*",
|
|
440
470
|
block_cost: "BLK",
|
|
441
471
|
weekly_cost: "WK",
|
|
442
472
|
progress_full: "#",
|
|
@@ -532,6 +562,11 @@ function getTheme(name) {
|
|
|
532
562
|
return themes[name] || themes.dark;
|
|
533
563
|
}
|
|
534
564
|
|
|
565
|
+
// src/utils/terminal.ts
|
|
566
|
+
function getTerminalWidth() {
|
|
567
|
+
return process.stdout.columns || 80;
|
|
568
|
+
}
|
|
569
|
+
|
|
535
570
|
// src/renderer.ts
|
|
536
571
|
var Renderer = class {
|
|
537
572
|
config;
|
|
@@ -550,25 +585,43 @@ var Renderer = class {
|
|
|
550
585
|
rightArrow: symbolSet.right,
|
|
551
586
|
separator: symbolSet.separator,
|
|
552
587
|
branch: symbolSet.branch,
|
|
553
|
-
model:
|
|
554
|
-
// Lightning bolt for model
|
|
588
|
+
model: symbolSet.model,
|
|
555
589
|
progressFull: symbolSet.progress_full,
|
|
556
|
-
progressEmpty: symbolSet.progress_empty
|
|
590
|
+
progressEmpty: symbolSet.progress_empty,
|
|
591
|
+
trendUp: "\u2191",
|
|
592
|
+
trendDown: "\u2193"
|
|
557
593
|
};
|
|
558
594
|
}
|
|
595
|
+
isCompactMode() {
|
|
596
|
+
const mode = this.config.display?.compactMode ?? "auto";
|
|
597
|
+
if (mode === "always") return true;
|
|
598
|
+
if (mode === "never") return false;
|
|
599
|
+
const threshold = this.config.display?.compactWidth ?? 80;
|
|
600
|
+
const termWidth = getTerminalWidth();
|
|
601
|
+
return termWidth < threshold;
|
|
602
|
+
}
|
|
559
603
|
formatProgressBar(percent, width) {
|
|
560
604
|
const filled = Math.round(percent / 100 * width);
|
|
561
605
|
const empty = width - filled;
|
|
562
606
|
return this.symbols.progressFull.repeat(filled) + this.symbols.progressEmpty.repeat(empty);
|
|
563
607
|
}
|
|
564
|
-
formatTimeRemaining(minutes) {
|
|
608
|
+
formatTimeRemaining(minutes, compact) {
|
|
565
609
|
if (minutes >= 60) {
|
|
566
610
|
const hours = Math.floor(minutes / 60);
|
|
567
611
|
const mins = minutes % 60;
|
|
612
|
+
if (compact) {
|
|
613
|
+
return mins > 0 ? `${hours}h${mins}m` : `${hours}h`;
|
|
614
|
+
}
|
|
568
615
|
return mins > 0 ? `${hours}h${mins}m` : `${hours}h`;
|
|
569
616
|
}
|
|
570
617
|
return `${minutes}m`;
|
|
571
618
|
}
|
|
619
|
+
getTrendSymbol(trend) {
|
|
620
|
+
if (!this.config.showTrend) return "";
|
|
621
|
+
if (trend === "up") return this.symbols.trendUp;
|
|
622
|
+
if (trend === "down") return this.symbols.trendDown;
|
|
623
|
+
return "";
|
|
624
|
+
}
|
|
572
625
|
getColorsForPercent(percent, baseColors) {
|
|
573
626
|
const threshold = this.config.budget?.warningThreshold ?? 80;
|
|
574
627
|
if (percent >= 100) {
|
|
@@ -598,63 +651,69 @@ var Renderer = class {
|
|
|
598
651
|
renderFallback(segments) {
|
|
599
652
|
return segments.map((seg) => ansi.bg(seg.colors.bg) + ansi.fg(seg.colors.fg) + seg.text + RESET_CODE).join(` ${this.symbols.separator} `);
|
|
600
653
|
}
|
|
601
|
-
renderDirectory(
|
|
602
|
-
if (!this.config.directory?.enabled || !envInfo.directory) {
|
|
654
|
+
renderDirectory(ctx) {
|
|
655
|
+
if (!this.config.directory?.enabled || !ctx.envInfo.directory) {
|
|
603
656
|
return null;
|
|
604
657
|
}
|
|
658
|
+
const name = ctx.compact && ctx.envInfo.directory.length > 12 ? ctx.envInfo.directory.slice(0, 10) + "\u2026" : ctx.envInfo.directory;
|
|
605
659
|
return {
|
|
606
|
-
text: ` ${
|
|
660
|
+
text: ` ${name} `,
|
|
607
661
|
colors: this.theme.directory
|
|
608
662
|
};
|
|
609
663
|
}
|
|
610
|
-
renderGit(
|
|
611
|
-
if (!this.config.git?.enabled || !envInfo.gitBranch) {
|
|
664
|
+
renderGit(ctx) {
|
|
665
|
+
if (!this.config.git?.enabled || !ctx.envInfo.gitBranch) {
|
|
612
666
|
return null;
|
|
613
667
|
}
|
|
614
|
-
const dirtyIndicator = envInfo.gitDirty ? " \u25CF" : "";
|
|
668
|
+
const dirtyIndicator = ctx.envInfo.gitDirty ? " \u25CF" : "";
|
|
615
669
|
const icon = this.usePowerline ? this.symbols.branch : "";
|
|
616
670
|
const prefix = icon ? `${icon} ` : "";
|
|
671
|
+
let branch = ctx.envInfo.gitBranch;
|
|
672
|
+
if (ctx.compact && branch.length > 10) {
|
|
673
|
+
branch = branch.slice(0, 8) + "\u2026";
|
|
674
|
+
}
|
|
617
675
|
return {
|
|
618
|
-
text: ` ${prefix}${
|
|
676
|
+
text: ` ${prefix}${branch}${dirtyIndicator} `,
|
|
619
677
|
colors: this.theme.git
|
|
620
678
|
};
|
|
621
679
|
}
|
|
622
|
-
renderModel(
|
|
623
|
-
if (!this.config.model?.enabled || !envInfo.model) {
|
|
680
|
+
renderModel(ctx) {
|
|
681
|
+
if (!this.config.model?.enabled || !ctx.envInfo.model) {
|
|
624
682
|
return null;
|
|
625
683
|
}
|
|
626
684
|
const icon = this.usePowerline ? this.symbols.model : "";
|
|
627
685
|
const prefix = icon ? `${icon} ` : "";
|
|
628
686
|
return {
|
|
629
|
-
text: ` ${prefix}${envInfo.model} `,
|
|
687
|
+
text: ` ${prefix}${ctx.envInfo.model} `,
|
|
630
688
|
colors: this.theme.model
|
|
631
689
|
};
|
|
632
690
|
}
|
|
633
|
-
renderBlock(
|
|
634
|
-
if (!blockInfo || !this.config.block?.enabled) {
|
|
691
|
+
renderBlock(ctx) {
|
|
692
|
+
if (!ctx.blockInfo || !this.config.block?.enabled) {
|
|
635
693
|
return null;
|
|
636
694
|
}
|
|
637
695
|
const icon = this.usePowerline ? this.symbols.block : "BLK";
|
|
638
|
-
if (blockInfo.percentUsed === null) {
|
|
696
|
+
if (ctx.blockInfo.percentUsed === null) {
|
|
639
697
|
return {
|
|
640
698
|
text: ` ${icon} -- `,
|
|
641
699
|
colors: this.theme.block
|
|
642
700
|
};
|
|
643
701
|
}
|
|
644
|
-
const percent = blockInfo.percentUsed;
|
|
702
|
+
const percent = ctx.blockInfo.percentUsed;
|
|
645
703
|
const colors = this.getColorsForPercent(percent, this.theme.block);
|
|
646
704
|
const displayStyle = this.config.block.displayStyle || "text";
|
|
647
705
|
const barWidth = this.config.block.barWidth || 10;
|
|
648
706
|
const showTime = this.config.block.showTimeRemaining ?? true;
|
|
707
|
+
const trend = this.getTrendSymbol(ctx.trendInfo?.fiveHourTrend ?? null);
|
|
649
708
|
let text;
|
|
650
|
-
if (displayStyle === "bar") {
|
|
709
|
+
if (displayStyle === "bar" && !ctx.compact) {
|
|
651
710
|
const bar = this.formatProgressBar(percent, barWidth);
|
|
652
|
-
text = `${bar} ${Math.round(percent)}
|
|
711
|
+
text = `${bar} ${Math.round(percent)}%${trend}`;
|
|
653
712
|
} else {
|
|
654
|
-
text = `${Math.round(percent)}
|
|
713
|
+
text = `${Math.round(percent)}%${trend}`;
|
|
655
714
|
}
|
|
656
|
-
if (showTime && blockInfo.timeRemaining !== null) {
|
|
657
|
-
const timeStr = this.formatTimeRemaining(blockInfo.timeRemaining);
|
|
715
|
+
if (showTime && ctx.blockInfo.timeRemaining !== null && !ctx.compact) {
|
|
716
|
+
const timeStr = this.formatTimeRemaining(ctx.blockInfo.timeRemaining, ctx.compact);
|
|
658
717
|
text += ` (${timeStr})`;
|
|
659
718
|
}
|
|
660
719
|
return {
|
|
@@ -662,48 +721,70 @@ var Renderer = class {
|
|
|
662
721
|
colors
|
|
663
722
|
};
|
|
664
723
|
}
|
|
665
|
-
renderWeekly(
|
|
666
|
-
if (!weeklyInfo || !this.config.weekly?.enabled) {
|
|
724
|
+
renderWeekly(ctx) {
|
|
725
|
+
if (!ctx.weeklyInfo || !this.config.weekly?.enabled) {
|
|
667
726
|
return null;
|
|
668
727
|
}
|
|
669
728
|
const icon = this.usePowerline ? this.symbols.weekly : "WK";
|
|
670
|
-
if (weeklyInfo.percentUsed === null) {
|
|
729
|
+
if (ctx.weeklyInfo.percentUsed === null) {
|
|
671
730
|
return {
|
|
672
731
|
text: ` ${icon} -- `,
|
|
673
732
|
colors: this.theme.weekly
|
|
674
733
|
};
|
|
675
734
|
}
|
|
676
|
-
const percent = weeklyInfo.percentUsed;
|
|
735
|
+
const percent = ctx.weeklyInfo.percentUsed;
|
|
677
736
|
const displayStyle = this.config.weekly.displayStyle || "text";
|
|
678
737
|
const barWidth = this.config.weekly.barWidth || 10;
|
|
679
738
|
const showWeekProgress = this.config.weekly.showWeekProgress ?? true;
|
|
739
|
+
const trend = this.getTrendSymbol(ctx.trendInfo?.sevenDayTrend ?? null);
|
|
680
740
|
let text;
|
|
681
|
-
if (displayStyle === "bar") {
|
|
741
|
+
if (displayStyle === "bar" && !ctx.compact) {
|
|
682
742
|
const bar = this.formatProgressBar(percent, barWidth);
|
|
683
|
-
text = `${bar} ${Math.round(percent)}
|
|
743
|
+
text = `${bar} ${Math.round(percent)}%${trend}`;
|
|
684
744
|
} else {
|
|
685
|
-
text = `${Math.round(percent)}
|
|
745
|
+
text = `${Math.round(percent)}%${trend}`;
|
|
686
746
|
}
|
|
687
|
-
if (showWeekProgress) {
|
|
688
|
-
text += ` (wk ${weeklyInfo.weekProgressPercent}%)`;
|
|
747
|
+
if (showWeekProgress && !ctx.compact) {
|
|
748
|
+
text += ` (wk ${ctx.weeklyInfo.weekProgressPercent}%)`;
|
|
689
749
|
}
|
|
690
750
|
return {
|
|
691
751
|
text: ` ${icon} ${text} `,
|
|
692
752
|
colors: this.theme.weekly
|
|
693
753
|
};
|
|
694
754
|
}
|
|
695
|
-
|
|
755
|
+
getSegment(name, ctx) {
|
|
756
|
+
switch (name) {
|
|
757
|
+
case "directory":
|
|
758
|
+
return this.renderDirectory(ctx);
|
|
759
|
+
case "git":
|
|
760
|
+
return this.renderGit(ctx);
|
|
761
|
+
case "model":
|
|
762
|
+
return this.renderModel(ctx);
|
|
763
|
+
case "block":
|
|
764
|
+
return this.renderBlock(ctx);
|
|
765
|
+
case "weekly":
|
|
766
|
+
return this.renderWeekly(ctx);
|
|
767
|
+
default:
|
|
768
|
+
return null;
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
render(blockInfo, weeklyInfo, envInfo, trendInfo = null) {
|
|
772
|
+
const compact = this.isCompactMode();
|
|
773
|
+
const ctx = {
|
|
774
|
+
blockInfo,
|
|
775
|
+
weeklyInfo,
|
|
776
|
+
envInfo,
|
|
777
|
+
trendInfo,
|
|
778
|
+
compact
|
|
779
|
+
};
|
|
696
780
|
const segments = [];
|
|
697
|
-
const
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
if (blockSegment) segments.push(blockSegment);
|
|
705
|
-
const weeklySegment = this.renderWeekly(weeklyInfo);
|
|
706
|
-
if (weeklySegment) segments.push(weeklySegment);
|
|
781
|
+
const order = this.config.segmentOrder ?? ["directory", "git", "model", "block", "weekly"];
|
|
782
|
+
for (const name of order) {
|
|
783
|
+
const segment = this.getSegment(name, ctx);
|
|
784
|
+
if (segment) {
|
|
785
|
+
segments.push(segment);
|
|
786
|
+
}
|
|
787
|
+
}
|
|
707
788
|
if (segments.length === 0) {
|
|
708
789
|
return "";
|
|
709
790
|
}
|
|
@@ -869,8 +950,10 @@ async function main() {
|
|
|
869
950
|
]);
|
|
870
951
|
debug("Block info:", JSON.stringify(blockInfo));
|
|
871
952
|
debug("Weekly info:", JSON.stringify(weeklyInfo));
|
|
953
|
+
const trendInfo = config.showTrend ? getUsageTrend() : null;
|
|
954
|
+
debug("Trend info:", JSON.stringify(trendInfo));
|
|
872
955
|
const renderer = new Renderer(config);
|
|
873
|
-
const output = renderer.render(blockInfo, weeklyInfo, envInfo);
|
|
956
|
+
const output = renderer.render(blockInfo, weeklyInfo, envInfo, trendInfo);
|
|
874
957
|
if (output) {
|
|
875
958
|
process.stdout.write(output);
|
|
876
959
|
}
|