claude-limitline 1.4.0 → 1.5.1

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.
Files changed (3) hide show
  1. package/README.md +7 -3
  2. package/dist/index.js +92 -11
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -173,12 +173,16 @@ claude-limitline retrieves data from two sources:
173
173
 
174
174
  ### OAuth Token Location
175
175
 
176
+ The tool automatically retrieves your OAuth token from Claude Code's credential storage:
177
+
176
178
  | Platform | Location |
177
179
  |----------|----------|
180
+ | **macOS** | Keychain (`Claude Code-credentials` service) |
178
181
  | **Windows** | Credential Manager or `~/.claude/.credentials.json` |
179
- | **macOS** | Keychain or `~/.claude/.credentials.json` |
180
182
  | **Linux** | secret-tool (GNOME Keyring) or `~/.claude/.credentials.json` |
181
183
 
184
+ > **Note:** On macOS, the token is read directly from the system Keychain where Claude Code stores it. No additional configuration is needed—just make sure you're logged into Claude Code (`claude --login`).
185
+
182
186
  ## Development
183
187
 
184
188
  ```bash
@@ -191,7 +195,7 @@ npm run dev # Watch mode
191
195
 
192
196
  ## Testing
193
197
 
194
- The project uses [Vitest](https://vitest.dev/) for testing with 164 tests covering config loading, themes, segments, utilities, and rendering.
198
+ The project uses [Vitest](https://vitest.dev/) for testing with 170 tests covering config loading, themes, segments, utilities, and rendering.
195
199
 
196
200
  ```bash
197
201
  npm test # Run tests once
@@ -207,7 +211,7 @@ npm run test:coverage # Coverage report
207
211
  | `src/themes/index.test.ts` | 37 | Theme retrieval, color validation |
208
212
  | `src/segments/block.test.ts` | 8 | Block segment, time calculations |
209
213
  | `src/segments/weekly.test.ts` | 13 | Weekly segment, week progress |
210
- | `src/utils/oauth.test.ts` | 13 | API responses, caching, trends |
214
+ | `src/utils/oauth.test.ts` | 19 | API responses, caching, trends, macOS keychain |
211
215
  | `src/utils/claude-hook.test.ts` | 21 | Model name formatting |
212
216
  | `src/utils/environment.test.ts` | 20 | Git branch, directory detection |
213
217
  | `src/utils/terminal.test.ts` | 13 | Terminal width, ANSI handling |
package/dist/index.js CHANGED
@@ -44,6 +44,9 @@ var DEFAULT_CONFIG = {
44
44
  showWeekProgress: true,
45
45
  viewMode: "simple"
46
46
  },
