ccstatusline-usage 2.3.17 → 2.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/README.md CHANGED
@@ -36,6 +36,42 @@ This fork adds API-based usage widgets beyond the upstream:
36
36
  - **Context Window Display** - Visual bar showing context usage
37
37
  - **Off Peak** - Shows peak/off-peak status with countdown timer (peak hours drain sessions faster)
38
38
  - **Two-line Layout** - Session info on line 1, context on line 2
39
+ - **Multi-provider routing** - Usage widgets dispatch per model: Anthropic models hit the usage API; opencode/local models (GLM, Kimi, MiniMax, Qwen, Ollama) skip the fetch and gracefully hide usage bars while keeping the real-time context bar.
40
+
41
+ ### Multi-Provider Routing (Opencode / Local Models)
42
+
43
+ Claude Code can route individual prompts to non-Anthropic backends via opencode or a local Ollama runtime. The status line reads the `model.id` that Claude Code sends on stdin each render and dispatches through a per-provider resolver (`src/utils/usage/resolver.ts`):
44
+
45
+ - **Anthropic** (`opus`, `sonnet`, `haiku` in the id) — fetches Session/Weekly/Reset from the usage API.
46
+ - **Opencode** (`glm`, `kimi`, `minimax`, `mm-`, `qwen`, `owen`, `mimo`) — no usage API call; Session/Weekly/Reset widgets hide themselves. Context Bar still renders when `context_window` is in the payload.
47
+ - **Unknown model id** — same behavior as opencode (hides usage widgets).
48
+
49
+ This means opencode-routed turns don't trigger pointless Anthropic API calls or rate-limiting during heavy local usage.
50
+
51
+ Example — configure Claude Code to route a model through opencode (edit `~/.claude/settings.json`):
52
+
53
+ ```jsonc
54
+ {
55
+ "statusLine": {
56
+ "type": "command",
57
+ "command": "npx -y ccstatusline-usage@latest",
58
+ "padding": 0
59
+ },
60
+ "model": "glm-5.1" // or "kimi-k2.6", "minimax-m2.7", "qwen-3.6-plus", "qwen3.6:35b-a3b-q4_K_M" for local Ollama
61
+ }
62
+ ```
63
+
64
+ What the status line renders per model:
65
+
66
+ ```
67
+ # Anthropic (opus / sonnet / haiku)
68
+ Session: [████░░░░░░░░░░░] 27.0% | Weekly: [████░░░░░░░░░░░] 34.0% | 2:03 hr | Model: Opus 4.7
69
+ Context: [██████░░░░░░░░░] 389k/1M (39%) | Pace: [░░░░░░█|░░░░░░░] D4/7 -8% | Off-peak (4:03 hr)
70
+
71
+ # Opencode / local (glm-5.1, kimi, qwen, …)
72
+ Model: glm-5.1 | Off-peak (4:03 hr)
73
+ Context: [██░░░░░░░░░░░░░] 50k/200k (25%)
74
+ ```
39
75
 
40
76
  ### Enhanced Status Line Preview
41
77
 
