glm-quota-line 0.7.0 → 0.9.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/CHANGELOG.md +18 -0
- package/README.en.md +22 -17
- package/README.md +24 -19
- package/package.json +6 -4
- package/src/claude/install.js +27 -43
- package/src/claude/sessionStart.js +2 -5
- package/src/claude/settings.js +2 -10
- package/src/cli/commands.js +15 -0
- package/src/cli/index.js +1 -25
- package/src/cli/update.js +1 -1
- package/src/core/quota/cache.js +11 -30
- package/src/core/quota/parse.js +1 -1
- package/src/core/quota/service.js +47 -38
- package/src/core/status/bar.js +1 -1
- package/src/core/status/format.js +5 -11
- package/src/core/status/theme.js +1 -11
- package/src/core/status/viewModel.js +3 -1
- package/src/shared/config.js +9 -22
- package/src/shared/constants.js +16 -14
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.9.0
|
|
4
|
+
|
|
5
|
+
- Replaced progressive tier-based backoff with diamond-shaped refresh strategy: high quota (80–100%) refreshes every 2 min, medium (30–79%) every 5 min, low (0–29%) every 2 min — frequent updates when usage is active or quota is near exhaustion, relaxed in between
|
|
6
|
+
- Added failure-type-aware retry TTLs: rate-limited (429) retries after 3 min, unavailable retries after 2 min, instead of reusing the quota-based TTL
|
|
7
|
+
- Removed `refreshCount`/`tierIndex` cache fields and `advanceTier()` — cache format is now simpler, and TTL is derived purely from quota percentage
|
|
8
|
+
- Aligned severity threshold (danger/warn boundary) from hardcoded 25% to `LOW_QUOTA_THRESHOLD` (30%), consistent with the refresh band boundary
|
|
9
|
+
- Unknown CLI commands and invalid config subcommands now print an error and exit with code 1 instead of silently showing quota status
|
|
10
|
+
|
|
11
|
+
## 0.8.0
|
|
12
|
+
|
|
13
|
+
- Added progressive refresh backoff: new sessions start at 3-minute intervals, advance to 5 minutes after 5 refreshes, and cap at 10 minutes after 5 more — giving new users fast feedback while reducing API pressure in long sessions
|
|
14
|
+
- Added low-quota override: when remaining quota drops below 30%, the refresh interval is forced back to 3 minutes regardless of current tier, so users see accurate data when it matters most
|
|
15
|
+
- Low-quota refreshes do not advance the tier counter, preserving the previous tier when quota recovers
|
|
16
|
+
- SessionStart (startup / resume / clear) resets the tier to level 0, ensuring fresh sessions always start responsive
|
|
17
|
+
- Rate-limited and failed responses no longer advance the tier counter
|
|
18
|
+
- Old cache files without tier fields automatically migrate to tier 0
|
|
19
|
+
- Simplified README quick start to two commands with style config clearly marked as optional
|
|
20
|
+
|
|
3
21
|
## 0.7.0
|
|
4
22
|
|
|
5
23
|
- Added `--version` and `version` so the installed CLI version is visible directly from the command line
|
package/README.en.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<h1 align="center">glm-quota-line</h1>
|
|
2
2
|
|
|
3
3
|
<p align="center">
|
|
4
|
-
One-command install, zero dependencies. Shows accurate GLM quota balance and reset time in the Claude Code status line — data from the official API, not estimated.
|
|
4
|
+
One-command install, zero dependencies. Shows accurate Zhipu GLM Coding Plan quota balance and reset time in the Claude Code status line — data from the official API, not estimated.
|
|
5
5
|
</p>
|
|
6
6
|
|
|
7
7
|
<p align="center">
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
<a href="./README.md">简体中文</a>
|
|
16
16
|
</p>
|
|
17
17
|
|
|
18
|
-
`glm-quota-line` reads the GLM quota API, caches results, and prints a short status line for Claude Code's `statusLine.command`. A `SessionStart` hook pre-refreshes the cache so new sessions never show stale data.
|
|
18
|
+
`glm-quota-line` reads the Zhipu GLM Coding Plan quota API, caches results, and prints a short status line for Claude Code's `statusLine.command`. A `SessionStart` hook pre-refreshes the cache so new sessions never show stale data.
|
|
19
19
|
|
|
20
20
|
## Features
|
|
21
21
|
|
|
@@ -25,13 +25,20 @@
|
|
|
25
25
|
- **Zero dependencies** — no runtime deps, single-purpose CLI
|
|
26
26
|
- **Smart caching** — refreshes by session, TTL, and token usage; supports manual auth overrides for proxy/gateway setups
|
|
27
27
|
|
|
28
|
-
##
|
|
28
|
+
## Quick Start
|
|
29
29
|
|
|
30
30
|
```bash
|
|
31
31
|
npm install -g glm-quota-line
|
|
32
|
+
glm-quota-line install
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
That's it — works out of the box. Default output (`style=text`, `theme=ansi`, `palette=dark`):
|
|
36
|
+
|
|
37
|
+
```text
|
|
38
|
+
GLM Lite | 5h 91% | week 47% | reset 14:47
|
|
32
39
|
```
|
|
33
40
|
|
|
34
|
-
|
|
41
|
+
Both the 5-hour quota (primary) and weekly quota (secondary) are shown with automatic reset time.
|
|
35
42
|
|
|
36
43
|
Upgrade:
|
|
37
44
|
|
|
@@ -40,28 +47,30 @@ npm install -g glm-quota-line
|
|
|
40
47
|
glm-quota-line check-update
|
|
41
48
|
```
|
|
42
49
|
|
|
43
|
-
##
|
|
50
|
+
## Custom Styles
|
|
51
|
+
|
|
52
|
+
All style options are optional — adjust only if needed:
|
|
44
53
|
|
|
45
54
|
```bash
|
|
46
|
-
glm-quota-line
|
|
47
|
-
glm-quota-line config set
|
|
48
|
-
glm-quota-line config set theme
|
|
49
|
-
glm-quota-line config set palette dark
|
|
55
|
+
glm-quota-line config set style bar # layout: text / compact / bar
|
|
56
|
+
glm-quota-line config set theme ansi # color: plain / ansi
|
|
57
|
+
glm-quota-line config set palette dark # palette: dark / mono (only when theme=ansi)
|
|
50
58
|
```
|
|
51
59
|
|
|
52
|
-
|
|
60
|
+
Different `style` outputs:
|
|
53
61
|
|
|
54
62
|
```text
|
|
63
|
+
# style=text (default)
|
|
55
64
|
GLM Lite | 5h 91% | week 47% | reset 14:47
|
|
56
65
|
|
|
66
|
+
# style=compact
|
|
57
67
|
GLM 5h 91% W 47% | 14:47
|
|
58
68
|
|
|
69
|
+
# style=bar
|
|
59
70
|
GLM Lite █░░░░░░░░░ 91% | W 47% | 14:47
|
|
60
71
|
```
|
|
61
72
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
## Styles & Colors
|
|
73
|
+
Recommended combinations:
|
|
65
74
|
|
|
66
75
|
| Use case | Config |
|
|
67
76
|
| -------------- | --------------------------------------------- |
|
|
@@ -69,10 +78,6 @@ Both the 5-hour quota (primary) and weekly quota (secondary) are shown with auto
|
|
|
69
78
|
| Light terminal | `style=compact`, `theme=ansi`, `palette=mono` |
|
|
70
79
|
| Plain fallback | any `style`, `theme=plain` |
|
|
71
80
|
|
|
72
|
-
`style` controls layout (`text` / `compact` / `bar`). `theme` enables ANSI color. `palette` only applies when `theme=ansi`.
|
|
73
|
-
|
|
74
|
-
Set `NO_COLOR=1` to force plain output.
|
|
75
|
-
|
|
76
81
|
## Custom Auth
|
|
77
82
|
|
|
78
83
|
If Claude Code runs behind a gateway or proxy and the injected `ANTHROPIC_AUTH_TOKEN` / `ANTHROPIC_BASE_URL` are not the real values:
|
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<h1 align="center">glm-quota-line</h1>
|
|
2
2
|
|
|
3
3
|
<p align="center">
|
|
4
|
-
一行命令安装、零依赖,在 Claude Code
|
|
4
|
+
一行命令安装、零依赖,在 Claude Code 状态栏实时显示智谱(Zhipu)GLM Coding Plan 配额余额与重置时间。数据来自官方接口,精准非估算。
|
|
5
5
|
</p>
|
|
6
6
|
|
|
7
7
|
<p align="center">
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
<a href="./README.en.md">English</a>
|
|
16
16
|
</p>
|
|
17
17
|
|
|
18
|
-
`glm-quota-line`
|
|
18
|
+
`glm-quota-line` 读取智谱(Zhipu)GLM Coding Plan 配额接口、缓存结果,并输出一行适合 Claude Code `statusLine.command` 的状态文本。通过 `SessionStart` hook 预刷新缓存,新会话不会显示旧配额。
|
|
19
19
|
|
|
20
20
|
## 功能
|
|
21
21
|
|
|
@@ -25,43 +25,52 @@
|
|
|
25
25
|
- **零依赖** — 无运行时依赖,单一用途
|
|
26
26
|
- **智能缓存** — 按会话、TTL 和 token 用量刷新;支持代理/网关手动覆盖鉴权
|
|
27
27
|
|
|
28
|
-
##
|
|
28
|
+
## 快速开始
|
|
29
29
|
|
|
30
30
|
```bash
|
|
31
31
|
npm install -g glm-quota-line
|
|
32
|
+
glm-quota-line install
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
两步完成,开箱即用。默认输出(`style=text`,`theme=ansi`,`palette=dark`):
|
|
36
|
+
|
|
37
|
+
```text
|
|
38
|
+
GLM Lite | 5h 91% | week 47% | reset 14:47
|
|
32
39
|
```
|
|
33
40
|
|
|
34
|
-
|
|
41
|
+
同时显示 5 小时配额(主要)和周配额(次要),并自动展示重置时间。
|
|
35
42
|
|
|
36
|
-
|
|
43
|
+
升级版本:
|
|
37
44
|
|
|
38
45
|
```bash
|
|
39
46
|
npm install -g glm-quota-line
|
|
40
47
|
glm-quota-line check-update
|
|
41
48
|
```
|
|
42
49
|
|
|
43
|
-
##
|
|
50
|
+
## 自定义样式
|
|
51
|
+
|
|
52
|
+
所有样式配置均为可选,按需调整:
|
|
44
53
|
|
|
45
54
|
```bash
|
|
46
|
-
glm-quota-line
|
|
47
|
-
glm-quota-line config set
|
|
48
|
-
glm-quota-line config set theme
|
|
49
|
-
glm-quota-line config set palette dark
|
|
55
|
+
glm-quota-line config set style bar # 布局: text / compact / bar
|
|
56
|
+
glm-quota-line config set theme ansi # 配色: plain / ansi
|
|
57
|
+
glm-quota-line config set palette dark # 色板: dark / mono(仅 theme=ansi 时生效)
|
|
50
58
|
```
|
|
51
59
|
|
|
52
|
-
|
|
60
|
+
不同 style 效果:
|
|
53
61
|
|
|
54
62
|
```text
|
|
63
|
+
# style=text(默认)
|
|
55
64
|
GLM Lite | 5h 91% | week 47% | reset 14:47
|
|
56
65
|
|
|
66
|
+
# style=compact
|
|
57
67
|
GLM 5h 91% W 47% | 14:47
|
|
58
68
|
|
|
69
|
+
# style=bar
|
|
59
70
|
GLM Lite █░░░░░░░░░ 91% | W 47% | 14:47
|
|
60
71
|
```
|
|
61
72
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
## 样式与配色
|
|
73
|
+
推荐搭配:
|
|
65
74
|
|
|
66
75
|
| 使用场景 | 配置 |
|
|
67
76
|
| ---------- | --------------------------------------------- |
|
|
@@ -69,10 +78,6 @@ GLM Lite █░░░░░░░░░ 91% | W 47% | 14:47
|
|
|
69
78
|
| 浅色终端 | `style=compact`, `theme=ansi`, `palette=mono` |
|
|
70
79
|
| 纯文本回退 | 任意 `style`,`theme=plain` |
|
|
71
80
|
|
|
72
|
-
`style` 控制布局(`text` / `compact` / `bar`),`theme` 控制是否启用 ANSI 颜色,`palette` 仅在 `theme=ansi` 时生效。
|
|
73
|
-
|
|
74
|
-
设置 `NO_COLOR=1` 可强制关闭颜色。
|
|
75
|
-
|
|
76
81
|
## 自定义鉴权
|
|
77
82
|
|
|
78
83
|
如果 Claude Code 运行在代理或网关后面,注入的 `ANTHROPIC_AUTH_TOKEN` / `ANTHROPIC_BASE_URL` 不是实际值:
|
|
@@ -101,7 +106,7 @@ glm-quota-line config set <style|display|theme|palette|auth-token|base-url> <val
|
|
|
101
106
|
glm-quota-line config unset <key>
|
|
102
107
|
```
|
|
103
108
|
|
|
104
|
-
|
|
109
|
+
运行 `glm-quota-line --help` 查看完整说明。
|
|
105
110
|
|
|
106
111
|
`glm-quota-line --version` 可直接查看当前安装版本;`check-update` 只检查并提示升级命令,不会自动修改环境。
|
|
107
112
|
|
package/package.json
CHANGED
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "glm-quota-line",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.9.0",
|
|
4
|
+
"description": "Zhipu GLM Coding Plan quota monitor for Claude Code status line.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"author": "caojian",
|
|
8
8
|
"preferGlobal": true,
|
|
9
9
|
"keywords": [
|
|
10
10
|
"claude-code",
|
|
11
|
-
"statusline",
|
|
12
11
|
"glm",
|
|
12
|
+
"zhipu",
|
|
13
|
+
"z.ai",
|
|
14
|
+
"coding-plan",
|
|
13
15
|
"quota",
|
|
14
|
-
"
|
|
16
|
+
"statusline"
|
|
15
17
|
],
|
|
16
18
|
"repository": {
|
|
17
19
|
"type": "git",
|
package/src/claude/install.js
CHANGED
|
@@ -32,7 +32,7 @@ function isToolManagedStatusLine(statusLine) {
|
|
|
32
32
|
return isToolManagedCommand(statusLine?.command);
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
function getStatusLineEntryPath() {
|
|
36
36
|
return path.resolve(__dirname, "..", "cli", "index.js");
|
|
37
37
|
}
|
|
38
38
|
|
|
@@ -53,38 +53,41 @@ function isToolManagedHook(hook) {
|
|
|
53
53
|
return hook?.type === "command" && isToolManagedCommand(hook.command);
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
function
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const existingGroups = Array.isArray(settings.hooks.SessionStart) ? settings.hooks.SessionStart : null;
|
|
62
|
-
if (!existingGroups) {
|
|
63
|
-
return false;
|
|
64
|
-
}
|
|
56
|
+
function filterManagedHooksFromGroups(groups) {
|
|
57
|
+
let hadManaged = false;
|
|
58
|
+
const filtered = [];
|
|
65
59
|
|
|
66
|
-
|
|
67
|
-
const nextGroups = [];
|
|
68
|
-
|
|
69
|
-
for (const group of existingGroups) {
|
|
60
|
+
for (const group of groups) {
|
|
70
61
|
if (!group || typeof group !== "object" || !Array.isArray(group.hooks)) {
|
|
71
|
-
|
|
62
|
+
filtered.push(group);
|
|
72
63
|
continue;
|
|
73
64
|
}
|
|
74
65
|
|
|
75
66
|
const nextHooks = group.hooks.filter((hook) => !isToolManagedHook(hook));
|
|
76
67
|
if (nextHooks.length !== group.hooks.length) {
|
|
77
|
-
|
|
68
|
+
hadManaged = true;
|
|
78
69
|
}
|
|
79
70
|
|
|
80
71
|
if (nextHooks.length > 0) {
|
|
81
|
-
|
|
82
|
-
...group,
|
|
83
|
-
hooks: nextHooks
|
|
84
|
-
});
|
|
72
|
+
filtered.push({ ...group, hooks: nextHooks });
|
|
85
73
|
}
|
|
86
74
|
}
|
|
87
75
|
|
|
76
|
+
return { groups: filtered, hadManaged };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function removeManagedSessionStartHooks(settings) {
|
|
80
|
+
if (!settings.hooks || typeof settings.hooks !== "object") {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const existingGroups = Array.isArray(settings.hooks.SessionStart) ? settings.hooks.SessionStart : null;
|
|
85
|
+
if (!existingGroups) {
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const { groups: nextGroups, hadManaged: removed } = filterManagedHooksFromGroups(existingGroups);
|
|
90
|
+
|
|
88
91
|
if (nextGroups.length > 0) {
|
|
89
92
|
settings.hooks.SessionStart = nextGroups;
|
|
90
93
|
} else {
|
|
@@ -101,21 +104,10 @@ function removeManagedSessionStartHooks(settings) {
|
|
|
101
104
|
function installManagedSessionStartHooks(settings, command) {
|
|
102
105
|
const existingGroups =
|
|
103
106
|
settings.hooks && typeof settings.hooks === "object" && Array.isArray(settings.hooks.SessionStart)
|
|
104
|
-
? settings.hooks.SessionStart
|
|
105
|
-
group && typeof group === "object" ? { ...group, hooks: Array.isArray(group.hooks) ? [...group.hooks] : group.hooks } : group
|
|
106
|
-
)
|
|
107
|
+
? settings.hooks.SessionStart
|
|
107
108
|
: [];
|
|
108
109
|
|
|
109
|
-
const nextGroups = existingGroups
|
|
110
|
-
if (!group || typeof group !== "object" || !Array.isArray(group.hooks)) {
|
|
111
|
-
return group;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
return {
|
|
115
|
-
...group,
|
|
116
|
-
hooks: group.hooks.filter((hook) => !isToolManagedHook(hook))
|
|
117
|
-
};
|
|
118
|
-
});
|
|
110
|
+
const { groups: nextGroups } = filterManagedHooksFromGroups(existingGroups);
|
|
119
111
|
|
|
120
112
|
for (const matcher of SESSION_START_MATCHERS) {
|
|
121
113
|
const targetGroup = nextGroups.find(
|
|
@@ -123,21 +115,13 @@ function installManagedSessionStartHooks(settings, command) {
|
|
|
123
115
|
);
|
|
124
116
|
|
|
125
117
|
if (targetGroup) {
|
|
126
|
-
targetGroup.hooks.push({
|
|
127
|
-
type: "command",
|
|
128
|
-
command
|
|
129
|
-
});
|
|
118
|
+
targetGroup.hooks.push({ type: "command", command });
|
|
130
119
|
continue;
|
|
131
120
|
}
|
|
132
121
|
|
|
133
122
|
nextGroups.push({
|
|
134
123
|
matcher,
|
|
135
|
-
hooks: [
|
|
136
|
-
{
|
|
137
|
-
type: "command",
|
|
138
|
-
command
|
|
139
|
-
}
|
|
140
|
-
]
|
|
124
|
+
hooks: [{ type: "command", command }]
|
|
141
125
|
});
|
|
142
126
|
}
|
|
143
127
|
|
|
@@ -3,7 +3,7 @@ import { resolveQuotaStatus } from "../core/quota/service.js";
|
|
|
3
3
|
import { readStatusLineInput } from "./input.js";
|
|
4
4
|
import { readToolConfig } from "./settings.js";
|
|
5
5
|
|
|
6
|
-
export const SESSION_START_MATCHERS = ["startup", "resume", "clear"
|
|
6
|
+
export const SESSION_START_MATCHERS = ["startup", "resume", "clear"];
|
|
7
7
|
|
|
8
8
|
export async function refreshQuotaOnSessionStart(options = {}) {
|
|
9
9
|
const hookInput = await readStatusLineInput(options.stdin ?? process.stdin);
|
|
@@ -12,10 +12,7 @@ export async function refreshQuotaOnSessionStart(options = {}) {
|
|
|
12
12
|
const resolveQuotaStatusFn = options.resolveQuotaStatusFn ?? resolveQuotaStatus;
|
|
13
13
|
const config = {
|
|
14
14
|
...loadConfigFn(options.env ?? process.env, userConfig),
|
|
15
|
-
sessionId: hookInput?.session_id || ""
|
|
16
|
-
// SessionStart runs before statusLine has token totals, so treat a new session
|
|
17
|
-
// as starting from zero to preserve later token-threshold refreshes.
|
|
18
|
-
observedTokens: 0
|
|
15
|
+
sessionId: hookInput?.session_id || ""
|
|
19
16
|
};
|
|
20
17
|
|
|
21
18
|
return resolveQuotaStatusFn(config, {
|
package/src/claude/settings.js
CHANGED
|
@@ -10,8 +10,9 @@ import {
|
|
|
10
10
|
isValidTheme
|
|
11
11
|
} from "../shared/constants.js";
|
|
12
12
|
import { readJsonFile, writeJsonFile } from "../shared/jsonFile.js";
|
|
13
|
+
import { normalizeOptionalString } from "../shared/config.js";
|
|
13
14
|
|
|
14
|
-
|
|
15
|
+
function getClaudeDir() {
|
|
15
16
|
return path.join(os.homedir(), ".claude");
|
|
16
17
|
}
|
|
17
18
|
|
|
@@ -23,15 +24,6 @@ export function getToolConfigPath() {
|
|
|
23
24
|
return path.join(getClaudeDir(), "glm-quota-line.json");
|
|
24
25
|
}
|
|
25
26
|
|
|
26
|
-
function normalizeOptionalString(value) {
|
|
27
|
-
if (typeof value !== "string") {
|
|
28
|
-
return undefined;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const trimmed = value.trim();
|
|
32
|
-
return trimmed || undefined;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
27
|
function redactSecret(value) {
|
|
36
28
|
if (!value) {
|
|
37
29
|
return value;
|
package/src/cli/commands.js
CHANGED
|
@@ -149,11 +149,13 @@ export async function handleCommand(args, output = process.stdout, dependencies
|
|
|
149
149
|
if (command === "config" && subcommand === "set") {
|
|
150
150
|
const configKey = CONFIG_KEYS[key];
|
|
151
151
|
if (!configKey) {
|
|
152
|
+
process.exitCode = 1;
|
|
152
153
|
output.write("Supported config keys: style, display, theme, palette, auth-token, base-url\n");
|
|
153
154
|
return true;
|
|
154
155
|
}
|
|
155
156
|
|
|
156
157
|
if (!configKey.validate(value)) {
|
|
158
|
+
process.exitCode = 1;
|
|
157
159
|
output.write(`${configKey.invalidMessage}\n`);
|
|
158
160
|
return true;
|
|
159
161
|
}
|
|
@@ -168,6 +170,7 @@ export async function handleCommand(args, output = process.stdout, dependencies
|
|
|
168
170
|
if (command === "config" && subcommand === "unset") {
|
|
169
171
|
const configKey = CONFIG_KEYS[key];
|
|
170
172
|
if (!configKey) {
|
|
173
|
+
process.exitCode = 1;
|
|
171
174
|
output.write("Supported config keys: style, display, theme, palette, auth-token, base-url\n");
|
|
172
175
|
return true;
|
|
173
176
|
}
|
|
@@ -177,5 +180,17 @@ export async function handleCommand(args, output = process.stdout, dependencies
|
|
|
177
180
|
return true;
|
|
178
181
|
}
|
|
179
182
|
|
|
183
|
+
if (command === "config") {
|
|
184
|
+
process.exitCode = 1;
|
|
185
|
+
output.write("Supported config subcommands: show, set, unset\n");
|
|
186
|
+
return true;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (command) {
|
|
190
|
+
process.exitCode = 1;
|
|
191
|
+
output.write(`Unknown command: ${command}\nRun 'glm-quota-line -h' for usage.\n`);
|
|
192
|
+
return true;
|
|
193
|
+
}
|
|
194
|
+
|
|
180
195
|
return false;
|
|
181
196
|
}
|
package/src/cli/index.js
CHANGED
|
@@ -67,14 +67,6 @@ Examples:
|
|
|
67
67
|
Environment:
|
|
68
68
|
ANTHROPIC_AUTH_TOKEN
|
|
69
69
|
ANTHROPIC_BASE_URL
|
|
70
|
-
GLM_DISPLAY_MODE
|
|
71
|
-
GLM_STYLE
|
|
72
|
-
GLM_BAR_WIDTH
|
|
73
|
-
GLM_TIMEOUT_MS
|
|
74
|
-
GLM_CACHE_TTL_MS
|
|
75
|
-
GLM_THEME
|
|
76
|
-
GLM_PALETTE
|
|
77
|
-
NO_COLOR
|
|
78
70
|
`);
|
|
79
71
|
}
|
|
80
72
|
|
|
@@ -87,21 +79,6 @@ function getStoredDisplayOverrides(userConfig) {
|
|
|
87
79
|
};
|
|
88
80
|
}
|
|
89
81
|
|
|
90
|
-
function asNonNegativeNumber(value) {
|
|
91
|
-
return Number.isFinite(value) && value >= 0 ? value : null;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
function getObservedTokens(statusLineInput) {
|
|
95
|
-
const inputTokens = asNonNegativeNumber(statusLineInput?.context_window?.total_input_tokens);
|
|
96
|
-
const outputTokens = asNonNegativeNumber(statusLineInput?.context_window?.total_output_tokens);
|
|
97
|
-
|
|
98
|
-
if (inputTokens === null && outputTokens === null) {
|
|
99
|
-
return null;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
return (inputTokens ?? 0) + (outputTokens ?? 0);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
82
|
export async function main() {
|
|
106
83
|
try {
|
|
107
84
|
const args = parseArgs();
|
|
@@ -128,8 +105,7 @@ export async function main() {
|
|
|
128
105
|
// Display config precedence is env defaults -> persisted config -> CLI flags.
|
|
129
106
|
...getStoredDisplayOverrides(userConfig),
|
|
130
107
|
...args,
|
|
131
|
-
sessionId: statusLineInput?.session_id || ""
|
|
132
|
-
observedTokens: getObservedTokens(statusLineInput)
|
|
108
|
+
sessionId: statusLineInput?.session_id || ""
|
|
133
109
|
};
|
|
134
110
|
const quotaStatus = await resolveQuotaStatus(config);
|
|
135
111
|
|
package/src/cli/update.js
CHANGED
package/src/core/quota/cache.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
|
|
4
|
+
import { asFiniteNumber } from "./parse.js";
|
|
5
|
+
|
|
4
6
|
function isValidQuotaShape(value) {
|
|
5
7
|
if (!value || typeof value.key !== "string") {
|
|
6
8
|
return false;
|
|
@@ -69,10 +71,6 @@ function isSuccessCacheShape(value) {
|
|
|
69
71
|
return isMultiQuotaSuccessCacheShape(value) || isLegacyPercentSuccessCacheShape(value);
|
|
70
72
|
}
|
|
71
73
|
|
|
72
|
-
function asFiniteNumber(value) {
|
|
73
|
-
return Number.isFinite(value) ? value : null;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
74
|
function normalizeCache(parsed) {
|
|
77
75
|
if (!parsed || typeof parsed !== "object") {
|
|
78
76
|
return null;
|
|
@@ -81,9 +79,11 @@ function normalizeCache(parsed) {
|
|
|
81
79
|
const result = isSuccessCacheShape(parsed.result) ? parsed.result : null;
|
|
82
80
|
const savedAt = asFiniteNumber(parsed.savedAt);
|
|
83
81
|
const lastAttemptAt = asFiniteNumber(parsed.lastAttemptAt) ?? savedAt;
|
|
84
|
-
const lastObservedTokensAtFetch = asFiniteNumber(parsed.lastObservedTokensAtFetch);
|
|
85
82
|
const sessionId = typeof parsed.sessionId === "string" ? parsed.sessionId : "";
|
|
86
|
-
const
|
|
83
|
+
const lastFailureKind =
|
|
84
|
+
parsed.lastFailureKind === "rate_limited" || parsed.lastFailureKind === "unavailable"
|
|
85
|
+
? parsed.lastFailureKind
|
|
86
|
+
: null;
|
|
87
87
|
|
|
88
88
|
if (result && savedAt === null) {
|
|
89
89
|
return null;
|
|
@@ -96,9 +96,8 @@ function normalizeCache(parsed) {
|
|
|
96
96
|
return {
|
|
97
97
|
savedAt,
|
|
98
98
|
lastAttemptAt,
|
|
99
|
-
lastObservedTokensAtFetch,
|
|
100
99
|
sessionId,
|
|
101
|
-
|
|
100
|
+
lastFailureKind,
|
|
102
101
|
result
|
|
103
102
|
};
|
|
104
103
|
}
|
|
@@ -109,10 +108,7 @@ async function writeCache(cacheFilePath, cache) {
|
|
|
109
108
|
...(Number.isFinite(cache.savedAt) ? { savedAt: cache.savedAt } : {}),
|
|
110
109
|
...(Number.isFinite(cache.lastAttemptAt) ? { lastAttemptAt: cache.lastAttemptAt } : {}),
|
|
111
110
|
...(cache.sessionId ? { sessionId: cache.sessionId } : {}),
|
|
112
|
-
...(
|
|
113
|
-
? { lastObservedTokensAtFetch: cache.lastObservedTokensAtFetch }
|
|
114
|
-
: {}),
|
|
115
|
-
...(cache.skipNextTokenTrigger ? { skipNextTokenTrigger: true } : {}),
|
|
111
|
+
...(cache.lastFailureKind ? { lastFailureKind: cache.lastFailureKind } : {}),
|
|
116
112
|
...(cache.result ? { result: cache.result } : {})
|
|
117
113
|
},
|
|
118
114
|
null,
|
|
@@ -139,34 +135,19 @@ export async function writeSuccessCache(cacheFilePath, result, options = {}) {
|
|
|
139
135
|
savedAt: now,
|
|
140
136
|
lastAttemptAt: now,
|
|
141
137
|
sessionId: options.sessionId || "",
|
|
142
|
-
|
|
143
|
-
skipNextTokenTrigger: false,
|
|
138
|
+
lastFailureKind: null,
|
|
144
139
|
result
|
|
145
140
|
});
|
|
146
141
|
}
|
|
147
142
|
|
|
148
|
-
export async function
|
|
143
|
+
export async function writeFailureCache(cacheFilePath, cached, options = {}) {
|
|
149
144
|
const now = options.now ?? Date.now();
|
|
150
145
|
|
|
151
146
|
await writeCache(cacheFilePath, {
|
|
152
147
|
savedAt: cached?.savedAt ?? null,
|
|
153
148
|
lastAttemptAt: now,
|
|
154
149
|
sessionId: options.sessionId || cached?.sessionId || "",
|
|
155
|
-
|
|
156
|
-
asFiniteNumber(options.observedTokens) ?? cached?.lastObservedTokensAtFetch ?? null,
|
|
157
|
-
skipNextTokenTrigger: true,
|
|
158
|
-
result: cached?.result ?? null
|
|
159
|
-
});
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
export async function writeSkippedTokenTriggerCache(cacheFilePath, cached, options = {}) {
|
|
163
|
-
await writeCache(cacheFilePath, {
|
|
164
|
-
savedAt: cached?.savedAt ?? null,
|
|
165
|
-
lastAttemptAt: cached?.lastAttemptAt ?? null,
|
|
166
|
-
sessionId: options.sessionId || cached?.sessionId || "",
|
|
167
|
-
lastObservedTokensAtFetch:
|
|
168
|
-
asFiniteNumber(options.observedTokens) ?? cached?.lastObservedTokensAtFetch ?? null,
|
|
169
|
-
skipNextTokenTrigger: false,
|
|
150
|
+
lastFailureKind: options.failureKind || "unavailable",
|
|
170
151
|
result: cached?.result ?? null
|
|
171
152
|
});
|
|
172
153
|
}
|
package/src/core/quota/parse.js
CHANGED
|
@@ -1,74 +1,74 @@
|
|
|
1
1
|
import {
|
|
2
2
|
readCache,
|
|
3
|
-
|
|
4
|
-
writeSkippedTokenTriggerCache,
|
|
3
|
+
writeFailureCache,
|
|
5
4
|
writeSuccessCache
|
|
6
5
|
} from "./cache.js";
|
|
7
6
|
import { fetchQuota } from "./fetch.js";
|
|
8
7
|
import { parseQuotaResponse } from "./parse.js";
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
}
|
|
8
|
+
import {
|
|
9
|
+
RATE_LIMIT_RETRY_TTL_MS,
|
|
10
|
+
REFRESH_BANDS,
|
|
11
|
+
UNAVAILABLE_RETRY_TTL_MS
|
|
12
|
+
} from "../../shared/constants.js";
|
|
15
13
|
|
|
16
14
|
function getCachedStatus(cached) {
|
|
17
15
|
return cached?.result ?? { kind: "unavailable" };
|
|
18
16
|
}
|
|
19
17
|
|
|
20
|
-
function
|
|
21
|
-
if (!
|
|
22
|
-
return
|
|
18
|
+
function getRefreshTtlMs(leftPercent) {
|
|
19
|
+
if (!Number.isFinite(leftPercent)) {
|
|
20
|
+
return REFRESH_BANDS[1].ttlMs;
|
|
23
21
|
}
|
|
24
22
|
|
|
25
|
-
|
|
26
|
-
|
|
23
|
+
const matchedBand = REFRESH_BANDS.find((band) => leftPercent >= band.minLeftPercent);
|
|
24
|
+
return matchedBand ? matchedBand.ttlMs : REFRESH_BANDS[REFRESH_BANDS.length - 1].ttlMs;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function getEffectiveTtlMs(cached) {
|
|
28
|
+
if (cached?.lastFailureKind === "rate_limited") {
|
|
29
|
+
return RATE_LIMIT_RETRY_TTL_MS;
|
|
27
30
|
}
|
|
28
31
|
|
|
29
|
-
if (cached
|
|
30
|
-
return
|
|
32
|
+
if (cached?.lastFailureKind === "unavailable") {
|
|
33
|
+
return UNAVAILABLE_RETRY_TTL_MS;
|
|
31
34
|
}
|
|
32
35
|
|
|
33
|
-
|
|
34
|
-
|
|
36
|
+
return getRefreshTtlMs(cached?.result?.leftPercent);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function shouldRefreshQuota(cached, cacheTtlMs, now, sessionId) {
|
|
40
|
+
if (!cached) {
|
|
41
|
+
return true;
|
|
35
42
|
}
|
|
36
43
|
|
|
37
|
-
if (
|
|
38
|
-
return
|
|
44
|
+
if (sessionId && cached.sessionId !== sessionId) {
|
|
45
|
+
return true;
|
|
39
46
|
}
|
|
40
47
|
|
|
41
|
-
if (cached.
|
|
42
|
-
return
|
|
48
|
+
if (cached.lastAttemptAt === null || now - cached.lastAttemptAt >= cacheTtlMs) {
|
|
49
|
+
return true;
|
|
43
50
|
}
|
|
44
51
|
|
|
45
|
-
return
|
|
52
|
+
return false;
|
|
46
53
|
}
|
|
47
54
|
|
|
48
55
|
export async function resolveQuotaStatus(config, options = {}) {
|
|
49
56
|
const now = options.now ?? Date.now();
|
|
50
57
|
const fetchImpl = options.fetchImpl;
|
|
51
58
|
const sessionId = config.sessionId || "";
|
|
52
|
-
const observedTokens = normalizeObservedTokens(config.observedTokens);
|
|
53
59
|
|
|
54
60
|
if (!config.authorization) {
|
|
55
61
|
return { kind: "auth_error" };
|
|
56
62
|
}
|
|
57
63
|
|
|
58
64
|
const cached = await readCache(config.cacheFilePath);
|
|
59
|
-
const
|
|
60
|
-
? { kind: "fetch" }
|
|
61
|
-
: shouldRefreshQuota(cached, config.cacheTtlMs, now, sessionId, observedTokens);
|
|
65
|
+
const effectiveTtlMs = getEffectiveTtlMs(cached);
|
|
62
66
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
67
|
+
const shouldRefresh = options.forceRefresh
|
|
68
|
+
? true
|
|
69
|
+
: shouldRefreshQuota(cached, effectiveTtlMs, now, sessionId);
|
|
66
70
|
|
|
67
|
-
if (
|
|
68
|
-
await writeSkippedTokenTriggerCache(config.cacheFilePath, cached, {
|
|
69
|
-
sessionId,
|
|
70
|
-
observedTokens
|
|
71
|
-
});
|
|
71
|
+
if (!shouldRefresh) {
|
|
72
72
|
return getCachedStatus(cached);
|
|
73
73
|
}
|
|
74
74
|
|
|
@@ -78,8 +78,7 @@ export async function resolveQuotaStatus(config, options = {}) {
|
|
|
78
78
|
if (parsed.kind === "success") {
|
|
79
79
|
await writeSuccessCache(config.cacheFilePath, parsed, {
|
|
80
80
|
now,
|
|
81
|
-
sessionId
|
|
82
|
-
observedTokens
|
|
81
|
+
sessionId
|
|
83
82
|
});
|
|
84
83
|
return parsed;
|
|
85
84
|
}
|
|
@@ -89,17 +88,27 @@ export async function resolveQuotaStatus(config, options = {}) {
|
|
|
89
88
|
}
|
|
90
89
|
|
|
91
90
|
if (parsed.kind === "rate_limited") {
|
|
92
|
-
await
|
|
91
|
+
await writeFailureCache(config.cacheFilePath, cached, {
|
|
93
92
|
now,
|
|
94
93
|
sessionId,
|
|
95
|
-
|
|
94
|
+
failureKind: "rate_limited"
|
|
96
95
|
});
|
|
97
96
|
return getCachedStatus(cached);
|
|
98
97
|
}
|
|
99
98
|
|
|
100
99
|
if (cached?.result) {
|
|
100
|
+
await writeFailureCache(config.cacheFilePath, cached, {
|
|
101
|
+
now,
|
|
102
|
+
sessionId,
|
|
103
|
+
failureKind: "unavailable"
|
|
104
|
+
});
|
|
101
105
|
return cached.result;
|
|
102
106
|
}
|
|
103
107
|
|
|
108
|
+
await writeFailureCache(config.cacheFilePath, cached, {
|
|
109
|
+
now,
|
|
110
|
+
sessionId,
|
|
111
|
+
failureKind: "unavailable"
|
|
112
|
+
});
|
|
104
113
|
return parsed;
|
|
105
114
|
}
|
package/src/core/status/bar.js
CHANGED
|
@@ -8,10 +8,6 @@ import { buildBar } from "./bar.js";
|
|
|
8
8
|
import { applyTheme } from "./theme.js";
|
|
9
9
|
import { buildStatusViewModel } from "./viewModel.js";
|
|
10
10
|
|
|
11
|
-
function getSeverityTone(model) {
|
|
12
|
-
return model.severity || "neutral";
|
|
13
|
-
}
|
|
14
|
-
|
|
15
11
|
function createErrorSegments(model) {
|
|
16
12
|
const tone = model.kind === "auth_error" ? "danger" : "warn";
|
|
17
13
|
return [
|
|
@@ -75,7 +71,7 @@ function appendResetSegments(segments, model) {
|
|
|
75
71
|
}
|
|
76
72
|
|
|
77
73
|
function createTextSegments(model, displayMode) {
|
|
78
|
-
const severityTone =
|
|
74
|
+
const severityTone = model.severity;
|
|
79
75
|
|
|
80
76
|
return appendResetSegments(
|
|
81
77
|
appendSecondarySegments(
|
|
@@ -91,7 +87,7 @@ function createTextSegments(model, displayMode) {
|
|
|
91
87
|
}
|
|
92
88
|
|
|
93
89
|
function createCompactSegments(model) {
|
|
94
|
-
const severityTone =
|
|
90
|
+
const severityTone = model.severity;
|
|
95
91
|
let segments;
|
|
96
92
|
|
|
97
93
|
if (model.secondaryQuota) {
|
|
@@ -126,7 +122,7 @@ function createBarSegments(model, barWidth) {
|
|
|
126
122
|
}
|
|
127
123
|
|
|
128
124
|
const bar = buildBar(model.primaryQuota.usedPercent, barWidth);
|
|
129
|
-
const severityTone =
|
|
125
|
+
const severityTone = model.severity;
|
|
130
126
|
const segments = [
|
|
131
127
|
{ text: model.levelLabel, tone: "label" },
|
|
132
128
|
{ text: " ", tone: "plain" },
|
|
@@ -157,8 +153,7 @@ export function formatStatus(result, options = {}) {
|
|
|
157
153
|
if (model.kind !== "success") {
|
|
158
154
|
return applyTheme(createErrorSegments(model), {
|
|
159
155
|
theme: normalizeTheme(options.theme),
|
|
160
|
-
palette: normalizePalette(options.palette)
|
|
161
|
-
env: options.env
|
|
156
|
+
palette: normalizePalette(options.palette)
|
|
162
157
|
});
|
|
163
158
|
}
|
|
164
159
|
|
|
@@ -175,7 +170,6 @@ export function formatStatus(result, options = {}) {
|
|
|
175
170
|
|
|
176
171
|
return applyTheme(segments, {
|
|
177
172
|
theme: normalizeTheme(options.theme),
|
|
178
|
-
palette: normalizePalette(options.palette)
|
|
179
|
-
env: options.env
|
|
173
|
+
palette: normalizePalette(options.palette)
|
|
180
174
|
});
|
|
181
175
|
}
|
package/src/core/status/theme.js
CHANGED
|
@@ -17,15 +17,6 @@ const ANSI = {
|
|
|
17
17
|
red: "\u001b[31m"
|
|
18
18
|
};
|
|
19
19
|
|
|
20
|
-
function shouldUseAnsi(theme, env = process.env) {
|
|
21
|
-
// NO_COLOR is the explicit opt-out and wins over local config.
|
|
22
|
-
if (env?.NO_COLOR) {
|
|
23
|
-
return false;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
return normalizeTheme(theme || DEFAULT_THEME) === "ansi";
|
|
27
|
-
}
|
|
28
|
-
|
|
29
20
|
function applyCodes(text, codes) {
|
|
30
21
|
if (!text || !codes.length) {
|
|
31
22
|
return text;
|
|
@@ -76,9 +67,8 @@ function getMonoCodes(tone) {
|
|
|
76
67
|
export function applyTheme(segments, options = {}) {
|
|
77
68
|
const theme = normalizeTheme(options.theme || DEFAULT_THEME);
|
|
78
69
|
const palette = normalizePalette(options.palette || DEFAULT_PALETTE);
|
|
79
|
-
const env = options.env || process.env;
|
|
80
70
|
|
|
81
|
-
if (
|
|
71
|
+
if (theme !== "ansi") {
|
|
82
72
|
return segments.map((segment) => segment.text).join("");
|
|
83
73
|
}
|
|
84
74
|
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { LOW_QUOTA_THRESHOLD } from "../../shared/constants.js";
|
|
2
|
+
|
|
1
3
|
function padTwoDigits(value) {
|
|
2
4
|
return String(value).padStart(2, "0");
|
|
3
5
|
}
|
|
@@ -37,7 +39,7 @@ function getSeverity(leftPercent) {
|
|
|
37
39
|
return "good";
|
|
38
40
|
}
|
|
39
41
|
|
|
40
|
-
if (leftPercent >=
|
|
42
|
+
if (leftPercent >= LOW_QUOTA_THRESHOLD) {
|
|
41
43
|
return "warn";
|
|
42
44
|
}
|
|
43
45
|
|
package/src/shared/config.js
CHANGED
|
@@ -12,22 +12,9 @@ import {
|
|
|
12
12
|
DEFAULT_QUOTA_URL,
|
|
13
13
|
DEFAULT_STYLE,
|
|
14
14
|
DEFAULT_THEME,
|
|
15
|
-
DEFAULT_TIMEOUT_MS
|
|
16
|
-
normalizeDisplayMode,
|
|
17
|
-
normalizePalette,
|
|
18
|
-
normalizeStatusStyle,
|
|
19
|
-
normalizeTheme
|
|
15
|
+
DEFAULT_TIMEOUT_MS
|
|
20
16
|
} from "./constants.js";
|
|
21
17
|
|
|
22
|
-
function parsePositiveInt(value, fallback) {
|
|
23
|
-
if (value === undefined || value === null || value === "") {
|
|
24
|
-
return fallback;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const parsed = Number.parseInt(String(value), 10);
|
|
28
|
-
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
18
|
function getCacheRoot() {
|
|
32
19
|
if (process.platform === "darwin") {
|
|
33
20
|
return path.join(os.homedir(), "Library", "Caches");
|
|
@@ -68,7 +55,7 @@ function deriveQuotaUrl(baseUrl) {
|
|
|
68
55
|
return "";
|
|
69
56
|
}
|
|
70
57
|
|
|
71
|
-
function normalizeOptionalString(value) {
|
|
58
|
+
export function normalizeOptionalString(value) {
|
|
72
59
|
if (typeof value !== "string") {
|
|
73
60
|
return "";
|
|
74
61
|
}
|
|
@@ -92,13 +79,13 @@ export function loadConfig(env = process.env, overrides = {}) {
|
|
|
92
79
|
quotaUrl: derivedQuotaUrl || DEFAULT_QUOTA_URL,
|
|
93
80
|
authorization,
|
|
94
81
|
anthropicBaseUrl,
|
|
95
|
-
timeoutMs:
|
|
96
|
-
cacheTtlMs:
|
|
97
|
-
displayMode:
|
|
98
|
-
style:
|
|
99
|
-
theme:
|
|
100
|
-
palette:
|
|
101
|
-
barWidth:
|
|
82
|
+
timeoutMs: DEFAULT_TIMEOUT_MS,
|
|
83
|
+
cacheTtlMs: DEFAULT_CACHE_TTL_MS,
|
|
84
|
+
displayMode: DEFAULT_DISPLAY_MODE,
|
|
85
|
+
style: DEFAULT_STYLE,
|
|
86
|
+
theme: DEFAULT_THEME,
|
|
87
|
+
palette: DEFAULT_PALETTE,
|
|
88
|
+
barWidth: DEFAULT_BAR_WIDTH,
|
|
102
89
|
cacheFilePath: path.join(getCacheRoot(), "glm-quota-line", cacheFileName)
|
|
103
90
|
};
|
|
104
91
|
}
|
package/src/shared/constants.js
CHANGED
|
@@ -2,18 +2,23 @@ export const DEFAULT_QUOTA_URL = "https://bigmodel.cn/api/monitor/usage/quota/li
|
|
|
2
2
|
export const DEFAULT_CN_BASE_URL = "https://open.bigmodel.cn";
|
|
3
3
|
export const DEFAULT_INTL_BASE_URL = "https://api.z.ai";
|
|
4
4
|
export const DEFAULT_TIMEOUT_MS = 5000;
|
|
5
|
-
export const DEFAULT_CACHE_TTL_MS =
|
|
5
|
+
export const DEFAULT_CACHE_TTL_MS = 600_000;
|
|
6
|
+
|
|
7
|
+
export const REFRESH_BANDS = [
|
|
8
|
+
{ minLeftPercent: 80, ttlMs: 120_000 }, // 2 min
|
|
9
|
+
{ minLeftPercent: 30, ttlMs: 300_000 }, // 5 min
|
|
10
|
+
{ minLeftPercent: 0, ttlMs: 120_000 } // 2 min
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
export const LOW_QUOTA_THRESHOLD = 30;
|
|
14
|
+
export const RATE_LIMIT_RETRY_TTL_MS = 180_000;
|
|
15
|
+
export const UNAVAILABLE_RETRY_TTL_MS = 120_000;
|
|
6
16
|
export const DEFAULT_DISPLAY_MODE = "left";
|
|
7
17
|
export const DEFAULT_STYLE = "text";
|
|
8
18
|
export const DEFAULT_BAR_WIDTH = 10;
|
|
9
|
-
export const DEFAULT_THEME = "
|
|
19
|
+
export const DEFAULT_THEME = "ansi";
|
|
10
20
|
export const DEFAULT_PALETTE = "dark";
|
|
11
21
|
|
|
12
|
-
export const STATUS_STYLES = ["text", "compact", "bar"];
|
|
13
|
-
export const DISPLAY_MODES = ["left", "used", "both"];
|
|
14
|
-
export const THEMES = ["plain", "ansi"];
|
|
15
|
-
export const PALETTES = ["dark", "mono"];
|
|
16
|
-
|
|
17
22
|
export const STATUS_BAR_CHARACTERS = {
|
|
18
23
|
filled: "█",
|
|
19
24
|
empty: "░"
|
|
@@ -22,23 +27,20 @@ export const STATUS_BAR_CHARACTERS = {
|
|
|
22
27
|
export const TOOL_CONFIG_SCHEMA_VERSION = 1;
|
|
23
28
|
export const TOOL_CONFIG_MANAGED_BY = "glm-quota-line";
|
|
24
29
|
|
|
25
|
-
export const QUOTA_UNAVAILABLE_TEXT = "GLM | quota unavailable";
|
|
26
|
-
export const AUTH_EXPIRED_TEXT = "GLM | auth expired";
|
|
27
|
-
|
|
28
30
|
export function isValidStatusStyle(value) {
|
|
29
|
-
return
|
|
31
|
+
return value === "text" || value === "compact" || value === "bar";
|
|
30
32
|
}
|
|
31
33
|
|
|
32
34
|
export function isValidDisplayMode(value) {
|
|
33
|
-
return
|
|
35
|
+
return value === "left" || value === "used" || value === "both";
|
|
34
36
|
}
|
|
35
37
|
|
|
36
38
|
export function isValidTheme(value) {
|
|
37
|
-
return
|
|
39
|
+
return value === "plain" || value === "ansi";
|
|
38
40
|
}
|
|
39
41
|
|
|
40
42
|
export function isValidPalette(value) {
|
|
41
|
-
return
|
|
43
|
+
return value === "dark" || value === "mono";
|
|
42
44
|
}
|
|
43
45
|
|
|
44
46
|
export function normalizeStatusStyle(value) {
|