47
+ context: {
48
+ enabled: true
49
+ },
47
50
  budget: {
48
51
  pollInterval: 15,
49
52
  warningThreshold: 80
@@ -161,12 +164,26 @@ async function getOAuthTokenWindows() {
161
164
  async function getOAuthTokenMacOS() {
162
165
  try {
163
166
  const { stdout } = await execAsync(
164
- `security find-generic-password -s "Claude Code" -w`,
167
+ `security find-generic-password -s "Claude Code-credentials" -w`,
165
168
  { timeout: 5e3 }
166
169
  );
167
- const token = stdout.trim();
168
- if (token && token.startsWith("sk-ant-oat")) {
169
- return token;
170
+ const content = stdout.trim();
171
+ if (content.startsWith("{")) {
172
+ try {
173
+ const parsed = JSON.parse(content);
174
+ if (parsed.claudeAiOauth && typeof parsed.claudeAiOauth === "object") {
175
+ const token = parsed.claudeAiOauth.accessToken;
176
+ if (token && typeof token === "string" && token.startsWith("sk-ant-oat")) {
177
+ debug("Found OAuth token in macOS Keychain under claudeAiOauth.accessToken");
178
+ return token;
179
+ }
180
+ }
181
+ } catch (parseError) {
182
+ debug("Failed to parse keychain JSON:", parseError);
183
+ }
184
+ }
185
+ if (content.startsWith("sk-ant-oat")) {
186
+ return content;
170
187
  }
171
188
  } catch (error) {
172
189
  debug("macOS Keychain retrieval failed:", error);
@@ -535,6 +552,8 @@ var darkTheme = {
535
552
  // Purple for Opus
536
553
  sonnet: { bg: "#1a1a1a", fg: "#89ddff" },
537
554
  // Light blue for Sonnet
555
+ context: { bg: "#2a2a2a", fg: "#87ceeb" },
556
+ // Sky blue for context
538
557
  warning: { bg: "#d75f00", fg: "#ffffff" },
539
558
  critical: { bg: "#af0000", fg: "#ffffff" }
540
559
  };
@@ -548,6 +567,8 @@ var lightTheme = {
548
567
  // Purple for Opus
549
568
  sonnet: { bg: "#0ea5e9", fg: "#ffffff" },
550
569
  // Sky blue for Sonnet
570
+ context: { bg: "#6366f1", fg: "#ffffff" },
571
+ // Indigo for context
551
572
  warning: { bg: "#f59e0b", fg: "#000000" },
552
573
  critical: { bg: "#ef4444", fg: "#ffffff" }
553
574
  };
@@ -561,6 +582,8 @@ var nordTheme = {
561
582
  // Nord purple for Opus
562
583
  sonnet: { bg: "#2e3440", fg: "#88c0d0" },
563
584
  // Nord frost for Sonnet
585
+ context: { bg: "#3b4252", fg: "#81a1c1" },
586
+ // Nord frost for context
564
587
  warning: { bg: "#d08770", fg: "#2e3440" },
565
588
  critical: { bg: "#bf616a", fg: "#eceff4" }
566
589
  };
@@ -574,6 +597,8 @@ var gruvboxTheme = {
574
597
  // Gruvbox purple for Opus
575
598
  sonnet: { bg: "#282828", fg: "#8ec07c" },
576
599
  // Gruvbox aqua for Sonnet
600
+ context: { bg: "#3c3836", fg: "#83a598" },
601
+ // Gruvbox blue for context
577
602
  warning: { bg: "#d79921", fg: "#282828" },
578
603
  critical: { bg: "#cc241d", fg: "#ebdbb2" }
579
604
  };
@@ -587,6 +612,8 @@ var tokyoNightTheme = {
587
612
  // Tokyo purple for Opus
588
613
  sonnet: { bg: "#1a202c", fg: "#7dcfff" },
589
614
  // Tokyo cyan for Sonnet
615
+ context: { bg: "#2d3748", fg: "#7aa2f7" },
616
+ // Tokyo blue for context
590
617
  warning: { bg: "#e0af68", fg: "#1a1b26" },
591
618
  critical: { bg: "#f7768e", fg: "#1a1b26" }
592
619
  };
@@ -600,6 +627,8 @@ var rosePineTheme = {
600
627
  // Rose Pine iris for Opus
601
628
  sonnet: { bg: "#232136", fg: "#31748f" },
602
629
  // Rose Pine pine for Sonnet
630
+ context: { bg: "#2a273f", fg: "#9ccfd8" },
631
+ // Rose Pine foam for context
603
632
  warning: { bg: "#f6c177", fg: "#191724" },
604
633
  critical: { bg: "#eb6f92", fg: "#191724" }
605
634
  };
@@ -639,9 +668,12 @@ var Renderer = class {
639
668
  sonnet: symbolSet.sonnet_cost,
640
669
  bottleneck: symbolSet.bottleneck,
641
670
  rightArrow: symbolSet.right,
671
+ leftArrow: symbolSet.left,
642
672
  separator: symbolSet.separator,
643
673
  branch: symbolSet.branch,
644
674
  model: symbolSet.model,
675
+ context: "\u25D0",
676
+ // Half-filled circle for context
645
677
  progressFull: symbolSet.progress_full,
646
678
  progressEmpty: symbolSet.progress_empty,
647
679
  trendUp: "\u2191",
@@ -704,6 +736,18 @@ var Renderer = class {
704
736
  output += RESET_CODE;
705
737
  return output;
706
738
  }
739
+ renderRightPowerline(segments) {
740
+ if (segments.length === 0) return "";
741
+ let output = "";
742
+ for (let i = 0; i < segments.length; i++) {
743
+ const seg = segments[i];
744
+ output += RESET_CODE;
745
+ output += ansi.fg(seg.colors.bg) + this.symbols.leftArrow;
746
+ output += ansi.bg(seg.colors.bg) + ansi.fg(seg.colors.fg) + seg.text;
747
+ }
748
+ output += RESET_CODE;
749
+ return output;
750
+ }
707
751
  renderFallback(segments) {
708
752
  return segments.map((seg) => ansi.bg(seg.colors.bg) + ansi.fg(seg.colors.fg) + seg.text + RESET_CODE).join(` ${this.symbols.separator} `);
709
753
  }
@@ -857,6 +901,18 @@ var Renderer = class {
857
901
  return this.renderWeeklySimple(ctx);
858
902
  }
859
903
  }
904
+ renderContext(ctx) {
905
+ if (!this.config.context?.enabled) {
906
+ return null;
907
+ }
908
+ const percent = ctx.envInfo.contextPercent;
909
+ const icon = this.usePowerline ? this.symbols.context : "CTX";
910
+ const colors = this.getColorsForPercent(percent, this.theme.context);
911
+ return {
912
+ text: ` ${icon} ${percent}% `,
913
+ colors
914
+ };
915
+ }
860
916
  getSegment(name, ctx) {
861
917
  switch (name) {
862
918
  case "directory":
@@ -869,6 +925,8 @@ var Renderer = class {
869
925
  return this.renderBlock(ctx);
870
926
  case "weekly":
871
927
  return this.renderWeekly(ctx);
928
+ case "context":
929
+ return this.renderContext(ctx);
872
930
  default:
873
931
  return null;
874
932
  }
@@ -882,22 +940,35 @@ var Renderer = class {
882
940
  trendInfo,
883
941
  compact
884
942
  };
885
- const segments = [];
943
+ const leftSegments = [];
886
944
  const order = this.config.segmentOrder ?? ["directory", "git", "model", "block", "weekly"];
887
945
  for (const name of order) {
946
+ if (name === "context") continue;
888
947
  const segment = this.getSegment(name, ctx);
889
948
  if (segment) {
890
- segments.push(segment);
949
+ leftSegments.push(segment);
891
950
  }
892
951
  }
893
- if (segments.length === 0) {
894
- return "";
952
+ const rightSegments = [];
953
+ const contextSegment = this.renderContext(ctx);
954
+ if (contextSegment) {
955
+ rightSegments.push(contextSegment);
895
956
  }
957
+ let output = "";
896
958
  if (this.usePowerline) {
897
- return this.renderPowerline(segments);
959
+ if (leftSegments.length > 0) {
960
+ output += this.renderPowerline(leftSegments);
961
+ }
962
+ if (rightSegments.length > 0) {
963
+ output += this.renderRightPowerline(rightSegments);
964
+ }
898
965
  } else {
899
- return this.renderFallback(segments);
966
+ const allSegments = [...leftSegments, ...rightSegments];
967
+ if (allSegments.length > 0) {
968
+ output = this.renderFallback(allSegments);
969
+ }
900
970
  }
971
+ return output;
901
972
  }
902
973
  };
903
974
 
@@ -1023,12 +1094,22 @@ function getClaudeModel(hookData) {
1023
1094
  }
1024
1095
  return null;
1025
1096
  }
1097
+ function getContextPercent(hookData) {
1098
+ const ctx = hookData?.context_window;
1099
+ if (!ctx?.current_usage || !ctx.context_window_size) {
1100
+ return 0;
1101
+ }
1102
+ const usage = ctx.current_usage;
1103
+ const totalTokens = (usage.input_tokens || 0) + (usage.cache_creation_input_tokens || 0) + (usage.cache_read_input_tokens || 0);
1104
+ return Math.round(totalTokens / ctx.context_window_size * 100);
1105
+ }
1026
1106
  function getEnvironmentInfo(hookData) {
1027
1107
  return {
1028
1108
  directory: getDirectoryName(hookData),
1029
1109
  gitBranch: getGitBranch(),
1030
1110
  gitDirty: hasGitChanges(),
1031
- model: getClaudeModel(hookData)
1111
+ model: getClaudeModel(hookData),
1112
+ contextPercent: getContextPercent(hookData)
1032
1113
  };
1033
1114
  }
1034
1115
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-limitline",
3
- "version": "1.4.0",
3
+ "version": "1.5.1",
4
4
  "description": "A statusline for Claude Code showing real-time usage limits and weekly tracking",
5
5
  "main": "dist/index.js",
6
6
  "bin": {