coding-usage-bar 0.1.0 → 0.2.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 +32 -8
- package/README.zh-CN.md +11 -3
- package/THIRD_PARTY_NOTICES.md +1 -0
- package/assets/provider-kimi.png +0 -0
- package/assets/screenshot-20260714-160548.png +0 -0
- package/assets/screenshot-20260720-153409.png +0 -0
- package/dist/burn.js +1 -1
- package/dist/claude.js +103 -3
- package/dist/config.js +8 -2
- package/dist/doctor.js +32 -0
- package/dist/format.d.ts +1 -1
- package/dist/format.js +2 -0
- package/dist/install.d.ts +1 -0
- package/dist/install.js +1 -1
- package/dist/kimi.d.ts +32 -0
- package/dist/kimi.js +148 -0
- package/dist/menubar.js +6 -1
- package/dist/notifier.js +1 -1
- package/dist/paths.js +3 -0
- package/dist/runtime.js +40 -1
- package/dist/types.d.ts +7 -1
- package/docs/assets/demo.gif +0 -0
- package/docs/assets/hero.png +0 -0
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -2,18 +2,26 @@
|
|
|
2
2
|
|
|
3
3
|
[English](README.md) · [简体中文](README.zh-CN.md)
|
|
4
4
|
|
|
5
|
-
**Claude Code and Codex usage monitoring in your macOS menu bar.** Track rolling plan limits across Claude Code, OpenAI Codex, GLM (Zhipu AI), DeepSeek, and
|
|
5
|
+
**Claude Code and Codex usage monitoring in your macOS menu bar.** Track rolling plan limits across Claude Code, OpenAI Codex, GLM (Zhipu AI), DeepSeek, MiniMax, and Kimi (Moonshot AI) before a coding session hits the wall.
|
|
6
6
|
|
|
7
7
|
[](https://www.npmjs.com/package/coding-usage-bar)
|
|
8
8
|
[](https://github.com/hanzhangzzz/coding-usage-bar/actions/workflows/test.yml)
|
|
9
9
|
[](LICENSE)
|
|
10
10
|
[](#requirements)
|
|
11
11
|
|
|
12
|
-

|
|
13
13
|
|
|
14
14
|
Coding Usage Bar does more than display quota percentages. It compares short rolling windows with the weekly budget and labels your pace as learning, under-burning, on track, over-burning, or close to a limit.
|
|
15
15
|
|
|
16
|
-
Claude Code and Codex data stays local and is read from files those tools already produce. Provider API keys for GLM, DeepSeek, and MiniMax are stored only in `~/.coding-usage-bar/config.json` and sent directly to their respective quota APIs.
|
|
16
|
+
Claude Code and Codex data stays local and is read from files those tools already produce. Provider API keys for GLM, DeepSeek, and MiniMax are stored only in `~/.coding-usage-bar/config.json` and sent directly to their respective quota APIs. Kimi reads its key from `~/.coding-usage-bar/config.json` or, when unset, from the kimi.com lane in `~/.config/claude-lanes/config.env`.
|
|
17
|
+
|
|
18
|
+
## Live SwiftBar Menu
|
|
19
|
+
|
|
20
|
+
The screenshot is captured from the live SwiftBar plugin. The animation reproduces opening and closing that menu without altering the captured usage data.
|
|
21
|
+
|
|
22
|
+
<p align="center">
|
|
23
|
+
<img src="docs/assets/demo.gif" alt="Opening the live Coding Usage Bar SwiftBar menu with Codex, Claude, GLM, DeepSeek, MiniMax, and Kimi usage" width="800">
|
|
24
|
+
</p>
|
|
17
25
|
|
|
18
26
|
## Quick Start
|
|
19
27
|
|
|
@@ -119,6 +127,21 @@ Coding Usage Bar calls `GET https://api.minimaxi.com/v1/token_plan/remains` to r
|
|
|
119
127
|
|
|
120
128
|
`region` defaults to `cn` (api.minimaxi.com); set it to `global` to use api.minimax.io. Without this key, MiniMax monitoring will report `MINIMAX_API_KEY_MISSING`.
|
|
121
129
|
|
|
130
|
+
## Kimi (Moonshot AI)
|
|
131
|
+
|
|
132
|
+
Coding Usage Bar calls `GET https://api.kimi.com/coding/v1/usages` to read 5h and 7d usage windows. The response carries the 300-minute rolling window in `limits[]` and the weekly window in the top-level `usage`, with `limit`/`used`/`remaining` as strings; Coding Usage Bar derives used% from `used / limit` (or from `used / (used + remaining)` when limit is 0). Set `kimi.apiKey` in `~/.coding-usage-bar/config.json`:
|
|
133
|
+
|
|
134
|
+
```json
|
|
135
|
+
{
|
|
136
|
+
"providers": ["codex", "claude", "glm", "minimax", "kimi"],
|
|
137
|
+
"kimi": {
|
|
138
|
+
"apiKey": "your-kimi-api-key"
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
If `kimi.apiKey` is empty, Coding Usage Bar falls back to the lane whose `CONFIG_<n>_BASE_URL` points at kimi.com in `~/.config/claude-lanes/config.env` and uses that lane's `CONFIG_<n>_AUTH_TOKEN` (and base URL). Without either source, Kimi monitoring will report `KIMI_API_KEY_MISSING`.
|
|
144
|
+
|
|
122
145
|
## Profiles
|
|
123
146
|
|
|
124
147
|
Set `CODING_USAGE_BAR_PROFILE=high` for the more aggressive profile. The default is `low`.
|
|
@@ -135,7 +158,7 @@ Both profiles are constrained by the weekly budget. When a short window is repor
|
|
|
135
158
|
|
|
136
159
|
```json
|
|
137
160
|
{
|
|
138
|
-
"providers": ["codex", "claude", "glm", "deepseek", "minimax"]
|
|
161
|
+
"providers": ["codex", "claude", "glm", "deepseek", "minimax", "kimi"]
|
|
139
162
|
}
|
|
140
163
|
```
|
|
141
164
|
|
|
@@ -153,7 +176,7 @@ coding-usage-bar menubar install
|
|
|
153
176
|
|
|
154
177
|
If SwiftBar is not installed, `coding-usage-bar doctor` will report it. If SwiftBar already has a custom plugin folder, Coding Usage Bar uses that folder.
|
|
155
178
|
|
|
156
|
-
The compact menu bar title and dropdown use recognizable Provider marks for Codex, Claude Code, Zhipu AI, DeepSeek, and
|
|
179
|
+
The compact menu bar title and dropdown use recognizable Provider marks for Codex, Claude Code, Zhipu AI, DeepSeek, MiniMax, and Kimi alongside the rolling-window data (DeepSeek shows balance instead of windows):
|
|
157
180
|
|
|
158
181
|
```text
|
|
159
182
|
{Codex icon} 5H:14%,7D:67% │ {Claude icon} 5H:24%,7D:74% │ {GLM icon} 5H:36%,7D:7%
|
|
@@ -166,13 +189,14 @@ SwiftBar only supports one bitmap image on a single stable title item, so Coding
|
|
|
166
189
|
| Path | Purpose |
|
|
167
190
|
|------|---------|
|
|
168
191
|
| `~/.coding-usage-bar/app/` | Stable runtime copy used by launchd, Claude ingest hints, and SwiftBar |
|
|
169
|
-
| `~/.coding-usage-bar/config.json` | Provider selection, default `["codex", "claude", "glm", "deepseek", "minimax"]` |
|
|
192
|
+
| `~/.coding-usage-bar/config.json` | Provider selection, default `["codex", "claude", "glm", "deepseek", "minimax", "kimi"]` |
|
|
170
193
|
| `~/.coding-usage-bar/status.json` | Stable display-layer entry point |
|
|
171
194
|
| `~/.coding-usage-bar/codex/latest.json` | Latest normalized Codex usage |
|
|
172
195
|
| `~/.coding-usage-bar/claude/latest.json` | Latest normalized Claude usage after status line ingest |
|
|
173
196
|
| `~/.coding-usage-bar/glm/latest.json` | Latest normalized GLM usage from Zhipu AI quota API |
|
|
174
197
|
| `~/.coding-usage-bar/deepseek/latest.json` | Latest normalized DeepSeek balance from DeepSeek API |
|
|
175
198
|
| `~/.coding-usage-bar/minimax/latest.json` | Latest normalized MiniMax 5h/7d usage from MiniMax API |
|
|
199
|
+
| `~/.coding-usage-bar/kimi/latest.json` | Latest normalized Kimi 5h/7d usage from Kimi usages API |
|
|
176
200
|
| `~/.local/bin/coding-usage-bar` | CLI shim pointing at the stable runtime |
|
|
177
201
|
| `~/Library/LaunchAgents/com.duying.coding-usage-bar.plist` | macOS launchd agent |
|
|
178
202
|
| SwiftBar `PluginDirectory` / `coding-usage-bar.1m.js` | Menu bar plugin wrapper |
|
|
@@ -191,7 +215,7 @@ See the [product and technical baseline](https://github.com/hanzhangzzz/coding-u
|
|
|
191
215
|
|
|
192
216
|
## Why Coding Usage Bar?
|
|
193
217
|
|
|
194
|
-
- **One menu bar for multiple coding providers:** Claude Code, Codex, GLM, DeepSeek, and
|
|
218
|
+
- **One menu bar for multiple coding providers:** Claude Code, Codex, GLM, DeepSeek, MiniMax, and Kimi.
|
|
195
219
|
- **Pacing, not just percentages:** short-window usage is evaluated against the weekly budget.
|
|
196
220
|
- **Local-first:** Claude Code and Codex usage is read from local tool output, without a separate account or telemetry service.
|
|
197
221
|
- **Visible but lightweight:** SwiftBar is only the host; collection and display state remain separated and auditable.
|
|
@@ -217,4 +241,4 @@ npm pack
|
|
|
217
241
|
|
|
218
242
|
[MIT](LICENSE)
|
|
219
243
|
|
|
220
|
-
Provider names and trademarks belong to their respective owners. Coding Usage Bar is an independent project and is not affiliated with or endorsed by Anthropic, OpenAI, Zhipu AI, DeepSeek, or
|
|
244
|
+
Provider names and trademarks belong to their respective owners. Coding Usage Bar is an independent project and is not affiliated with or endorsed by Anthropic, OpenAI, Zhipu AI, DeepSeek, MiniMax, or Moonshot AI. Provider marks are used only for identification, are excluded from the MIT License, and are documented in [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md).
|
package/README.zh-CN.md
CHANGED
|
@@ -6,9 +6,17 @@
|
|
|
6
6
|
|
|
7
7
|

|
|
8
8
|
|
|
9
|
-
Coding Usage Bar 同时支持 Claude Code、OpenAI Codex、GLM(智谱 AI)、DeepSeek 和
|
|
9
|
+
Coding Usage Bar 同时支持 Claude Code、OpenAI Codex、GLM(智谱 AI)、DeepSeek、MiniMax 和 Kimi(Moonshot AI)。它不只展示百分比,还会结合短周期和周额度判断当前节奏是偏慢、正常、偏快,还是已经接近限额。
|
|
10
10
|
|
|
11
|
-
Claude Code 与 Codex 的数据直接读取它们在本机产生的文件,不需要额外登录,也不会上传使用记录。GLM、DeepSeek 和 MiniMax 的 API Key 只保存在 `~/.coding-usage-bar/config.json`,并直接请求对应 Provider 的额度接口。
|
|
11
|
+
Claude Code 与 Codex 的数据直接读取它们在本机产生的文件,不需要额外登录,也不会上传使用记录。GLM、DeepSeek 和 MiniMax 的 API Key 只保存在 `~/.coding-usage-bar/config.json`,并直接请求对应 Provider 的额度接口。Kimi 的 Key 优先读 `~/.coding-usage-bar/config.json`,未配置时回退到 `~/.config/claude-lanes/config.env` 中指向 kimi.com 的 lane。
|
|
12
|
+
|
|
13
|
+
## 真实 SwiftBar 菜单
|
|
14
|
+
|
|
15
|
+
以下截图来自正在运行的 SwiftBar 插件;动画复现菜单的展开与收起,不修改截图中的用量数据。
|
|
16
|
+
|
|
17
|
+
<p align="center">
|
|
18
|
+
<img src="docs/assets/demo.gif" alt="打开 Coding Usage Bar 的真实 SwiftBar 菜单,查看 Codex、Claude、GLM、DeepSeek、MiniMax 和 Kimi 用量" width="800">
|
|
19
|
+
</p>
|
|
12
20
|
|
|
13
21
|
## 快速开始
|
|
14
22
|
|
|
@@ -50,4 +58,4 @@ npm pack
|
|
|
50
58
|
|
|
51
59
|
[MIT](LICENSE)
|
|
52
60
|
|
|
53
|
-
各 Provider 名称与商标归其权利人所有。本项目独立开发,与 Anthropic、OpenAI、智谱 AI、DeepSeek、MiniMax 无隶属或背书关系。Provider 标识仅用于识别,不适用项目 MIT License,详见 [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md)。
|
|
61
|
+
各 Provider 名称与商标归其权利人所有。本项目独立开发,与 Anthropic、OpenAI、智谱 AI、DeepSeek、MiniMax、Moonshot AI 无隶属或背书关系。Provider 标识仅用于识别,不适用项目 MIT License,详见 [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md)。
|
package/THIRD_PARTY_NOTICES.md
CHANGED
|
@@ -9,6 +9,7 @@ Coding Usage Bar includes the following provider marks solely to identify the se
|
|
|
9
9
|
| `assets/provider-glm.png`, `assets/provider-glm-dark.png` | Zhipu AI / GLM |
|
|
10
10
|
| `assets/provider-deepseek.png` | DeepSeek |
|
|
11
11
|
| `assets/provider-minimax.png` | MiniMax |
|
|
12
|
+
| `assets/provider-kimi.png` | Moonshot AI / Kimi |
|
|
12
13
|
| `assets/provider-marks.png` | Composite of the marks listed above |
|
|
13
14
|
|
|
14
15
|
These marks and their associated names are owned by their respective holders. They are **not** licensed under this repository's MIT License. Their inclusion identifies the Provider whose usage is displayed and does not imply affiliation, sponsorship, or endorsement. No rights to the marks are granted by this project. Use of a mark remains subject to its owner's applicable brand rules, including the [OpenAI Design Guidelines](https://openai.com/brand/).
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/dist/burn.js
CHANGED
|
@@ -56,7 +56,7 @@ export function estimateConversionRate(samples) {
|
|
|
56
56
|
return Number.isFinite(value) && value > 0 ? value : null;
|
|
57
57
|
}
|
|
58
58
|
function providerLabel(provider) {
|
|
59
|
-
return provider === "claude" ? "Claude" : provider === "glm" ? "GLM" : provider === "deepseek" ? "DeepSeek" : provider === "minimax" ? "MiniMax" : "Codex";
|
|
59
|
+
return provider === "claude" ? "Claude" : provider === "glm" ? "GLM" : provider === "deepseek" ? "DeepSeek" : provider === "minimax" ? "MiniMax" : provider === "kimi" ? "Kimi" : "Codex";
|
|
60
60
|
}
|
|
61
61
|
function messageForState(provider, state, fiveUsed, target) {
|
|
62
62
|
const label = providerLabel(provider);
|
package/dist/claude.js
CHANGED
|
@@ -114,22 +114,122 @@ function statusLineScriptCandidates(command) {
|
|
|
114
114
|
}
|
|
115
115
|
return [...new Set(candidates)];
|
|
116
116
|
}
|
|
117
|
+
function stripInlineShellComment(line) {
|
|
118
|
+
let quote = null;
|
|
119
|
+
let escaped = false;
|
|
120
|
+
for (let index = 0; index < line.length; index += 1) {
|
|
121
|
+
const char = line[index];
|
|
122
|
+
if (escaped) {
|
|
123
|
+
escaped = false;
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
if (char === "\\" && quote !== "'") {
|
|
127
|
+
escaped = true;
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
if ((char === "'" || char === "\"") && !quote) {
|
|
131
|
+
quote = char;
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
if (char === quote) {
|
|
135
|
+
quote = null;
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
if (char === "#" && !quote && (index === 0 || /\s/.test(line[index - 1]))) {
|
|
139
|
+
return line.slice(0, index);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return line;
|
|
143
|
+
}
|
|
144
|
+
function hasBalancedShellQuotes(line) {
|
|
145
|
+
let quote = null;
|
|
146
|
+
let escaped = false;
|
|
147
|
+
for (const char of line) {
|
|
148
|
+
if (escaped) {
|
|
149
|
+
escaped = false;
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
if (char === "\\" && quote !== "'") {
|
|
153
|
+
escaped = true;
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
if ((char === "'" || char === "\"") && !quote) {
|
|
157
|
+
quote = char;
|
|
158
|
+
}
|
|
159
|
+
else if (char === quote) {
|
|
160
|
+
quote = null;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return quote === null;
|
|
164
|
+
}
|
|
165
|
+
function executableShellLines(text) {
|
|
166
|
+
const result = [];
|
|
167
|
+
const heredocDelimiters = [];
|
|
168
|
+
for (const rawLine of text.replace(/\\\r?\n/g, " ").split(/\r?\n/)) {
|
|
169
|
+
if (heredocDelimiters.length > 0) {
|
|
170
|
+
if (rawLine.trim() === heredocDelimiters[0]) {
|
|
171
|
+
heredocDelimiters.shift();
|
|
172
|
+
}
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
const line = stripInlineShellComment(rawLine);
|
|
176
|
+
const heredocPattern = /<<-?\s*(?:'([^']+)'|"([^"]+)"|\\([^\s;|&()<>]+)|([^\s;|&()<>]+))/g;
|
|
177
|
+
const heredocs = [...line.matchAll(heredocPattern)];
|
|
178
|
+
if (heredocs.length > 0) {
|
|
179
|
+
for (const heredoc of heredocs) {
|
|
180
|
+
heredocDelimiters.push(heredoc[1] ?? heredoc[2] ?? heredoc[3] ?? heredoc[4]);
|
|
181
|
+
}
|
|
182
|
+
result.push(line.slice(0, heredocs[0].index));
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
result.push(line);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return result;
|
|
189
|
+
}
|
|
117
190
|
export function claudeStatusLineHasIngest(command, options) {
|
|
191
|
+
const isCliPath = (word) => (path.basename(word) === "coding-usage-bar"
|
|
192
|
+
|| word === options.appCliPath
|
|
193
|
+
|| word.endsWith("/.coding-usage-bar/app/dist/cli.js"));
|
|
194
|
+
const hasIngestCommand = (text) => {
|
|
195
|
+
const lines = executableShellLines(text);
|
|
196
|
+
if (lines.some((line) => !hasBalancedShellQuotes(line))) {
|
|
197
|
+
return false;
|
|
198
|
+
}
|
|
199
|
+
return lines.some((line) => {
|
|
200
|
+
if (!line.trim()) {
|
|
201
|
+
return false;
|
|
202
|
+
}
|
|
203
|
+
const words = splitShellWords(line);
|
|
204
|
+
const commandStarts = [0, ...words.flatMap((word, index) => word === "|" ? [index + 1] : [])];
|
|
205
|
+
return commandStarts.some((start) => {
|
|
206
|
+
const executable = words[start];
|
|
207
|
+
const executableIsNode = ["node", "nodejs"].includes(path.basename(executable ?? ""));
|
|
208
|
+
const cliIndex = executableIsNode ? start + 1 : start;
|
|
209
|
+
const finalArgument = words[cliIndex + 2]?.replace(/[;)]$/, "");
|
|
210
|
+
return isCliPath(words[cliIndex] ?? "")
|
|
211
|
+
&& words[cliIndex + 1] === "ingest"
|
|
212
|
+
&& finalArgument === "claude-statusline";
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
};
|
|
118
216
|
if (!command) {
|
|
119
217
|
return false;
|
|
120
218
|
}
|
|
121
|
-
if (command
|
|
122
|
-
|| command.includes(options.appCliPath)
|
|
219
|
+
if (hasIngestCommand(command)
|
|
123
220
|
|| command === options.managedScriptPath) {
|
|
124
221
|
return true;
|
|
125
222
|
}
|
|
126
223
|
for (const candidate of statusLineScriptCandidates(command)) {
|
|
127
224
|
try {
|
|
225
|
+
if (isCliPath(candidate)) {
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
128
228
|
if (!fs.statSync(candidate).isFile()) {
|
|
129
229
|
continue;
|
|
130
230
|
}
|
|
131
231
|
const script = fs.readFileSync(candidate, "utf8");
|
|
132
|
-
if (
|
|
232
|
+
if (hasIngestCommand(script)) {
|
|
133
233
|
return true;
|
|
134
234
|
}
|
|
135
235
|
}
|
package/dist/config.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { readJsonFile, writeJsonAtomic } from "./fs-util.js";
|
|
2
2
|
import { buildPaths } from "./paths.js";
|
|
3
|
-
const DEFAULT_PROVIDERS = ["codex", "claude", "glm", "deepseek", "minimax"];
|
|
4
|
-
const PROVIDERS = new Set(["codex", "claude", "glm", "deepseek", "minimax"]);
|
|
3
|
+
const DEFAULT_PROVIDERS = ["codex", "claude", "glm", "deepseek", "minimax", "kimi"];
|
|
4
|
+
const PROVIDERS = new Set(["codex", "claude", "glm", "deepseek", "minimax", "kimi"]);
|
|
5
5
|
function normalizeProviders(value) {
|
|
6
6
|
if (!Array.isArray(value)) {
|
|
7
7
|
return DEFAULT_PROVIDERS;
|
|
@@ -24,6 +24,7 @@ export function defaultConfig() {
|
|
|
24
24
|
glm: { baseUrl: "https://open.bigmodel.cn", apiKey: "" },
|
|
25
25
|
deepseek: { apiKey: "" },
|
|
26
26
|
minimax: { region: "cn", apiKey: "" },
|
|
27
|
+
kimi: { apiKey: "" },
|
|
27
28
|
};
|
|
28
29
|
}
|
|
29
30
|
export function readConfig(paths = buildPaths()) {
|
|
@@ -40,6 +41,7 @@ export function readConfig(paths = buildPaths()) {
|
|
|
40
41
|
glm: fileConfig.glm ?? defaultConfig().glm,
|
|
41
42
|
deepseek: fileConfig.deepseek ?? defaultConfig().deepseek,
|
|
42
43
|
minimax: fileConfig.minimax ?? defaultConfig().minimax,
|
|
44
|
+
kimi: fileConfig.kimi ?? defaultConfig().kimi,
|
|
43
45
|
};
|
|
44
46
|
}
|
|
45
47
|
export function ensureConfig(paths = buildPaths()) {
|
|
@@ -60,5 +62,9 @@ export function ensureConfig(paths = buildPaths()) {
|
|
|
60
62
|
if (!latest.minimax) {
|
|
61
63
|
writeJsonAtomic(paths.configFile, { ...latest, minimax: defaultConfig().minimax });
|
|
62
64
|
}
|
|
65
|
+
const current = readJsonFile(paths.configFile) ?? latest;
|
|
66
|
+
if (!current.kimi) {
|
|
67
|
+
writeJsonAtomic(paths.configFile, { ...current, kimi: defaultConfig().kimi });
|
|
68
|
+
}
|
|
63
69
|
return false;
|
|
64
70
|
}
|
package/dist/doctor.js
CHANGED
|
@@ -4,6 +4,7 @@ import { readConfig } from "./config.js";
|
|
|
4
4
|
import { collectCodexUsage } from "./codex.js";
|
|
5
5
|
import { claudeStatusLineHasIngest, getClaudeStatusLineCommand, readClaudeSettings } from "./claude.js";
|
|
6
6
|
import { isDir, isFile } from "./fs-util.js";
|
|
7
|
+
import { resolveKimiConfig } from "./kimi.js";
|
|
7
8
|
import { buildPaths, providerLatestPath } from "./paths.js";
|
|
8
9
|
import { notificationBackend } from "./notifier.js";
|
|
9
10
|
import { isSwiftBarInstalled, swiftBarPluginPath } from "./menubar.js";
|
|
@@ -184,6 +185,37 @@ export function runDoctor(options = {}) {
|
|
|
184
185
|
message: "disabled by config",
|
|
185
186
|
});
|
|
186
187
|
}
|
|
188
|
+
if (monitored.has("kimi")) {
|
|
189
|
+
const kimiConfig = resolveKimiConfig(config.kimi, paths.homeDir);
|
|
190
|
+
const hasApiKey = Boolean(kimiConfig.apiKey);
|
|
191
|
+
const keySource = config.kimi?.apiKey
|
|
192
|
+
? "coding-usage-bar config"
|
|
193
|
+
: "claude-lanes config.env";
|
|
194
|
+
checks.push({
|
|
195
|
+
name: "Kimi API key",
|
|
196
|
+
ok: hasApiKey,
|
|
197
|
+
message: hasApiKey
|
|
198
|
+
? `configured (${keySource})`
|
|
199
|
+
: "not set; edit ~/.coding-usage-bar/config.json to set kimi.apiKey, or configure a kimi.com lane in ~/.config/claude-lanes/config.env",
|
|
200
|
+
});
|
|
201
|
+
const kimiLatest = providerLatestPath(paths, "kimi");
|
|
202
|
+
checks.push({
|
|
203
|
+
name: "Kimi usage cache",
|
|
204
|
+
ok: !hasApiKey || isFile(kimiLatest),
|
|
205
|
+
message: isFile(kimiLatest)
|
|
206
|
+
? `found ${kimiLatest}`
|
|
207
|
+
: hasApiKey
|
|
208
|
+
? `missing ${kimiLatest}; run coding-usage-bar daemon --once to collect`
|
|
209
|
+
: "skipped (API key not set)",
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
checks.push({
|
|
214
|
+
name: "Kimi",
|
|
215
|
+
ok: true,
|
|
216
|
+
message: "disabled by config",
|
|
217
|
+
});
|
|
218
|
+
}
|
|
187
219
|
const backend = notificationBackend();
|
|
188
220
|
const notificationOk = backend !== "unsupported" && backend !== "burnt-toast";
|
|
189
221
|
checks.push({
|
package/dist/format.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { BurnAnalysis, StatusIssue, ProviderUsage, UsageWindow, StatusSnapshot } from "./types.js";
|
|
2
2
|
export declare function formatDurationUntil(iso: string, now?: Date): string;
|
|
3
|
-
export declare function formatProviderLabel(provider: string): "Claude" | "GLM" | "DeepSeek" | "MiniMax" | "Codex";
|
|
3
|
+
export declare function formatProviderLabel(provider: string): "Claude" | "GLM" | "DeepSeek" | "MiniMax" | "Kimi" | "Codex";
|
|
4
4
|
export declare function formatWindowLabel(window: UsageWindow): "5h" | "7d";
|
|
5
5
|
export declare function usageBar(usedPercent: number, width?: number): string;
|
|
6
6
|
export declare function formatStatusRows(usages: ProviderUsage[], analyses: BurnAnalysis[]): string;
|
package/dist/format.js
CHANGED
package/dist/install.d.ts
CHANGED
package/dist/install.js
CHANGED
|
@@ -382,7 +382,7 @@ export function uninstall(options = {}) {
|
|
|
382
382
|
messages.push(`[dry-run] would unload and remove ${paths.launchAgentFile}`);
|
|
383
383
|
}
|
|
384
384
|
else {
|
|
385
|
-
stopLaunchAgent(paths.launchAgentFile);
|
|
385
|
+
stopLaunchAgent(paths.launchAgentFile, options.launchctlRunner);
|
|
386
386
|
fs.rmSync(paths.launchAgentFile, { force: true });
|
|
387
387
|
messages.push(`Removed launchd agent: ${paths.launchAgentFile}`);
|
|
388
388
|
}
|
package/dist/kimi.d.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { KimiConfig, ProviderUsage } from "./types.js";
|
|
2
|
+
interface KimiQuotaDetail {
|
|
3
|
+
limit?: string | number;
|
|
4
|
+
used?: string | number;
|
|
5
|
+
remaining?: string | number;
|
|
6
|
+
resetTime?: string;
|
|
7
|
+
}
|
|
8
|
+
interface KimiWindowLimit {
|
|
9
|
+
window?: {
|
|
10
|
+
duration?: number;
|
|
11
|
+
timeUnit?: string;
|
|
12
|
+
};
|
|
13
|
+
detail?: KimiQuotaDetail;
|
|
14
|
+
}
|
|
15
|
+
interface KimiUsagesResponse {
|
|
16
|
+
user?: {
|
|
17
|
+
membership?: {
|
|
18
|
+
level?: string;
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
usage?: KimiQuotaDetail;
|
|
22
|
+
limits?: KimiWindowLimit[];
|
|
23
|
+
}
|
|
24
|
+
export declare function fetchKimiUsages(config: KimiConfig): Promise<KimiUsagesResponse>;
|
|
25
|
+
export declare function usageFromKimiUsages(usages: KimiUsagesResponse, options: {
|
|
26
|
+
observedAt?: string;
|
|
27
|
+
source: string;
|
|
28
|
+
}): ProviderUsage | null;
|
|
29
|
+
export declare function readClaudeLanesKimiConfig(configFile: string): KimiConfig | null;
|
|
30
|
+
export declare function resolveKimiConfig(config: KimiConfig | undefined, homeDir?: string): KimiConfig;
|
|
31
|
+
export declare function collectKimiUsage(config: KimiConfig): Promise<ProviderUsage>;
|
|
32
|
+
export {};
|
package/dist/kimi.js
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { makeProviderUsage, normalizeWindow } from "./usage.js";
|
|
5
|
+
const DEFAULT_BASE_URL = "https://api.kimi.com/coding";
|
|
6
|
+
const KIMI_HOST_PATTERN = /kimi\.com/;
|
|
7
|
+
function asNumber(value) {
|
|
8
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
9
|
+
return value;
|
|
10
|
+
}
|
|
11
|
+
if (typeof value === "string" && value.trim() !== "") {
|
|
12
|
+
const parsed = Number(value);
|
|
13
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
14
|
+
}
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
// Kimi reports limit/used/remaining per window. Prefer the used/limit ratio;
|
|
18
|
+
// when limit is 0 or missing, derive the total from used + remaining instead of
|
|
19
|
+
// falling back to 0% while a real signal exists.
|
|
20
|
+
function percentFromDetail(detail) {
|
|
21
|
+
const limit = asNumber(detail.limit);
|
|
22
|
+
const used = asNumber(detail.used);
|
|
23
|
+
const remaining = asNumber(detail.remaining);
|
|
24
|
+
if (limit !== null && limit > 0 && used !== null) {
|
|
25
|
+
return Math.max(0, Math.min(100, (used / limit) * 100));
|
|
26
|
+
}
|
|
27
|
+
if (used !== null && remaining !== null && used + remaining > 0) {
|
|
28
|
+
return Math.max(0, Math.min(100, (used / (used + remaining)) * 100));
|
|
29
|
+
}
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
function fiveHourLimit(limits) {
|
|
33
|
+
const withWindow = limits.filter((entry) => typeof entry.window?.duration === "number");
|
|
34
|
+
const exact = withWindow.find((entry) => entry.window?.duration === 300 && entry.window?.timeUnit === "TIME_UNIT_MINUTE");
|
|
35
|
+
if (exact) {
|
|
36
|
+
return exact;
|
|
37
|
+
}
|
|
38
|
+
const sorted = [...withWindow].sort((left, right) => (left.window?.duration ?? Infinity) - (right.window?.duration ?? Infinity));
|
|
39
|
+
return sorted[0] ?? null;
|
|
40
|
+
}
|
|
41
|
+
export async function fetchKimiUsages(config) {
|
|
42
|
+
const baseUrl = (config.baseUrl || DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
43
|
+
const url = `${baseUrl}/v1/usages`;
|
|
44
|
+
const response = await fetch(url, {
|
|
45
|
+
method: "GET",
|
|
46
|
+
headers: {
|
|
47
|
+
"Authorization": `Bearer ${config.apiKey}`,
|
|
48
|
+
"Content-Type": "application/json",
|
|
49
|
+
},
|
|
50
|
+
signal: AbortSignal.timeout(15_000),
|
|
51
|
+
});
|
|
52
|
+
if (!response.ok) {
|
|
53
|
+
throw new Error(`Kimi usages API returned HTTP ${response.status}`);
|
|
54
|
+
}
|
|
55
|
+
return (await response.json());
|
|
56
|
+
}
|
|
57
|
+
export function usageFromKimiUsages(usages, options) {
|
|
58
|
+
const shortWindow = fiveHourLimit(usages.limits ?? []);
|
|
59
|
+
const fiveHourPercent = shortWindow?.detail ? percentFromDetail(shortWindow.detail) : null;
|
|
60
|
+
const fiveHourReset = shortWindow?.detail?.resetTime;
|
|
61
|
+
const sevenDayPercent = usages.usage ? percentFromDetail(usages.usage) : null;
|
|
62
|
+
const sevenDayReset = usages.usage?.resetTime;
|
|
63
|
+
if (fiveHourPercent === null || !fiveHourReset || sevenDayPercent === null || !sevenDayReset) {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
const fiveHour = normalizeWindow("five_hour", {
|
|
67
|
+
used_percent: fiveHourPercent,
|
|
68
|
+
resets_at: fiveHourReset,
|
|
69
|
+
}, 300);
|
|
70
|
+
const sevenDay = normalizeWindow("seven_day", {
|
|
71
|
+
used_percent: sevenDayPercent,
|
|
72
|
+
resets_at: sevenDayReset,
|
|
73
|
+
}, 10080);
|
|
74
|
+
if (!fiveHour || !sevenDay) {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
return makeProviderUsage({
|
|
78
|
+
provider: "kimi",
|
|
79
|
+
source: options.source,
|
|
80
|
+
observedAt: options.observedAt,
|
|
81
|
+
planType: usages.user?.membership?.level ?? null,
|
|
82
|
+
fiveHour,
|
|
83
|
+
sevenDay,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
// Claude Code lanes keep the Kimi credential in ~/.config/claude-lanes/config.env
|
|
87
|
+
// as CONFIG_<n>_BASE_URL / CONFIG_<n>_AUTH_TOKEN pairs. Scan every numbered entry
|
|
88
|
+
// and pick the one whose base URL points at kimi.com, so lane reordering does not
|
|
89
|
+
// break the lookup.
|
|
90
|
+
export function readClaudeLanesKimiConfig(configFile) {
|
|
91
|
+
let content;
|
|
92
|
+
try {
|
|
93
|
+
content = fs.readFileSync(configFile, "utf8");
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
const entries = new Map();
|
|
99
|
+
for (const line of content.split(/\r?\n/)) {
|
|
100
|
+
const trimmed = line.trim();
|
|
101
|
+
if (!trimmed || trimmed.startsWith("#")) {
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
const eq = trimmed.indexOf("=");
|
|
105
|
+
if (eq <= 0) {
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
entries.set(trimmed.slice(0, eq).trim(), trimmed.slice(eq + 1).trim());
|
|
109
|
+
}
|
|
110
|
+
const indices = new Set();
|
|
111
|
+
for (const key of entries.keys()) {
|
|
112
|
+
const match = key.match(/^CONFIG_(\d+)_(?:BASE_URL|AUTH_TOKEN)$/);
|
|
113
|
+
if (match) {
|
|
114
|
+
indices.add(Number(match[1]));
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
for (const index of [...indices].sort((left, right) => left - right)) {
|
|
118
|
+
const baseUrl = entries.get(`CONFIG_${index}_BASE_URL`);
|
|
119
|
+
const apiKey = entries.get(`CONFIG_${index}_AUTH_TOKEN`);
|
|
120
|
+
if (baseUrl && apiKey && KIMI_HOST_PATTERN.test(baseUrl)) {
|
|
121
|
+
return { baseUrl, apiKey };
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
// Resolution order: explicit ~/.coding-usage-bar/config.json kimi.* wins;
|
|
127
|
+
// otherwise fall back to the claude-lanes lane that targets kimi.com.
|
|
128
|
+
export function resolveKimiConfig(config, homeDir = os.homedir()) {
|
|
129
|
+
const lanes = readClaudeLanesKimiConfig(path.join(homeDir, ".config", "claude-lanes", "config.env"));
|
|
130
|
+
return {
|
|
131
|
+
baseUrl: config?.baseUrl || lanes?.baseUrl,
|
|
132
|
+
apiKey: config?.apiKey || lanes?.apiKey,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
export async function collectKimiUsage(config) {
|
|
136
|
+
if (!config.apiKey) {
|
|
137
|
+
throw new Error("Kimi API key not configured. Edit ~/.coding-usage-bar/config.json to set kimi.apiKey, or configure a kimi.com lane in ~/.config/claude-lanes/config.env.");
|
|
138
|
+
}
|
|
139
|
+
const baseUrl = (config.baseUrl || DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
140
|
+
const usages = await fetchKimiUsages(config);
|
|
141
|
+
const usage = usageFromKimiUsages(usages, {
|
|
142
|
+
source: `${baseUrl}/v1/usages`,
|
|
143
|
+
});
|
|
144
|
+
if (!usage) {
|
|
145
|
+
throw new Error("Kimi usages response did not contain expected 5h/7d quota data");
|
|
146
|
+
}
|
|
147
|
+
return usage;
|
|
148
|
+
}
|
package/dist/menubar.js
CHANGED
|
@@ -23,6 +23,7 @@ const PROVIDER_ICON_ASSET = {
|
|
|
23
23
|
glm: "provider-glm.png",
|
|
24
24
|
deepseek: "provider-deepseek.png",
|
|
25
25
|
minimax: "provider-minimax.png",
|
|
26
|
+
kimi: "provider-kimi.png",
|
|
26
27
|
};
|
|
27
28
|
const PROVIDER_ICON_DARK_ASSET = {
|
|
28
29
|
glm: "provider-glm-dark.png",
|
|
@@ -33,6 +34,7 @@ const PROVIDER_ICON_FALLBACK = {
|
|
|
33
34
|
glm: "cpu",
|
|
34
35
|
deepseek: "bubble.left.and.bubble.right",
|
|
35
36
|
minimax: "waveform.path.ecg",
|
|
37
|
+
kimi: "k.square.fill",
|
|
36
38
|
};
|
|
37
39
|
const TITLE_ICON_ASSET = "provider-marks.png";
|
|
38
40
|
const ALERT_COLOR = "#D97757,#E8956E";
|
|
@@ -56,7 +58,7 @@ const STATE_PRIORITY = {
|
|
|
56
58
|
const TEXT_COLOR = "#111827,#F9FAFB";
|
|
57
59
|
const MUTED_COLOR = "#6B7280,#A1A1AA";
|
|
58
60
|
const ROW_FONT = "Menlo";
|
|
59
|
-
const TITLE_PROVIDER_ORDER = ["codex", "claude", "glm", "deepseek", "minimax"];
|
|
61
|
+
const TITLE_PROVIDER_ORDER = ["codex", "claude", "glm", "deepseek", "minimax", "kimi"];
|
|
60
62
|
const TITLE_SEPARATOR = "│";
|
|
61
63
|
const METER_WIDTH = 12;
|
|
62
64
|
const ASSET_DIR = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "assets");
|
|
@@ -615,6 +617,9 @@ function issueLabel(code) {
|
|
|
615
617
|
if (code === "MINIMAX_API_KEY_MISSING") {
|
|
616
618
|
return "MiniMax API key not set";
|
|
617
619
|
}
|
|
620
|
+
if (code === "KIMI_API_KEY_MISSING") {
|
|
621
|
+
return "Kimi API key not set";
|
|
622
|
+
}
|
|
618
623
|
if (code === "USAGE_STALE") {
|
|
619
624
|
return "Usage data is stale";
|
|
620
625
|
}
|
package/dist/notifier.js
CHANGED
|
@@ -53,7 +53,7 @@ export function markNotified(analysis, paths = buildPaths(), now = new Date()) {
|
|
|
53
53
|
writeJsonAtomic(paths.notificationStateFile, state);
|
|
54
54
|
}
|
|
55
55
|
function notificationText(analysis) {
|
|
56
|
-
const provider = analysis.provider === "claude" ? "Claude" : analysis.provider === "glm" ? "GLM" : "Codex";
|
|
56
|
+
const provider = analysis.provider === "claude" ? "Claude" : analysis.provider === "glm" ? "GLM" : analysis.provider === "minimax" ? "MiniMax" : analysis.provider === "deepseek" ? "DeepSeek" : analysis.provider === "kimi" ? "Kimi" : "Codex";
|
|
57
57
|
const suffix = analysis.fiveHour ? `剩 ${formatDurationUntil(analysis.fiveHour.resetsAt)} 重置。` : "";
|
|
58
58
|
if (analysis.state === "UNDER_BURN") {
|
|
59
59
|
return {
|
package/dist/paths.js
CHANGED
|
@@ -11,6 +11,7 @@ export function buildPaths(homeDir = os.homedir()) {
|
|
|
11
11
|
glmDir: path.join(stateDir, "glm"),
|
|
12
12
|
deepseekDir: path.join(stateDir, "deepseek"),
|
|
13
13
|
minimaxDir: path.join(stateDir, "minimax"),
|
|
14
|
+
kimiDir: path.join(stateDir, "kimi"),
|
|
14
15
|
notificationStateFile: path.join(stateDir, "notifications.json"),
|
|
15
16
|
statusFile: path.join(stateDir, "status.json"),
|
|
16
17
|
starPromptFile: path.join(stateDir, "star-prompt.json"),
|
|
@@ -33,6 +34,8 @@ function providerDir(paths, provider) {
|
|
|
33
34
|
return paths.deepseekDir;
|
|
34
35
|
if (provider === "minimax")
|
|
35
36
|
return paths.minimaxDir;
|
|
37
|
+
if (provider === "kimi")
|
|
38
|
+
return paths.kimiDir;
|
|
36
39
|
if (provider === "claude")
|
|
37
40
|
return paths.claudeDir;
|
|
38
41
|
return paths.codexDir;
|
package/dist/runtime.js
CHANGED
|
@@ -6,13 +6,14 @@ import { collectCodexUsage } from "./codex.js";
|
|
|
6
6
|
import { readJsonFile } from "./fs-util.js";
|
|
7
7
|
import { collectGlmUsage } from "./glm.js";
|
|
8
8
|
import { collectDeepseekUsage } from "./deepseek.js";
|
|
9
|
+
import { collectKimiUsage, resolveKimiConfig } from "./kimi.js";
|
|
9
10
|
import { collectMinimaxUsage } from "./minimax.js";
|
|
10
11
|
import { buildPaths } from "./paths.js";
|
|
11
12
|
import { loadLatestUsage, saveUsage } from "./store.js";
|
|
12
13
|
import { createStatusSnapshot, loadStatusSnapshot, refreshStatusSnapshotFreshness, saveStatusSnapshot, } from "./status.js";
|
|
13
14
|
const FIXTURES_DIR = fileURLToPath(new URL("../fixtures", import.meta.url));
|
|
14
15
|
export function loadFixtureUsages(fixturesDir) {
|
|
15
|
-
const providers = ["claude", "codex", "glm", "deepseek", "minimax"];
|
|
16
|
+
const providers = ["claude", "codex", "glm", "deepseek", "minimax", "kimi"];
|
|
16
17
|
return providers
|
|
17
18
|
.map((provider) => readJsonFile(path.join(fixturesDir, provider, "latest.json")))
|
|
18
19
|
.filter((item) => item !== null);
|
|
@@ -181,6 +182,44 @@ export async function collectLocalState(options = {}) {
|
|
|
181
182
|
}
|
|
182
183
|
}
|
|
183
184
|
}
|
|
185
|
+
if (monitored.has("kimi")) {
|
|
186
|
+
const kimiConfig = resolveKimiConfig(config.kimi, paths.homeDir);
|
|
187
|
+
if (!kimiConfig.apiKey) {
|
|
188
|
+
issues.push({
|
|
189
|
+
provider: "kimi",
|
|
190
|
+
severity: "warning",
|
|
191
|
+
code: "KIMI_API_KEY_MISSING",
|
|
192
|
+
message: "Kimi API key not configured. Edit ~/.coding-usage-bar/config.json to set kimi.apiKey, or configure a kimi.com lane in ~/.config/claude-lanes/config.env.",
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
try {
|
|
197
|
+
const kimi = await collectKimiUsage(kimiConfig);
|
|
198
|
+
saveUsage(kimi, paths);
|
|
199
|
+
usages.push(kimi);
|
|
200
|
+
}
|
|
201
|
+
catch (error) {
|
|
202
|
+
const cached = loadLatestUsage("kimi", paths);
|
|
203
|
+
if (cached) {
|
|
204
|
+
usages.push(cached);
|
|
205
|
+
issues.push({
|
|
206
|
+
provider: "kimi",
|
|
207
|
+
severity: "warning",
|
|
208
|
+
code: "KIMI_USING_CACHE",
|
|
209
|
+
message: `Kimi live usage unavailable; using cached usage from ${cached.observedAt}. ${error instanceof Error ? error.message : String(error)}`,
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
issues.push({
|
|
214
|
+
provider: "kimi",
|
|
215
|
+
severity: "error",
|
|
216
|
+
code: "KIMI_USAGE_MISSING",
|
|
217
|
+
message: error instanceof Error ? error.message : String(error),
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
184
223
|
return { usages, issues };
|
|
185
224
|
}
|
|
186
225
|
export async function collectLocalUsages(options = {}) {
|
package/dist/types.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type ProviderId = "claude" | "codex" | "glm" | "deepseek" | "minimax";
|
|
1
|
+
export type ProviderId = "claude" | "codex" | "glm" | "deepseek" | "minimax" | "kimi";
|
|
2
2
|
export type WindowName = "five_hour" | "seven_day";
|
|
3
3
|
export type BurnProfile = "low" | "high";
|
|
4
4
|
export type BurnState = "RAW" | "UNDER_BURN" | "ON_TRACK" | "OVER_BURN" | "LIMIT_RISK";
|
|
@@ -51,6 +51,10 @@ export interface MinimaxConfig {
|
|
|
51
51
|
region?: "cn" | "global";
|
|
52
52
|
apiKey?: string;
|
|
53
53
|
}
|
|
54
|
+
export interface KimiConfig {
|
|
55
|
+
baseUrl?: string;
|
|
56
|
+
apiKey?: string;
|
|
57
|
+
}
|
|
54
58
|
export interface RuntimePaths {
|
|
55
59
|
homeDir: string;
|
|
56
60
|
stateDir: string;
|
|
@@ -60,6 +64,7 @@ export interface RuntimePaths {
|
|
|
60
64
|
glmDir: string;
|
|
61
65
|
deepseekDir: string;
|
|
62
66
|
minimaxDir: string;
|
|
67
|
+
kimiDir: string;
|
|
63
68
|
notificationStateFile: string;
|
|
64
69
|
statusFile: string;
|
|
65
70
|
starPromptFile: string;
|
|
@@ -98,4 +103,5 @@ export interface BurnConfig {
|
|
|
98
103
|
glm?: GlmConfig;
|
|
99
104
|
deepseek?: DeepseekConfig;
|
|
100
105
|
minimax?: MinimaxConfig;
|
|
106
|
+
kimi?: KimiConfig;
|
|
101
107
|
}
|
|
Binary file
|
package/docs/assets/hero.png
CHANGED
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "coding-usage-bar",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.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",
|
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
},
|
|
34
34
|
"files": [
|
|
35
35
|
"assets",
|
|
36
|
+
"docs/assets/demo.gif",
|
|
36
37
|
"docs/assets/hero.png",
|
|
37
38
|
"dist",
|
|
38
39
|
"fixtures",
|