coding-usage-bar 0.5.1 → 0.6.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.
package/README.md CHANGED
@@ -114,6 +114,8 @@ Coding Usage Bar calls the Zhipu AI quota API (`GET /api/monitor/usage/quota/lim
114
114
 
115
115
  Get your API key from the [Zhipu AI console](https://open.bigmodel.cn). Without this key, GLM monitoring will report `GLM_API_KEY_MISSING`.
116
116
 
117
+ Zhipu meters newer Coding Plans in credits (积分) instead of raw tokens and reports them as `CREDIT_LIMIT` windows. Coding Usage Bar keys off the response shape, records `quotaUnit` as `credits` or `tokens` in `status.json`, and labels the dropdown with `metered in credits` so the 5h/7d percentages read correctly for either plan generation. This detection has not been verified against a real V3 (points-based) account yet; it is based on the `CREDIT_LIMIT` shape observed on existing plans. If you are on a V3 plan, a redacted `coding-usage-bar status --refresh` output in an issue would confirm it.
118
+
117
119
  ## DeepSeek
118
120
 
119
121
  Coding Usage Bar calls `GET https://api.deepseek.com/user/balance` to read your account balance. DeepSeek exposes balance rather than 5h/7d usage windows, so the menu bar shows `Available` or `Depleted` plus the currency amount. Set `deepseek.apiKey` in `~/.coding-usage-bar/config.json`:
@@ -129,6 +131,17 @@ Coding Usage Bar calls `GET https://api.deepseek.com/user/balance` to read your
129
131
 
130
132
  Without this key, DeepSeek monitoring will report `DEEPSEEK_API_KEY_MISSING`.
131
133
 
134
+ ## Peak and off-peak pricing
135
+
136
+ GLM Coding Plan and the DeepSeek API both publish a clock-based discount, and both state it in Beijing time. Coding Usage Bar computes the current tier on the producer side, stores it in `status.json` as `pricing`, and shows it in the CLI table (`price` row), the dropdown, and the usage card, together with the time of the next flip:
137
+
138
+ | Provider | Peak (Asia/Shanghai) | Off-peak | Holidays |
139
+ |---|---|---|---|
140
+ | DeepSeek API | Mon–Fri 09:00–12:00 and 14:00–18:00 | 50% of the peak rate | Chinese public holidays are off-peak all day |
141
+ | GLM Coding Plan | Mon–Fri 14:00–18:00 | credits deducted at 50% | Not excluded by the published rule |
142
+
143
+ Sources: [DeepSeek pricing](https://api-docs.deepseek.com/quick_start/pricing) and the [GLM Coding Plan overview](https://docs.bigmodel.cn/cn/coding-plan/overview). The 2026 holiday table follows the State Council notice (国办发明电〔2025〕7号); adjusted working weekends stay off-peak because both vendors phrase the rule as Monday to Friday. Time-boxed promotions are not modelled. The tier only tells you what the provider charges right now; it does not change the burn analysis.
144
+
132
145
  ## MiniMax (M3)
133
146
 
134
147
  Coding Usage Bar calls `GET https://api.minimaxi.com/v1/token_plan/remains` to read 5h and 7d usage windows. MiniMax returns two signals per model: a count-based ratio (`current_*_usage_count` / `current_*_total_count`) for tiered models like `video`, and a credit-based `current_*_remaining_percent` (0-100) for the `general` model where the count stays at 0. Coding Usage Bar prefers the count ratio when total > 0 and otherwise derives used% as `100 - remaining_percent`. Set `minimax.apiKey` and (optionally) `minimax.region` in `~/.coding-usage-bar/config.json`:
package/README.zh-CN.md CHANGED
@@ -50,6 +50,17 @@ coding-usage-bar status
50
50
  - Claude Code、Codex 本地优先,不依赖额外遥测服务
51
51
  - 展示层只读稳定的 `status.json`,不会在打开菜单时临时访问数据源
52
52
  - 接近限额或燃烧节奏异常时发送系统通知
53
+ - 国产套餐规则内置:GLM Coding Plan 与 DeepSeek 的峰谷时段按北京时间计算,法定节假日按国办通知处理,CLI、下拉菜单和卡片都会显示当前是高峰还是谷时、几折、几点切换
54
+ - GLM 新版 Coding Plan 按积分(`CREDIT_LIMIT`)计量时自动识别,标注"按积分计量",5h/7d 百分比不会因为套餐换代而失真。该识别基于现有套餐观测到的 `CREDIT_LIMIT` 形状,尚未用真实 V3 积分制账号实测;V3 用户可在 issue 里贴脱敏的 `coding-usage-bar status --refresh` 输出帮助确认
55
+
56
+ ## 峰谷时段
57
+
58
+ | Provider | 高峰(北京时间) | 谷时 | 法定节假日 |
59
+ |---|---|---|---|
60
+ | DeepSeek API | 周一至周五 09:00–12:00、14:00–18:00 | 高峰价的 50% | 全天谷时 |
61
+ | GLM Coding Plan | 周一至周五 14:00–18:00 | 积分按 50% 抵扣 | 官方规则未排除,按工作日字面处理 |
62
+
63
+ 依据 [DeepSeek 定价页](https://api-docs.deepseek.com/quick_start/pricing)与 [GLM Coding Plan 概览](https://docs.bigmodel.cn/cn/coding-plan/overview)。2026 年节假日表来自国办发明电〔2025〕7号;调休上班的周末仍按周末处理,因为两家都把规则写成"周一至周五"。限时活动(如双节全天谷时)不建模。峰谷只说明厂商此刻怎么计费,不改变燃烧节奏分析。
53
64
 
54
65
  完整命令、Provider 配置和实现边界请查看[英文 README](README.md)。
55
66
 
package/dist/deepseek.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { deepseekPricing } from "./pricing.js";
1
2
  const DEFAULT_BASE_URL = "https://api.deepseek.com";
2
3
  export async function fetchDeepseekBalance(config) {
3
4
  const url = `${DEFAULT_BASE_URL}/user/balance`;
@@ -19,11 +20,13 @@ export function usageFromDeepseekBalance(balance, options) {
19
20
  if (!info) {
20
21
  return null;
21
22
  }
23
+ const observedAt = options.observedAt ?? new Date().toISOString();
22
24
  return {
23
25
  provider: "deepseek",
24
26
  source: options.source,
25
- observedAt: options.observedAt ?? new Date().toISOString(),
27
+ observedAt,
26
28
  planType: null,
29
+ pricing: deepseekPricing(new Date(observedAt)),
27
30
  windows: [],
28
31
  balance: {
29
32
  total: info.total_balance,
package/dist/format.js CHANGED
@@ -62,10 +62,24 @@ export function usageBar(usedPercent, width = 12) {
62
62
  const usedCells = Math.max(0, Math.min(width, Math.round((usedPercent / 100) * width)));
63
63
  return `[${"#".repeat(usedCells)}${"-".repeat(width - usedCells)}] ${Math.round(usedPercent)}%`;
64
64
  }
65
+ function pricingRow(usage) {
66
+ if (!usage.pricing) {
67
+ return null;
68
+ }
69
+ const tier = usage.pricing.tier === "off_peak" ? `off-peak ${usage.pricing.discountPercent}% off` : "peak, full price";
70
+ return [
71
+ providerLabel(usage.provider).padEnd(8),
72
+ "price".padEnd(8),
73
+ tier.padEnd(19),
74
+ formatDurationUntil(usage.pricing.until).padEnd(10),
75
+ usage.quotaUnit === "credits" ? "credits" : "",
76
+ ].join("");
77
+ }
65
78
  export function formatStatusRows(usages, analyses) {
66
79
  const rows = ["Provider Period Usage Reset State"];
67
80
  for (const usage of usages) {
68
81
  const analysis = analyses.find((item) => item.provider === usage.provider);
82
+ const price = pricingRow(usage);
69
83
  if (usage.balance && usage.windows.length === 0) {
70
84
  const currency = usage.balance.currency === "CNY" ? "¥" : "$";
71
85
  rows.push([
@@ -75,6 +89,8 @@ export function formatStatusRows(usages, analyses) {
75
89
  "".padEnd(10),
76
90
  analysis?.state ?? "RAW",
77
91
  ].join(""));
92
+ if (price)
93
+ rows.push(price);
78
94
  continue;
79
95
  }
80
96
  for (const window of usage.windows) {
@@ -86,6 +102,8 @@ export function formatStatusRows(usages, analyses) {
86
102
  analysis?.state ?? "RAW",
87
103
  ].join(""));
88
104
  }
105
+ if (price)
106
+ rows.push(price);
89
107
  }
90
108
  return rows.join("\n");
91
109
  }
package/dist/glm.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { makeProviderUsage, normalizeWindow } from "./usage.js";
2
+ import { glmPricing } from "./pricing.js";
2
3
  const DEFAULT_BASE_URL = "https://open.bigmodel.cn";
3
4
  export async function fetchGlmQuota(config) {
4
5
  const baseUrl = config.baseUrl || DEFAULT_BASE_URL;
@@ -50,7 +51,7 @@ function pickTokenWindows(limits) {
50
51
  for (const type of ["TOKENS_LIMIT", "CREDIT_LIMIT"]) {
51
52
  const picked = pickTokenWindowsOfType(limits.filter((limit) => limit.type === type));
52
53
  if (picked) {
53
- return picked;
54
+ return { ...picked, unit: type === "CREDIT_LIMIT" ? "credits" : "tokens" };
54
55
  }
55
56
  }
56
57
  return null;
@@ -77,7 +78,7 @@ export function usageFromGlmQuota(quota, options) {
77
78
  if (!fiveHour || !sevenDay) {
78
79
  return null;
79
80
  }
80
- return makeProviderUsage({
81
+ const usage = makeProviderUsage({
81
82
  provider: "glm",
82
83
  source: options.source,
83
84
  observedAt: options.observedAt,
@@ -85,6 +86,11 @@ export function usageFromGlmQuota(quota, options) {
85
86
  fiveHour,
86
87
  sevenDay,
87
88
  });
89
+ return {
90
+ ...usage,
91
+ quotaUnit: picked.unit,
92
+ pricing: glmPricing(new Date(usage.observedAt)),
93
+ };
88
94
  }
89
95
  export async function collectGlmUsage(config) {
90
96
  if (!config.apiKey) {
package/dist/menubar.d.ts CHANGED
@@ -5,6 +5,7 @@ export type TitleMode = "auto" | TitleTier;
5
5
  export declare function readTitleMode(paths?: RuntimePaths): TitleMode;
6
6
  export declare function writeTitleMode(mode: TitleMode, paths?: RuntimePaths): TitleMode;
7
7
  export declare function cycleTitleMode(paths?: RuntimePaths): TitleMode;
8
+ export declare function resolveTitleTier(mode: TitleMode, display: StatusSnapshot["display"], providerCount: number): TitleTier;
8
9
  export declare function pickTitleTier(display: StatusSnapshot["display"], providerCount: number): TitleTier;
9
10
  export declare function swiftBarPluginPath(paths?: RuntimePaths): {
10
11
  dir: string;
package/dist/menubar.js CHANGED
@@ -4,6 +4,7 @@ import { execFileSync } from "node:child_process";
4
4
  import { fileURLToPath } from "node:url";
5
5
  import { ensureDir, isFile } from "./fs-util.js";
6
6
  import { formatProviderLabel, formatResetTime } from "./format.js";
7
+ import { pricingLabel } from "./pricing.js";
7
8
  import { buildPaths } from "./paths.js";
8
9
  import { stableNodeExecutable } from "./node-runtime.js";
9
10
  import { loadDisplayStatusSnapshot } from "./runtime.js";
@@ -170,6 +171,20 @@ export function cycleTitleMode(paths = buildPaths()) {
170
171
  const next = TITLE_MODE_CYCLE[(TITLE_MODE_CYCLE.indexOf(current) + 1) % TITLE_MODE_CYCLE.length];
171
172
  return writeTitleMode(next, paths);
172
173
  }
174
+ const TITLE_TIER_WIDTH_RANK = { icon: 0, single: 1, full: 2 };
175
+ // A manual mode may narrow the title but never widen it past a measured budget:
176
+ // an overflowing item is pushed under the notch, and the Title row that would
177
+ // undo the choice lives in that item's own dropdown, so the user is locked out.
178
+ // With no measurement the manual choice stands, since autoTier is only a guess.
179
+ export function resolveTitleTier(mode, display, providerCount) {
180
+ const autoTier = pickTitleTier(display, providerCount);
181
+ if (mode === "auto") {
182
+ return autoTier;
183
+ }
184
+ const budget = display?.extrasBudgetPt;
185
+ const measured = typeof budget === "number" && Number.isFinite(budget);
186
+ return measured && TITLE_TIER_WIDTH_RANK[mode] > TITLE_TIER_WIDTH_RANK[autoTier] ? autoTier : mode;
187
+ }
173
188
  export function pickTitleTier(display, providerCount) {
174
189
  if (providerCount === 0) {
175
190
  return "icon";
@@ -661,10 +676,13 @@ function titleLine(tier, providers, title, topState, prefersDark) {
661
676
  ? line(text, { ...titleIconParams(), dropdown: false })
662
677
  : iconLine();
663
678
  }
664
- function titleModeLabel(mode, autoTier) {
665
- return mode === "auto"
666
- ? `Title Auto (${TITLE_TIER_LABEL[autoTier]})`
667
- : `Title ${TITLE_TIER_LABEL[mode]}`;
679
+ function titleModeLabel(mode, autoTier, tier) {
680
+ if (mode === "auto") {
681
+ return `Title Auto (${TITLE_TIER_LABEL[autoTier]})`;
682
+ }
683
+ return mode === tier
684
+ ? `Title ${TITLE_TIER_LABEL[mode]}`
685
+ : `Title ${TITLE_TIER_LABEL[mode]} (no room, ${TITLE_TIER_LABEL[tier]})`;
668
686
  }
669
687
  // Carries the "why" behind a degraded title without spending a menu row on it.
670
688
  function titleModeTooltip(display, autoTier) {
@@ -768,7 +786,9 @@ function cardProviderBlock(item, now) {
768
786
  darkColor,
769
787
  headline: `${currency}${item.usage.balance.total}`,
770
788
  rows: [],
771
- note: "Pay-as-you-go · no windowed quota",
789
+ note: item.usage.pricing
790
+ ? `Pay-as-you-go · ${pricingLabel(item.usage.pricing, formatResetTime(item.usage.pricing.until, now))}`
791
+ : "Pay-as-you-go · no windowed quota",
772
792
  message: null,
773
793
  updated: observedUpdatedLabel(item, now),
774
794
  };
@@ -792,7 +812,9 @@ function cardProviderBlock(item, now) {
792
812
  darkColor,
793
813
  headline: headlinePct ? `${Math.round(headlinePct.usedPercent)}%` : STATE_LABEL[item.analysis.state],
794
814
  rows,
795
- note: null,
815
+ note: item.usage.pricing
816
+ ? `${pricingLabel(item.usage.pricing, formatResetTime(item.usage.pricing.until, now))}${item.usage.quotaUnit === "credits" ? " · credits" : ""}`
817
+ : null,
796
818
  message: shortCardMessage(item),
797
819
  updated: observedUpdatedLabel(item, now),
798
820
  };
@@ -834,8 +856,8 @@ export function renderMenuBar(snapshot = loadDisplayStatusSnapshot(), paths = bu
834
856
  const providers = titleProviders(snapshot);
835
857
  const title = providers.map(titleSegment).join(` ${TITLE_SEPARATOR} `);
836
858
  const autoTier = pickTitleTier(snapshot.display, providers.length);
837
- const tier = mode === "auto" ? autoTier : mode;
838
- const modeLine = line(titleModeLabel(mode, autoTier), {
859
+ const tier = resolveTitleTier(mode, snapshot.display, providers.length);
860
+ const modeLine = line(titleModeLabel(mode, autoTier, tier), {
839
861
  bash: stableNodeExecutable(),
840
862
  param1: appCliPath(paths),
841
863
  param2: "menubar",
@@ -901,6 +923,9 @@ export function renderMenuBar(snapshot = loadDisplayStatusSnapshot(), paths = bu
901
923
  font: ROW_FONT,
902
924
  size: 12,
903
925
  }));
926
+ if (item.usage.pricing) {
927
+ lines.push(muted(pricingLabel(item.usage.pricing, formatResetTime(item.usage.pricing.until, now))));
928
+ }
904
929
  lines.push(line(item.analysis.message, { color: MUTED_COLOR, size: 12, length: 84 }));
905
930
  lines.push("---");
906
931
  continue;
@@ -920,6 +945,10 @@ export function renderMenuBar(snapshot = loadDisplayStatusSnapshot(), paths = bu
920
945
  if (item.usage.blocked) {
921
946
  lines.push(muted(`Blocked ${item.usage.blocked.reason}. 5h/7d numbers above still reflect the window quotas.`));
922
947
  }
948
+ if (item.usage.pricing) {
949
+ const unit = item.usage.quotaUnit === "credits" ? " · metered in credits" : "";
950
+ lines.push(muted(`${pricingLabel(item.usage.pricing, formatResetTime(item.usage.pricing.until, now))}${unit}`));
951
+ }
923
952
  lines.push(muted(targetLabel(item)));
924
953
  lines.push(line(item.analysis.message, { color: MUTED_COLOR, size: 12, length: 84 }));
925
954
  lines.push("---");
@@ -0,0 +1,5 @@
1
+ import { PricingInfo } from "./types.js";
2
+ export declare function isCnPublicHoliday(date: Date): boolean;
3
+ export declare function deepseekPricing(now?: Date): PricingInfo;
4
+ export declare function glmPricing(now?: Date): PricingInfo;
5
+ export declare function pricingLabel(pricing: PricingInfo, until: string): string;
@@ -0,0 +1,115 @@
1
+ // Peak / off-peak pricing tiers for the two providers that publish a clock-based
2
+ // discount. Both rules are stated by the vendor in Beijing time (UTC+8, no DST),
3
+ // so the arithmetic below deliberately avoids the host time zone.
4
+ //
5
+ // DeepSeek (api-docs.deepseek.com/quick_start/pricing): peak is 01:00-04:00 and
6
+ // 06:00-10:00 UTC, Monday through Friday, excluding Chinese public holidays;
7
+ // every other hour is off-peak at 50% of the peak rate. In Beijing time that is
8
+ // 09:00-12:00 and 14:00-18:00.
9
+ //
10
+ // GLM Coding Plan V3 (docs.bigmodel.cn/cn/coding-plan/overview): 高峰时段 is
11
+ // 每周一至周五的 14:00~18:00 (UTC+8) at the base credit deduction; all other
12
+ // hours deduct 50% of the base credits. The page does not exclude public
13
+ // holidays, and time-boxed promotions (e.g. all-day off-peak during a holiday
14
+ // campaign) are not modelled here: the tier follows the standing rule only.
15
+ const BEIJING_OFFSET_MINUTES = 8 * 60;
16
+ const MINUTES_PER_DAY = 24 * 60;
17
+ const LOOKAHEAD_DAYS = 40;
18
+ // Rest days from 国务院办公厅关于2026年部分节假日安排的通知 (国办发明电〔2025〕7号,
19
+ // 2025-11-04). Adjusted working weekends (调休上班) are intentionally not
20
+ // promoted to weekdays: both vendors phrase the peak rule as Monday-Friday.
21
+ const CN_PUBLIC_HOLIDAYS = {
22
+ 2026: new Set([
23
+ "01-01", "01-02", "01-03",
24
+ "02-15", "02-16", "02-17", "02-18", "02-19", "02-20", "02-21", "02-22", "02-23",
25
+ "04-04", "04-05", "04-06",
26
+ "05-01", "05-02", "05-03", "05-04", "05-05",
27
+ "06-19", "06-20", "06-21",
28
+ "09-25", "09-26", "09-27",
29
+ "10-01", "10-02", "10-03", "10-04", "10-05", "10-06", "10-07",
30
+ ]),
31
+ };
32
+ const DEEPSEEK_RULE = {
33
+ peakSegments: [[9 * 60, 12 * 60], [14 * 60, 18 * 60]],
34
+ excludePublicHolidays: true,
35
+ offPeakDiscountPercent: 50,
36
+ rule: "DeepSeek API: peak Mon-Fri 09:00-12:00 & 14:00-18:00 Asia/Shanghai excluding CN public holidays; off-peak 50% off",
37
+ };
38
+ const GLM_RULE = {
39
+ peakSegments: [[14 * 60, 18 * 60]],
40
+ excludePublicHolidays: false,
41
+ offPeakDiscountPercent: 50,
42
+ rule: "GLM Coding Plan: peak Mon-Fri 14:00-18:00 Asia/Shanghai; off-peak credits deducted at 50%",
43
+ };
44
+ function beijingParts(date) {
45
+ const shifted = new Date(date.getTime() + BEIJING_OFFSET_MINUTES * 60_000);
46
+ return {
47
+ year: shifted.getUTCFullYear(),
48
+ month: shifted.getUTCMonth() + 1,
49
+ day: shifted.getUTCDate(),
50
+ weekday: shifted.getUTCDay(),
51
+ minutes: shifted.getUTCHours() * 60 + shifted.getUTCMinutes(),
52
+ };
53
+ }
54
+ function beijingInstant(year, month, day, minutes) {
55
+ return new Date(Date.UTC(year, month - 1, day, 0, minutes - BEIJING_OFFSET_MINUTES));
56
+ }
57
+ export function isCnPublicHoliday(date) {
58
+ const parts = beijingParts(date);
59
+ const key = `${String(parts.month).padStart(2, "0")}-${String(parts.day).padStart(2, "0")}`;
60
+ return CN_PUBLIC_HOLIDAYS[parts.year]?.has(key) ?? false;
61
+ }
62
+ function tierAt(rule, date) {
63
+ const parts = beijingParts(date);
64
+ if (parts.weekday === 0 || parts.weekday === 6) {
65
+ return "off_peak";
66
+ }
67
+ if (rule.excludePublicHolidays && isCnPublicHoliday(date)) {
68
+ return "off_peak";
69
+ }
70
+ const inPeak = rule.peakSegments.some(([start, end]) => parts.minutes >= start && parts.minutes < end);
71
+ return inPeak ? "peak" : "off_peak";
72
+ }
73
+ // The tier can only flip at a segment edge or at midnight, so walking those
74
+ // instants day by day finds the next change without minute-level scanning.
75
+ function nextTierChange(rule, date) {
76
+ const current = tierAt(rule, date);
77
+ const start = beijingParts(date);
78
+ const edges = Array.from(new Set([0, ...rule.peakSegments.flatMap(([a, b]) => [a, b])]))
79
+ .filter((m) => m < MINUTES_PER_DAY)
80
+ .sort((a, b) => a - b);
81
+ for (let offset = 0; offset <= LOOKAHEAD_DAYS; offset += 1) {
82
+ for (const edge of edges) {
83
+ const candidate = beijingInstant(start.year, start.month, start.day + offset, edge);
84
+ if (candidate.getTime() <= date.getTime()) {
85
+ continue;
86
+ }
87
+ if (tierAt(rule, candidate) !== current) {
88
+ return candidate;
89
+ }
90
+ }
91
+ }
92
+ // Unreachable with the rules above (every week has weekdays); keep a sane bound.
93
+ return beijingInstant(start.year, start.month, start.day + LOOKAHEAD_DAYS, 0);
94
+ }
95
+ function pricingFor(rule, date) {
96
+ const tier = tierAt(rule, date);
97
+ return {
98
+ tier,
99
+ discountPercent: tier === "off_peak" ? rule.offPeakDiscountPercent : 0,
100
+ until: nextTierChange(rule, date).toISOString(),
101
+ timezone: "Asia/Shanghai",
102
+ rule: rule.rule,
103
+ };
104
+ }
105
+ export function deepseekPricing(now = new Date()) {
106
+ return pricingFor(DEEPSEEK_RULE, now);
107
+ }
108
+ export function glmPricing(now = new Date()) {
109
+ return pricingFor(GLM_RULE, now);
110
+ }
111
+ export function pricingLabel(pricing, until) {
112
+ return pricing.tier === "off_peak"
113
+ ? `Off-peak · ${pricing.discountPercent}% off until ${until}`
114
+ : `Peak · full price until ${until}`;
115
+ }
package/dist/types.d.ts CHANGED
@@ -8,11 +8,20 @@ export interface UsageWindow {
8
8
  usedPercent: number;
9
9
  resetsAt: string;
10
10
  }
11
+ export interface PricingInfo {
12
+ tier: "peak" | "off_peak";
13
+ discountPercent: number;
14
+ until: string;
15
+ timezone: string;
16
+ rule: string;
17
+ }
11
18
  export interface ProviderUsage {
12
19
  provider: ProviderId;
13
20
  source: string;
14
21
  observedAt: string;
15
22
  planType?: string | null;
23
+ quotaUnit?: "tokens" | "credits";
24
+ pricing?: PricingInfo;
16
25
  windows: UsageWindow[];
17
26
  balance?: {
18
27
  total: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "coding-usage-bar",
3
- "version": "0.5.1",
3
+ "version": "0.6.1",
4
4
  "description": "macOS menu bar usage monitor for Claude Code, Codex, GLM, DeepSeek, and MiniMax.",
5
5
  "author": "hanzhangzzz",
6
6
  "license": "MIT",