@@ -67,6 +103,13 @@ Session: [████░░░░░░░░░░░] 27.0% | Weekly: [██
67
103
 
68
104
  ## 🆕 Recent Updates
69
105
 
106
+ ### [v2.4.0](https://github.com/pcvelz/ccstatusline-usage/releases/tag/v2.4.0) - Multi-provider router for usage widgets
107
+
108
+ - [pcvelz/ccstatusline-usage](https://github.com/pcvelz/ccstatusline-usage): **Provider pattern end-to-end** — Usage widgets now dispatch through `resolveProvider(modelId)` in `src/utils/usage/resolver.ts`. Anthropic models (`opus`/`sonnet`/`haiku`) fetch from the usage API; opencode/local models (`glm`, `kimi`, `minimax`, `mm-`, `qwen`, `owen`, `mimo`) skip the fetch entirely so heavy local-model sessions no longer trigger needless Anthropic API calls or rate-limiting.
109
+ - [pcvelz/ccstatusline-usage](https://github.com/pcvelz/ccstatusline-usage): The prefetch layer (`usage-prefetch.ts`) now reads `data.model.id` from the payload and dispatches via `provider.fetchUsage()` instead of the hardcoded Anthropic path. `opencodeProvider` / `nullProvider` return empty `UsageData`, so Session/Weekly/Reset widgets hide themselves naturally.
110
+ - [pcvelz/ccstatusline-usage](https://github.com/pcvelz/ccstatusline-usage): **Context Bar stays backend-agnostic** — renders whenever `context_window` is present in the payload, regardless of which provider handled the turn.
111
+ - See the new [Multi-Provider Routing](#multi-provider-routing-opencode--local-models) section under Fork Enhancements for example config.
112
+
70
113
  ### [v2.3.17](https://github.com/pcvelz/ccstatusline-usage/releases/tag/v2.3.17) - Local model fallback for API usage widgets
71
114
 
72
115
  - [pcvelz/ccstatusline-usage](https://github.com/pcvelz/ccstatusline-usage): **Local model fallback** — When the active model is not Opus/Sonnet/Haiku (e.g. a local Ollama model like `qwen3-coder:30b`), the Session, Weekly, and Context widgets now render empty-bar placeholders (`[░░░░░░░░░░░░░░░] -.0%`) and the Reset Timer shows `-:00 hr` instead of misleading Claude API values.
@@ -52556,7 +52556,7 @@ class OutputStyleWidget {
52556
52556
  }
52557
52557
 
52558
52558
  // src/utils/git.ts
52559
- import { execSync } from "child_process";
52559
+ import { execFileSync } from "child_process";
52560
52560
  function resolveGitCwd(context) {
52561
52561
  const candidates = [
52562
52562
  context.data?.cwd,
@@ -52571,13 +52571,18 @@ function resolveGitCwd(context) {
52571
52571
  return;
52572
52572
  }
52573
52573
  function runGit(command, context) {
52574
+ const args = command.trim().split(/\s+/).filter(Boolean);
52575
+ return runGitArgs(args, context, command);
52576
+ }
52577
+ function runGitArgs(args, context, cacheCommand) {
52574
52578
  const cwd2 = resolveGitCwd(context);
52575
- const cacheKey = `${command}|${cwd2 ?? ""}`;
52579
+ const cacheToken = cacheCommand ?? args.join("\x00");
52580
+ const cacheKey = `${cacheToken}|${cwd2 ?? ""}`;
52576
52581
  if (gitCommandCache.has(cacheKey)) {
52577
52582
  return gitCommandCache.get(cacheKey) ?? null;
52578
52583
  }
52579
52584
  try {
52580
- const output = execSync(`git ${command}`, {
52585
+ const output = execFileSync("git", args, {
52581
52586
  encoding: "utf8",
52582
52587
  stdio: ["pipe", "pipe", "ignore"],
52583
52588
  ...cwd2 ? { cwd: cwd2 } : {}
@@ -53136,7 +53141,7 @@ var init_GitRootDir = __esm(() => {
53136
53141
  });
53137
53142
 
53138
53143
  // src/utils/gh-pr-cache.ts
53139
- import { execFileSync } from "child_process";
53144
+ import { execFileSync as execFileSync2 } from "child_process";
53140
53145
  import {
53141
53146
  existsSync as existsSync2,
53142
53147
  mkdirSync,
@@ -53278,7 +53283,7 @@ function truncateTitle(title, maxWidth) {
53278
53283
  var PR_CACHE_TTL = 30000, GH_TIMEOUT = 5000, DEFAULT_TITLE_MAX_WIDTH = 30, DEFAULT_PR_CACHE_DEPS;
53279
53284
  var init_gh_pr_cache = __esm(() => {
53280
53285
  DEFAULT_PR_CACHE_DEPS = {
53281
- execFileSync,
53286
+ execFileSync: execFileSync2,
53282
53287
  existsSync: existsSync2,
53283
53288
  mkdirSync,
53284
53289
  readFileSync: readFileSync2,
@@ -53956,7 +53961,7 @@ function parseRemoteUrl(url2) {
53956
53961
  }
53957
53962
  }
53958
53963
  function getRemoteInfo(remoteName, context) {
53959
- const url2 = runGit(`remote get-url ${remoteName}`, context);
53964
+ const url2 = runGitArgs(["remote", "get-url", "--", remoteName], context, `remote get-url -- ${remoteName}`);
53960
53965
  if (!url2) {
53961
53966
  return null;
53962
53967
  }
@@ -55301,6 +55306,15 @@ function getContextConfig(modelIdentifier, contextWindowSize) {
55301
55306
  if (!modelIdentifier) {
55302
55307
  return defaultConfig;
55303
55308
  }
55309
+ const normalizedModel = modelIdentifier.toLowerCase().trim();
55310
+ for (const [modelName, contextSize] of Object.entries(OPENCODE_MODEL_CONTEXT_MAP)) {
55311
+ if (normalizedModel.includes(modelName)) {
55312
+ return {
55313
+ maxTokens: contextSize,
55314
+ usableTokens: Math.floor(contextSize * USABLE_CONTEXT_RATIO)
55315
+ };
55316
+ }
55317
+ }
55304
55318
  const inferredWindowSize = parseContextWindowSize(modelIdentifier);
55305
55319
  if (inferredWindowSize !== null) {
55306
55320
  return {
@@ -55310,7 +55324,24 @@ function getContextConfig(modelIdentifier, contextWindowSize) {
55310
55324
  }
55311
55325
  return defaultConfig;
55312
55326
  }
55313
- var DEFAULT_CONTEXT_WINDOW_SIZE = 200000, USABLE_CONTEXT_RATIO = 0.8;
55327
+ var DEFAULT_CONTEXT_WINDOW_SIZE = 200000, USABLE_CONTEXT_RATIO = 0.8, OPENCODE_MODEL_CONTEXT_MAP;
55328
+ var init_model_context = __esm(() => {
55329
+ OPENCODE_MODEL_CONTEXT_MAP = {
55330
+ "glm-5.1": 1e6,
55331
+ "glm-4.5": 1e6,
55332
+ "glm-4.0": 1e6,
55333
+ "mm-2.7": 1e6,
55334
+ "mm-2.5": 1e6,
55335
+ "kimi-k2.6": 1e6,
55336
+ "kimi-k2.5": 1e6,
55337
+ "owen-3.6": 1e6,
55338
+ "owen-3.5": 1e6,
55339
+ "qwen-2.5": 1e6,
55340
+ "qwen-2.0": 1e6,
55341
+ "qwen-1.5": 1e6,
55342
+ "qwen-1.0": 1e6
55343
+ };
55344
+ });
55314
55345
 
55315
55346
  // src/utils/context-percentage.ts
55316
55347
  function calculateContextPercentage(context) {
@@ -55325,10 +55356,12 @@ function calculateContextPercentage(context) {
55325
55356
  const contextConfig = getContextConfig(modelIdentifier, contextWindowMetrics.windowSize);
55326
55357
  return Math.min(100, context.tokenMetrics.contextLength / contextConfig.maxTokens * 100);
55327
55358
  }
55328
- var init_context_percentage = () => {};
55359
+ var init_context_percentage = __esm(() => {
55360
+ init_model_context();
55361
+ });
55329
55362
 
55330
55363
  // src/utils/terminal.ts
55331
- import { execSync as execSync2 } from "child_process";
55364
+ import { execSync } from "child_process";
55332
55365
  import * as fs2 from "fs";
55333
55366
  import * as path2 from "path";
55334
55367
  function getPackageVersion() {
@@ -55352,7 +55385,7 @@ function getPackageVersion() {
55352
55385
  function probeTerminalWidth() {
55353
55386
  if (process.env.TMUX) {
55354
55387
  try {
55355
- const output = execSync2("tmux display-message -p '#{pane_width}'", { encoding: "utf8", stdio: ["pipe", "pipe", "ignore"], timeout: 2000 }).trim();
55388
+ const output = execSync("tmux display-message -p '#{pane_width}'", { encoding: "utf8", stdio: ["pipe", "pipe", "ignore"], timeout: 2000 }).trim();
55356
55389
  const parsed = parseInt(output, 10);
55357
55390
  if (!isNaN(parsed) && parsed > 0)
55358
55391
  return parsed;
@@ -55378,7 +55411,7 @@ function probeTerminalWidth() {
55378
55411
  }
55379
55412
  }
55380
55413
  try {
55381
- const width = execSync2("tput cols 2>/dev/null", {
55414
+ const width = execSync("tput cols 2>/dev/null", {
55382
55415
  encoding: "utf8",
55383
55416
  stdio: ["pipe", "pipe", "ignore"]
55384
55417
  }).trim();
@@ -55395,7 +55428,7 @@ function parsePositiveInteger(value) {
55395
55428
  }
55396
55429
  function getParentProcessId(pid) {
55397
55430
  try {
55398
- const parentPidOutput = execSync2(`ps -o ppid= -p ${pid}`, {
55431
+ const parentPidOutput = execSync(`ps -o ppid= -p ${pid}`, {
55399
55432
  encoding: "utf8",
55400
55433
  stdio: ["pipe", "pipe", "ignore"],
55401
55434
  shell: "/bin/sh"
@@ -55407,7 +55440,7 @@ function getParentProcessId(pid) {
55407
55440
  }
55408
55441
  function getTTYForProcess(pid) {
55409
55442
  try {
55410
- const tty2 = execSync2(`ps -o tty= -p ${pid}`, {
55443
+ const tty2 = execSync(`ps -o tty= -p ${pid}`, {
55411
55444
  encoding: "utf8",
55412
55445
  stdio: ["pipe", "pipe", "ignore"],
55413
55446
  shell: "/bin/sh"
@@ -55422,7 +55455,7 @@ function getTTYForProcess(pid) {
55422
55455
  }
55423
55456
  function getWidthForTTY(tty2) {
55424
55457
  try {
55425
- const width = execSync2(`stty size < /dev/${tty2} | awk '{print $2}'`, {
55458
+ const width = execSync(`stty size < /dev/${tty2} | awk '{print $2}'`, {
55426
55459
  encoding: "utf8",
55427
55460
  stdio: ["pipe", "pipe", "ignore"],
55428
55461
  shell: "/bin/sh"
@@ -55438,7 +55471,7 @@ function getTerminalWidth() {
55438
55471
  function canDetectTerminalWidth() {
55439
55472
  return probeTerminalWidth() !== null;
55440
55473
  }
55441
- var __dirname = "/Users/peter/Documents/Code/ccstatusline-usage/src/utils", PACKAGE_VERSION = "2.3.17";
55474
+ var __dirname = "/Users/peter/Documents/Code/ccstatusline-usage/src/utils", PACKAGE_VERSION = "2.4.0";
55442
55475
  var init_terminal = () => {};
55443
55476
 
55444
55477
  // src/utils/renderer.ts
@@ -56311,6 +56344,7 @@ class ContextPercentageWidget {
56311
56344
  }
56312
56345
  }
56313
56346
  var init_ContextPercentage = __esm(() => {
56347
+ init_model_context();
56314
56348
  init_context_inverse();
56315
56349
  });
56316
56350
 
@@ -56371,6 +56405,7 @@ class ContextPercentageUsableWidget {
56371
56405
  }
56372
56406
  }
56373
56407
  var init_ContextPercentageUsable = __esm(() => {
56408
+ init_model_context();
56374
56409
  init_context_inverse();
56375
56410
  });
56376
56411
 
@@ -57026,7 +57061,7 @@ var init_CustomSymbol = __esm(async () => {
57026
57061
  });
57027
57062
 
57028
57063
  // src/widgets/CustomCommand.tsx
57029
- import { execSync as execSync3 } from "child_process";
57064
+ import { execSync as execSync2 } from "child_process";
57030
57065
 
57031
57066
  class CustomCommandWidget {
57032
57067
  getDefaultColor() {
@@ -57073,7 +57108,7 @@ class CustomCommandWidget {
57073
57108
  try {
57074
57109
  const timeout = item.timeout ?? 1000;
57075
57110
  const jsonInput = JSON.stringify(context.data);
57076
- let output = execSync3(item.commandPath, {
57111
+ let output = execSync2(item.commandPath, {
57077
57112
  encoding: "utf8",
57078
57113
  input: jsonInput,
57079
57114
  timeout,
@@ -58237,7 +58272,7 @@ var init_usage_types = __esm(() => {
58237
58272
  });
58238
58273
 
58239
58274
  // src/utils/usage-fetch.ts
58240
- import { execFileSync as execFileSync2 } from "child_process";
58275
+ import { execFileSync as execFileSync3 } from "child_process";
58241
58276
  import * as fs3 from "fs";
58242
58277
  import * as https from "https";
58243
58278
  import * as os4 from "os";
@@ -58383,7 +58418,7 @@ function parseMacKeychainCredentialCandidates(rawDump, servicePrefix = MACOS_USA
58383
58418
  }
58384
58419
  function readMacKeychainSecret(service) {
58385
58420
  try {
58386
- return execFileSync2("security", ["find-generic-password", "-s", service, "-w"], { encoding: "utf8", stdio: ["pipe", "pipe", "ignore"] }).trim();
58421
+ return execFileSync3("security", ["find-generic-password", "-s", service, "-w"], { encoding: "utf8", stdio: ["pipe", "pipe", "ignore"] }).trim();
58387
58422
  } catch {
58388
58423
  return null;
58389
58424
  }
@@ -58394,7 +58429,7 @@ function readUsageTokenFromMacKeychainService(service) {
58394
58429
  }
58395
58430
  function listMacKeychainCredentialCandidates() {
58396
58431
  try {
58397
- const rawDump = execFileSync2("security", ["dump-keychain"], {
58432
+ const rawDump = execFileSync3("security", ["dump-keychain"], {
58398
58433
  encoding: "utf8",
58399
58434
  maxBuffer: MACOS_SECURITY_DUMP_MAX_BUFFER,
58400
58435
  stdio: ["pipe", "pipe", "ignore"]
@@ -58640,7 +58675,8 @@ var init_usage_fetch = __esm(() => {
58640
58675
  extraUsageLimit: exports_external.number().nullable().optional(),
58641
58676
  extraUsageUsed: exports_external.number().nullable().optional(),
58642
58677
  extraUsageUtilization: exports_external.number().nullable().optional(),
58643
- error: exports_external.string().nullable().optional()
58678
+ error: exports_external.string().nullable().optional(),
58679
+ provider: exports_external.enum(["anthropic", "opencode"]).nullable().optional()
58644
58680
  });
58645
58681
  UsageApiResponseSchema = exports_external.object({
58646
58682
  five_hour: exports_external.object({
@@ -62796,7 +62832,7 @@ var init_TotalSpeed = __esm(async () => {
62796
62832
  });
62797
62833
 
62798
62834
  // src/widgets/FreeMemory.ts
62799
- import { execSync as execSync4 } from "child_process";
62835
+ import { execSync as execSync3 } from "child_process";
62800
62836
  import os7 from "os";
62801
62837
  function formatBytes(bytes) {
62802
62838
  const GB = 1024 ** 3;
@@ -62812,7 +62848,7 @@ function formatBytes(bytes) {
62812
62848
  }
62813
62849
  function getUsedMemoryMacOS() {
62814
62850
  try {
62815
- const output = execSync4("vm_stat", { encoding: "utf8" });
62851
+ const output = execSync3("vm_stat", { encoding: "utf8" });
62816
62852
  const lines = output.split(`
62817
62853
  `);
62818
62854
  const firstLine = lines[0];
@@ -62934,6 +62970,61 @@ class SessionNameWidget {
62934
62970
  }
62935
62971
  var init_SessionName = () => {};
62936
62972
 
62973
+ // src/utils/usage/providers/anthropic.ts
62974
+ var anthropicProvider;
62975
+ var init_anthropic = __esm(() => {
62976
+ init_usage_fetch();
62977
+ anthropicProvider = {
62978
+ name: "anthropic",
62979
+ async fetchUsage() {
62980
+ const data = await fetchUsageData();
62981
+ return { ...data, provider: "anthropic" };
62982
+ }
62983
+ };
62984
+ });
62985
+
62986
+ // src/utils/usage/providers/null.ts
62987
+ var nullProvider;
62988
+ var init_null = __esm(() => {
62989
+ nullProvider = {
62990
+ name: "null",
62991
+ fetchUsage() {
62992
+ return Promise.resolve({ provider: null });
62993
+ }
62994
+ };
62995
+ });
62996
+
62997
+ // src/utils/usage/providers/opencode.ts
62998
+ var opencodeProvider;
62999
+ var init_opencode = __esm(() => {
63000
+ opencodeProvider = {
63001
+ name: "opencode",
63002
+ fetchUsage() {
63003
+ return Promise.resolve({ provider: "opencode" });
63004
+ }
63005
+ };
63006
+ });
63007
+
63008
+ // src/utils/usage/resolver.ts
63009
+ function resolveProvider(modelId) {
63010
+ if (!modelId)
63011
+ return nullProvider;
63012
+ const id = modelId.toLowerCase();
63013
+ if (ANTHROPIC_KEYWORDS.some((k) => id.includes(k)))
63014
+ return anthropicProvider;
63015
+ if (OPENCODE_PATTERN.test(id))
63016
+ return opencodeProvider;
63017
+ return nullProvider;
63018
+ }
63019
+ var OPENCODE_PATTERN, ANTHROPIC_KEYWORDS;
63020
+ var init_resolver = __esm(() => {
63021
+ init_anthropic();
63022
+ init_null();
63023
+ init_opencode();
63024
+ OPENCODE_PATTERN = /(?:^|[^a-z])(glm|kimi|minimax|mm-|qwen|owen|mimo)/i;
63025
+ ANTHROPIC_KEYWORDS = ["opus", "sonnet", "haiku"];
63026
+ });
63027
+
62937
63028
  // src/widgets/ApiUsage.tsx
62938
63029
  function getDisplaySize(context) {
62939
63030
  const w = context.terminalWidth ?? 0;
@@ -62991,17 +63082,6 @@ function getModelId(context) {
62991
63082
  const model = context.data?.model;
62992
63083
  return (typeof model === "string" ? model : model?.id) ?? "";
62993
63084
  }
62994
- function isLocalModel(context) {
62995
- const modelId = getModelId(context);
62996
- if (modelId === "")
62997
- return false;
62998
- return !(modelId.includes("opus") || modelId.includes("sonnet") || modelId.includes("haiku"));
62999
- }
63000
- function renderLocalUsageFallback(label, shortLabel, size2) {
63001
- if (size2 === "mobile")
63002
- return `${shortLabel}: [░░░░] -.0%`;
63003
- return `${label}: [░░░░░░░░░░░░░░░] -.0%`;
63004
- }
63005
63085
 
63006
63086
  class SessionUsageWidget {
63007
63087
  getDefaultColor() {
@@ -63028,8 +63108,8 @@ class SessionUsageWidget {
63028
63108
  if (data.sessionUsage === undefined)
63029
63109
  return null;
63030
63110
  const size2 = getDisplaySize(context);
63031
- if (isLocalModel(context))
63032
- return renderLocalUsageFallback("Session", "S", size2);
63111
+ if (resolveProvider(getModelId(context)).name === "null")
63112
+ return null;
63033
63113
  const extraUsed = data.extraUsageUsed;
63034
63114
  const extraLimit = data.extraUsageLimit;
63035
63115
  if (size2 !== "mobile" && data.extraUsageEnabled === true && extraUsed !== undefined && extraLimit !== undefined && data.sessionUsage >= 100 && (data.weeklyUsage === undefined || data.weeklyUsage < 100)) {
@@ -63071,8 +63151,8 @@ class WeeklyUsageWidget {
63071
63151
  if (data.weeklyUsage === undefined)
63072
63152
  return null;
63073
63153
  const size2 = getDisplaySize(context);
63074
- if (isLocalModel(context))
63075
- return renderLocalUsageFallback("Weekly", "W", size2);
63154
+ if (resolveProvider(getModelId(context)).name === "null")
63155
+ return null;
63076
63156
  const extraUsed = data.extraUsageUsed;
63077
63157
  const extraLimit = data.extraUsageLimit;
63078
63158
  if (data.extraUsageEnabled === true && extraUsed !== undefined && extraLimit !== undefined && data.weeklyUsage >= 100) {
@@ -63111,8 +63191,8 @@ class ResetTimerWidget {
63111
63191
  const data = context.usageData ?? {};
63112
63192
  if (data.error)
63113
63193
  return getUsageErrorMessage(data.error);
63114
- if (isLocalModel(context))
63115
- return "-:00 hr";
63194
+ if (resolveProvider(getModelId(context)).name === "null")
63195
+ return null;
63116
63196
  const modelId = getModelId(context);
63117
63197
  const is1mModel = modelId.includes("[1m]");
63118
63198
  const isOpus = modelId.includes("opus");
@@ -63171,8 +63251,6 @@ class ContextBarWidget {
63171
63251
  const cw = context.data?.context_window;
63172
63252
  if (!cw)
63173
63253
  return null;
63174
- if (isLocalModel(context) && !getModelId(context).includes("qwen"))
63175
- return renderLocalUsageFallback("Context", "C", getDisplaySize(context));
63176
63254
  const total = Number(cw.context_window_size) || 200000;
63177
63255
  let used = 0;
63178
63256
  if (typeof cw.current_usage === "number") {
@@ -63202,6 +63280,7 @@ class ContextBarWidget {
63202
63280
  var DARK_RED_OPEN2 = "\x1B[38;2;204;0;0m", DARK_RED_CLOSE2 = "\x1B[39m", MOBILE_THRESHOLD = 134, MEDIUM_THRESHOLD2 = 178, MOBILE_BAR_WIDTH = 4, MEDIUM_BAR_WIDTH = 8, DEFAULT_BAR_WIDTH = 15;
63203
63281
  var init_ApiUsage = __esm(() => {
63204
63282
  init_usage();
63283
+ init_resolver();
63205
63284
  });
63206
63285
 
63207
63286
  // src/widgets/WeeklyResetTimer.ts
@@ -63775,11 +63854,11 @@ var init_ThinkingEffort = __esm(() => {
63775
63854
  });
63776
63855
 
63777
63856
  // src/widgets/Battery.ts
63778
- import { execSync as execSync5 } from "child_process";
63857
+ import { execSync as execSync4 } from "child_process";
63779
63858
  import { readFileSync as readFileSync11 } from "fs";
63780
63859
  function getMacBatteryInfo() {
63781
63860
  try {
63782
- const output = execSync5("pmset -g batt", { encoding: "utf-8", timeout: 2000 });
63861
+ const output = execSync4("pmset -g batt", { encoding: "utf-8", timeout: 2000 });
63783
63862
  const match = /(\d+)%;\s*(charging|discharging|charged|finishing charge|AC attached)/i.exec(output);
63784
63863
  const percentStr = match?.[1];
63785
63864
  const stateStr = match?.[2];
@@ -64904,7 +64983,7 @@ var init_config = __esm(() => {
64904
64983
  });
64905
64984
 
64906
64985
  // src/utils/claude-settings.ts
64907
- import { execSync as execSync6 } from "child_process";
64986
+ import { execSync as execSync5 } from "child_process";
64908
64987
  import * as fs11 from "fs";
64909
64988
  import * as os9 from "os";
64910
64989
  import * as path9 from "path";
@@ -65013,7 +65092,7 @@ async function isInstalled() {
65013
65092
  function isBunxAvailable() {
65014
65093
  try {
65015
65094
  const command = process.platform === "win32" ? "where bunx" : "which bunx";
65016
- execSync6(command, { stdio: "ignore" });
65095
+ execSync5(command, { stdio: "ignore" });
65017
65096
  return true;
65018
65097
  } catch {
65019
65098
  return false;
@@ -65021,7 +65100,7 @@ function isBunxAvailable() {
65021
65100
  }
65022
65101
  function getClaudeCodeVersion() {
65023
65102
  try {
65024
- const output = execSync6("claude --version", { encoding: "utf-8", stdio: ["pipe", "pipe", "ignore"], timeout: 5000 }).trim();
65103
+ const output = execSync5("claude --version", { encoding: "utf-8", stdio: ["pipe", "pipe", "ignore"], timeout: 5000 }).trim();
65025
65104
  const match = /^(\d+\.\d+\.\d+)/.exec(output);
65026
65105
  return match?.[1] ?? null;
65027
65106
  } catch {
@@ -65832,7 +65911,7 @@ function openExternalUrl(url2) {
65832
65911
  }
65833
65912
 
65834
65913
  // src/utils/powerline.ts
65835
- import { execSync as execSync7 } from "child_process";
65914
+ import { execSync as execSync6 } from "child_process";
65836
65915
  import * as fs12 from "fs";
65837
65916
  import * as os11 from "os";
65838
65917
  import * as path10 from "path";
@@ -65966,7 +66045,7 @@ async function installPowerlineFonts() {
65966
66045
  if (fs12.existsSync(tempDir)) {
65967
66046
  fs12.rmSync(tempDir, { recursive: true, force: true });
65968
66047
  }
65969
- execSync7(`git clone --depth=1 https://github.com/powerline/fonts.git "${tempDir}"`, {
66048
+ execSync6(`git clone --depth=1 https://github.com/powerline/fonts.git "${tempDir}"`, {
65970
66049
  stdio: "pipe",
65971
66050
  encoding: "utf8"
65972
66051
  });
@@ -65974,14 +66053,14 @@ async function installPowerlineFonts() {
65974
66053
  const installScript = path10.join(tempDir, "install.sh");
65975
66054
  if (fs12.existsSync(installScript)) {
65976
66055
  fs12.chmodSync(installScript, 493);
65977
- execSync7(`cd "${tempDir}" && ./install.sh`, {
66056
+ execSync6(`cd "${tempDir}" && ./install.sh`, {
65978
66057
  stdio: "pipe",
65979
66058
  encoding: "utf8",
65980
66059
  shell: "/bin/bash"
65981
66060
  });
65982
66061
  if (platform4 === "linux") {
65983
66062
  try {
65984
- execSync7("fc-cache -f -v", {
66063
+ execSync6("fc-cache -f -v", {
65985
66064
  stdio: "pipe",
65986
66065
  encoding: "utf8"
65987
66066
  });
@@ -71250,56 +71329,7 @@ var StatusJSONSchema = exports_external.looseObject({
71250
71329
  // src/ccstatusline.ts
71251
71330
  init_ansi();
71252
71331
  init_colors();
71253
- init_config();
71254
- init_jsonl();
71255
- await init_renderer2();
71256
71332
 
71257
- // src/utils/skills.ts
71258
- import * as fs13 from "fs";
71259
- import * as os13 from "os";
71260
- import * as path11 from "path";
71261
- var EMPTY = { totalInvocations: 0, uniqueSkills: [], lastSkill: null };
71262
- function getSkillsDir() {
71263
- return path11.join(os13.homedir(), ".cache", "ccstatusline", "skills");
71264
- }
71265
- function getSkillsFilePath(sessionId) {
71266
- return path11.join(getSkillsDir(), `skills-${sessionId}.jsonl`);
71267
- }
71268
- function getSkillsMetrics(sessionId) {
71269
- const filePath = getSkillsFilePath(sessionId);
71270
- if (!fs13.existsSync(filePath)) {
71271
- return EMPTY;
71272
- }
71273
- try {
71274
- const invocations = fs13.readFileSync(filePath, "utf-8").trim().split(`
71275
- `).filter((line) => line.trim()).map((line) => {
71276
- try {
71277
- return JSON.parse(line);
71278
- } catch {
71279
- return null;
71280
- }
71281
- }).filter((e) => e !== null && typeof e.skill === "string" && typeof e.session_id === "string");
71282
- if (invocations.length === 0) {
71283
- return EMPTY;
71284
- }
71285
- const uniqueSkills = [];
71286
- const seenSkills = new Set;
71287
- for (let i = invocations.length - 1;i >= 0; i--) {
71288
- const skill = invocations[i]?.skill;
71289
- if (skill && !seenSkills.has(skill)) {
71290
- seenSkills.add(skill);
71291
- uniqueSkills.push(skill);
71292
- }
71293
- }
71294
- return {
71295
- totalInvocations: invocations.length,
71296
- uniqueSkills,
71297
- lastSkill: invocations[invocations.length - 1]?.skill ?? null
71298
- };
71299
- } catch {
71300
- return EMPTY;
71301
- }
71302
- }
71303
71333
  // src/utils/compact-renderer.ts
71304
71334
  init_source();
71305
71335
  init_ColorLevel();
@@ -71361,11 +71391,63 @@ function renderCompactOutput(preRenderedLines, settings, maxWidth) {
71361
71391
  }
71362
71392
  }
71363
71393
 
71394
+ // src/ccstatusline.ts
71395
+ init_config();
71396
+ init_jsonl();
71397
+ await init_renderer2();
71398
+
71399
+ // src/utils/skills.ts
71400
+ import * as fs13 from "fs";
71401
+ import * as os13 from "os";
71402
+ import * as path11 from "path";
71403
+ var EMPTY = { totalInvocations: 0, uniqueSkills: [], lastSkill: null };
71404
+ function getSkillsDir() {
71405
+ return path11.join(os13.homedir(), ".cache", "ccstatusline", "skills");
71406
+ }
71407
+ function getSkillsFilePath(sessionId) {
71408
+ return path11.join(getSkillsDir(), `skills-${sessionId}.jsonl`);
71409
+ }
71410
+ function getSkillsMetrics(sessionId) {
71411
+ const filePath = getSkillsFilePath(sessionId);
71412
+ if (!fs13.existsSync(filePath)) {
71413
+ return EMPTY;
71414
+ }
71415
+ try {
71416
+ const invocations = fs13.readFileSync(filePath, "utf-8").trim().split(`
71417
+ `).filter((line) => line.trim()).map((line) => {
71418
+ try {
71419
+ return JSON.parse(line);
71420
+ } catch {
71421
+ return null;
71422
+ }
71423
+ }).filter((e) => e !== null && typeof e.skill === "string" && typeof e.session_id === "string");
71424
+ if (invocations.length === 0) {
71425
+ return EMPTY;
71426
+ }
71427
+ const uniqueSkills = [];
71428
+ const seenSkills = new Set;
71429
+ for (let i = invocations.length - 1;i >= 0; i--) {
71430
+ const skill = invocations[i]?.skill;
71431
+ if (skill && !seenSkills.has(skill)) {
71432
+ seenSkills.add(skill);
71433
+ uniqueSkills.push(skill);
71434
+ }
71435
+ }
71436
+ return {
71437
+ totalInvocations: invocations.length,
71438
+ uniqueSkills,
71439
+ lastSkill: invocations[invocations.length - 1]?.skill ?? null
71440
+ };
71441
+ } catch {
71442
+ return EMPTY;
71443
+ }
71444
+ }
71445
+
71364
71446
  // src/ccstatusline.ts
71365
71447
  init_terminal();
71366
71448
 
71367
71449
  // src/utils/usage-prefetch.ts
71368
- init_usage();
71450
+ init_resolver();
71369
71451
  var USAGE_WIDGET_TYPES = new Set([
71370
71452
  "session-usage",
71371
71453
  "weekly-usage",
@@ -71406,17 +71488,20 @@ async function prefetchUsageDataIfNeeded(lines, data) {
71406
71488
  if (!hasUsageDependentWidgets(lines)) {
71407
71489
  return null;
71408
71490
  }
71491
+ const model = data?.model;
71492
+ const modelId = (typeof model === "string" ? model : model?.id) ?? "";
71493
+ const provider = resolveProvider(modelId);
71409
71494
  const rateLimitsData = extractUsageDataFromRateLimits(data?.rate_limits);
71410
71495
  if (hasCompleteRateLimitsUsageData(rateLimitsData)) {
71411
71496
  if (hasExtraUsageDependentWidgets(lines)) {
71412
- const apiData = await fetchUsageData();
71497
+ const apiData = await provider.fetchUsage();
71413
71498
  if (apiData.error === undefined) {
71414
71499
  return { ...rateLimitsData, extraUsageEnabled: apiData.extraUsageEnabled, extraUsageLimit: apiData.extraUsageLimit, extraUsageUsed: apiData.extraUsageUsed, extraUsageUtilization: apiData.extraUsageUtilization };
71415
71500
  }
71416
71501
  }
71417
71502
  return rateLimitsData;
71418
71503
  }
71419
- return fetchUsageData();
71504
+ return provider.fetchUsage();
71420
71505
  }
71421
71506
 
71422
71507
  // src/ccstatusline.ts
@@ -71460,8 +71545,8 @@ async function ensureWindowsUtf8CodePage() {
71460
71545
  return;
71461
71546
  }
71462
71547
  try {
71463
- const { execFileSync: execFileSync3 } = await import("child_process");
71464
- execFileSync3("chcp.com", ["65001"], { stdio: "ignore" });
71548
+ const { execFileSync: execFileSync4 } = await import("child_process");
71549
+ execFileSync4("chcp.com", ["65001"], { stdio: "ignore" });
71465
71550
  } catch {}
71466
71551
  }
71467
71552
  async function renderMultipleLines(data) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ccstatusline-usage",
3
- "version": "2.3.17",
3
+ "version": "2.4.0",
4
4
  "description": "A customizable status line formatter for Claude Code CLI",
5
5
  "module": "src/ccstatusline.ts",
6
6
  "type": "module",
@@ -30,8 +30,8 @@
30
30
  "chalk": "^5.5.0",
31
31
  "eslint": "^10.0.0",
32
32
  "eslint-import-resolver-typescript": "^4.4.4",
33
- "eslint-plugin-import": "^2.32.0",
34
33
  "eslint-plugin-import-newlines": "^2.0.0",
34
+ "eslint-plugin-import-x": "^4.16.2",
35
35
  "eslint-plugin-react": "^7.37.5",
36
36
  "eslint-plugin-react-hooks": "^7.0.1",
37
37
  "globals": "^17.3.0",