glm-quota-line 0.5.0 → 0.7.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 CHANGED
@@ -1,5 +1,20 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.7.0
4
+
5
+ - Added `--version` and `version` so the installed CLI version is visible directly from the command line
6
+ - Added `check-update` to compare the installed version with npm and print a suggested upgrade command without auto-updating
7
+ - Improved CLI help and README docs with upgrade and version-check examples
8
+ - Corrected published package metadata to include `README.en.md` in the npm file list
9
+
10
+ ## 0.6.0
11
+
12
+ - Added official international GLM quota endpoint support for `api.z.ai` while preserving domestic `open.bigmodel.cn` detection
13
+ - Added dual-token status rendering for the new package shape, showing both the 5h quota and weekly quota in `text`, `compact`, and `bar` styles
14
+ - Kept old packages compatible by continuing to read the legacy `TOKENS_LIMIT(number=5)` quota and ignoring `TIME_LIMIT` / MCP usage
15
+ - Updated cache compatibility so existing cached percent-based results still render after upgrading
16
+ - Refreshed English and Chinese README examples to document international setup and the new token-only display behavior
17
+
3
18
  ## 0.5.0
4
19
 
5
20
  - Changed bar characters from `■□` (discrete squares) to `█░` (continuous blocks) to match Claude Code official statusline style
package/README.en.md ADDED
@@ -0,0 +1,117 @@
1
+ <h1 align="center">glm-quota-line</h1>
2
+
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.
5
+ </p>
6
+
7
+ <p align="center">
8
+ <a href="https://www.npmjs.com/package/glm-quota-line"><img alt="npm version" src="https://img.shields.io/npm/v/glm-quota-line?logo=npm&color=cb3837"></a>
9
+ <a href="https://www.npmjs.com/package/glm-quota-line"><img alt="node version" src="https://img.shields.io/node/v/glm-quota-line?logo=node.js&color=339933"></a>
10
+ <a href="./LICENSE"><img alt="license" src="https://img.shields.io/npm/l/glm-quota-line"></a>
11
+ <img alt="dependencies" src="https://img.shields.io/badge/dependencies-0-2ea44f">
12
+ </p>
13
+
14
+ <p align="center">
15
+ <a href="./README.md">简体中文</a>
16
+ </p>
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.
19
+
20
+ ## Features
21
+
22
+ - **One-command install** — `glm-quota-line install` wires status line and `SessionStart` hooks into Claude Code automatically
23
+ - **Accurate quota display** — reads directly from the official GLM quota API, not estimated; shows both 5-hour and weekly token quotas
24
+ - **Domestic + international endpoints** — auto-detects `open.bigmodel.cn` and `api.z.ai`
25
+ - **Zero dependencies** — no runtime deps, single-purpose CLI
26
+ - **Smart caching** — refreshes by session, TTL, and token usage; supports manual auth overrides for proxy/gateway setups
27
+
28
+ ## Installation
29
+
30
+ ```bash
31
+ npm install -g glm-quota-line
32
+ ```
33
+
34
+ Global install is recommended because `install` writes stable executable paths into `statusLine.command` and `SessionStart` hooks. `npx` works for one-off previews.
35
+
36
+ Upgrade:
37
+
38
+ ```bash
39
+ npm install -g glm-quota-line
40
+ glm-quota-line check-update
41
+ ```
42
+
43
+ ## Quick Start
44
+
45
+ ```bash
46
+ glm-quota-line install
47
+ glm-quota-line config set style bar
48
+ glm-quota-line config set theme ansi
49
+ glm-quota-line config set palette dark
50
+ ```
51
+
52
+ ## Output
53
+
54
+ ```text
55
+ GLM Lite | 5h 91% | week 47% | reset 14:47
56
+
57
+ GLM 5h 91% W 47% | 14:47
58
+
59
+ GLM Lite █░░░░░░░░░ 91% | W 47% | 14:47
60
+ ```
61
+
62
+ Both the 5-hour quota (primary) and weekly quota (secondary) are shown with automatic reset time.
63
+
64
+ ## Styles & Colors
65
+
66
+ | Use case | Config |
67
+ | -------------- | --------------------------------------------- |
68
+ | Dark terminal | `style=bar`, `theme=ansi`, `palette=dark` |
69
+ | Light terminal | `style=compact`, `theme=ansi`, `palette=mono` |
70
+ | Plain fallback | any `style`, `theme=plain` |
71
+
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
+ ## Custom Auth
77
+
78
+ If Claude Code runs behind a gateway or proxy and the injected `ANTHROPIC_AUTH_TOKEN` / `ANTHROPIC_BASE_URL` are not the real values:
79
+
80
+ ```bash
81
+ glm-quota-line config set auth-token <your-real-token>
82
+ glm-quota-line config set base-url https://open.bigmodel.cn/api/anthropic
83
+ # or
84
+ glm-quota-line config set base-url https://api.z.ai/api/anthropic
85
+ ```
86
+
87
+ Clear with `glm-quota-line config unset auth-token` / `base-url`. Stored overrides take precedence over Claude-injected environment variables.
88
+
89
+ ## Commands
90
+
91
+ ```bash
92
+ glm-quota-line [--style text|compact|bar] [--display left|used|both]
93
+ [--theme plain|ansi] [--palette dark|mono]
94
+
95
+ glm-quota-line install [--force]
96
+ glm-quota-line uninstall
97
+ glm-quota-line version
98
+ glm-quota-line check-update
99
+ glm-quota-line config show
100
+ glm-quota-line config set <style|display|theme|palette|auth-token|base-url> <value>
101
+ glm-quota-line config unset <key>
102
+ ```
103
+
104
+ Run `glm-quota-line --help` for full descriptions.
105
+
106
+ Use `glm-quota-line --version` to print the installed version. `check-update` only checks and prints the suggested upgrade command; it does not modify your environment.
107
+
108
+ ## Notes
109
+
110
+ - Only `TOKENS_LIMIT` quotas are shown; `TIME_LIMIT` / MCP usage is ignored
111
+ - Missing auth returns `GLM | auth expired`; API failures return `GLM | quota unavailable`
112
+ - `install` does not replace an unmanaged status line unless `--force` is used
113
+ - `install --force` backs up the previous entry; `uninstall` restores it when possible
114
+
115
+ ## License
116
+
117
+ [MIT](./LICENSE)
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
- Zero-dependency CLI for showing GLM Coding Plan quota in the Claude Code status line.
4
+ 一行命令安装、零依赖,在 Claude Code 状态栏实时显示 GLM 配额余额与重置时间。数据来自官方接口,精准非估算。
5
5
  </p>
6
6
 
7
7
  <p align="center">
@@ -12,50 +12,35 @@
12
12
  </p>
13
13
 
14
14
  <p align="center">
15
- <a href="./README.zh-CN.md">简体中文</a>
15
+ <a href="./README.en.md">English</a>
16
16
  </p>
17
17
 
18
- ## Overview
18
+ `glm-quota-line` 读取 GLM 配额接口、缓存结果,并输出一行适合 Claude Code `statusLine.command` 的状态文本。通过 `SessionStart` hook 预刷新缓存,新会话不会显示旧配额。
19
19
 
20
- `glm-quota-line` reads the GLM quota endpoint, caches successful responses, and prints a single short line for `statusLine.command`.
20
+ ## 功能
21
21
 
22
- It also installs a Claude Code `SessionStart` hook to pre-refresh the quota cache before a new session renders the status line.
22
+ - **一行命令安装** — `glm-quota-line install` 自动接入 Claude Code 状态栏和 `SessionStart` hook
23
+ - **精准配额显示** — 数据来自官方配额接口,非估算;同时展示 5 小时和周 token 配额
24
+ - **国内 + 国际端点** — 自动识别 `open.bigmodel.cn` 和 `api.z.ai`
25
+ - **零依赖** — 无运行时依赖,单一用途
26
+ - **智能缓存** — 按会话、TTL 和 token 用量刷新;支持代理/网关手动覆盖鉴权
23
27
 
24
- It is intentionally scoped to one host only: Claude Code.
25
-
26
- ## Why
27
-
28
- - Built for a single purpose: show GLM quota clearly in Claude Code
29
- - Small and predictable: no runtime dependencies, no unnecessary abstraction
30
- - Practical for real usage: cache-aware, session-aware, and easy to install
31
-
32
- ## Features
33
-
34
- - Built for Claude Code `statusLine.command`
35
- - No runtime dependencies
36
- - Optional `text`, `compact`, and `bar` layouts
37
- - Optional ANSI color themes for dark and light terminals
38
- - Manual `auth-token` and `base-url` overrides for proxy or gateway setups
39
- - Automatic install and uninstall for Claude Code status line integration
40
- - SessionStart pre-refresh so a new Claude session does not briefly show stale quota
41
-
42
- ## Installation
43
-
44
- Recommended:
28
+ ## 安装
45
29
 
46
30
  ```bash
47
31
  npm install -g glm-quota-line
48
32
  ```
49
33
 
50
- From a local clone:
34
+ 推荐全局安装,`install` 会将稳定的可执行路径写入 `statusLine.command` 和 `SessionStart` hooks。`npx` 适合一次性试跑。
35
+
36
+ 升级:
51
37
 
52
38
  ```bash
53
- npm install -g .
39
+ npm install -g glm-quota-line
40
+ glm-quota-line check-update
54
41
  ```
55
42
 
56
- `npx` is fine for one-off previews, but global install is the better default for Claude Code integration because `install` writes stable executable paths into both `statusLine.command` and the managed `SessionStart` hooks.
57
-
58
- ## Quick Start
43
+ ## 快速开始
59
44
 
60
45
  ```bash
61
46
  glm-quota-line install
@@ -64,71 +49,44 @@ glm-quota-line config set theme ansi
64
49
  glm-quota-line config set palette dark
65
50
  ```
66
51
 
67
- For a one-off preview:
68
-
69
- ```bash
70
- glm-quota-line --style compact
71
- ```
72
-
73
- ## Output
52
+ ## 输出示例
74
53
 
75
54
  ```text
76
- GLM Lite | 5h left 91% | reset 14:47
77
- GLM 91% | 14:47
78
- GLM Lite █░░░░░░░░░ 91% | 14:47
79
- ```
55
+ GLM Lite | 5h 91% | week 47% | reset 14:47
80
56
 
81
- ## Recommended Setups
57
+ GLM 5h 91% W 47% | 14:47
82
58
 
83
- | Use case | Config |
84
- | --- | --- |
85
- | Dark terminal | `style=bar`, `theme=ansi`, `palette=dark` |
86
- | Light terminal | `style=compact`, `theme=ansi`, `palette=mono` |
87
- | Plain fallback | any `style`, `theme=plain` |
88
-
89
- ## Color Presets
90
-
91
- `style` controls layout. `theme` enables or disables ANSI color. `palette` only applies when `theme=ansi`.
92
-
93
- - Dark terminal: `theme=ansi` + `palette=dark`
94
- - Light terminal: `theme=ansi` + `palette=mono`
95
- - Maximum compatibility: `theme=plain`
59
+ GLM Lite █░░░░░░░░░ 91% | W 47% | 14:47
60
+ ```
96
61
 
97
- Examples:
62
+ 同时显示 5 小时配额(主要)和周配额(次要),并自动展示重置时间。
98
63
 
99
- ```bash
100
- glm-quota-line config set style text
101
- glm-quota-line config set theme ansi
102
- glm-quota-line config set palette dark
64
+ ## 样式与配色
103
65
 
104
- glm-quota-line config set style compact
105
- glm-quota-line config set theme ansi
106
- glm-quota-line config set palette mono
66
+ | 使用场景 | 配置 |
67
+ | ---------- | --------------------------------------------- |
68
+ | 深色终端 | `style=bar`, `theme=ansi`, `palette=dark` |
69
+ | 浅色终端 | `style=compact`, `theme=ansi`, `palette=mono` |
70
+ | 纯文本回退 | 任意 `style`,`theme=plain` |
107
71
 
108
- glm-quota-line config set theme plain
109
- ```
72
+ `style` 控制布局(`text` / `compact` / `bar`),`theme` 控制是否启用 ANSI 颜色,`palette` 仅在 `theme=ansi` 时生效。
110
73
 
111
- Set `NO_COLOR=1` to force plain output.
74
+ 设置 `NO_COLOR=1` 可强制关闭颜色。
112
75
 
113
- ## Manual Credential Overrides
76
+ ## 自定义鉴权
114
77
 
115
- If Claude Code is running behind a gateway or proxy and the injected `ANTHROPIC_AUTH_TOKEN` / `ANTHROPIC_BASE_URL` are not the real values, store explicit overrides:
78
+ 如果 Claude Code 运行在代理或网关后面,注入的 `ANTHROPIC_AUTH_TOKEN` / `ANTHROPIC_BASE_URL` 不是实际值:
116
79
 
117
80
  ```bash
118
81
  glm-quota-line config set auth-token <your-real-token>
119
82
  glm-quota-line config set base-url https://open.bigmodel.cn/api/anthropic
83
+ # 或
84
+ glm-quota-line config set base-url https://api.z.ai/api/anthropic
120
85
  ```
121
86
 
122
- Clear them later with:
87
+ 清除:`glm-quota-line config unset auth-token` / `base-url`。手动保存的值优先于 Claude 注入的环境变量。
123
88
 
124
- ```bash
125
- glm-quota-line config unset auth-token
126
- glm-quota-line config unset base-url
127
- ```
128
-
129
- Stored overrides take precedence over Claude-injected environment variables. `config show` redacts the token, but the local config file still stores the real value in plain text.
130
-
131
- ## Commands
89
+ ## 命令
132
90
 
133
91
  ```bash
134
92
  glm-quota-line [--style text|compact|bar] [--display left|used|both]
@@ -136,27 +94,24 @@ glm-quota-line [--style text|compact|bar] [--display left|used|both]
136
94
 
137
95
  glm-quota-line install [--force]
138
96
  glm-quota-line uninstall
97
+ glm-quota-line version
98
+ glm-quota-line check-update
139
99
  glm-quota-line config show
140
- glm-quota-line config set style <text|compact|bar>
141
- glm-quota-line config set display <left|used|both>
142
- glm-quota-line config set theme <plain|ansi>
143
- glm-quota-line config set palette <dark|mono>
144
- glm-quota-line config set auth-token <token>
145
- glm-quota-line config set base-url <url>
146
- glm-quota-line config unset <style|display|theme|palette|auth-token|base-url>
100
+ glm-quota-line config set <style|display|theme|palette|auth-token|base-url> <value>
101
+ glm-quota-line config unset <key>
147
102
  ```
148
103
 
149
- Run `glm-quota-line --help` for the full command descriptions and examples.
104
+ 完整说明运行 `glm-quota-line --help`。
105
+
106
+ `glm-quota-line --version` 可直接查看当前安装版本;`check-update` 只检查并提示升级命令,不会自动修改环境。
150
107
 
151
- ## Notes
108
+ ## 说明
152
109
 
153
- - Default output is plain text
154
- - Missing or expired auth returns `GLM | auth expired`
155
- - API or parsing failures return `GLM | quota unavailable`
156
- - `install` wires both the status line command and managed `SessionStart` hooks
157
- - `install` does not replace an unmanaged Claude status line unless `--force` is used
158
- - `install --force` backs up the previous entry and `uninstall` restores it when possible
110
+ - 只展示 `TOKENS_LIMIT` 配额,忽略 `TIME_LIMIT` / MCP 使用量
111
+ - 鉴权缺失返回 `GLM | auth expired`;接口异常返回 `GLM | quota unavailable`
112
+ - `install` 默认不会覆盖非本工具管理的状态栏,除非使用 `--force`
113
+ - `install --force` 会备份旧配置,`uninstall` 会在可能时恢复
159
114
 
160
- ## License
115
+ ## 许可证
161
116
 
162
117
  [MIT](./LICENSE)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "glm-quota-line",
3
- "version": "0.5.0",
3
+ "version": "0.7.0",
4
4
  "description": "Claude Code status line helper for GLM Coding Plan quota.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -27,7 +27,7 @@
27
27
  "files": [
28
28
  "src",
29
29
  "README.md",
30
- "README.zh-CN.md",
30
+ "README.en.md",
31
31
  "LICENSE",
32
32
  "CHANGELOG.md"
33
33
  ],
package/src/cli/args.js CHANGED
@@ -26,6 +26,11 @@ export function parseArgs(argv = process.argv.slice(2)) {
26
26
  continue;
27
27
  }
28
28
 
29
+ if (arg === "--version" || arg === "-v") {
30
+ options.version = true;
31
+ continue;
32
+ }
33
+
29
34
  if (arg === "--force") {
30
35
  options.force = true;
31
36
  continue;
@@ -67,4 +72,3 @@ export function parseArgs(argv = process.argv.slice(2)) {
67
72
  options.positionals = positionals;
68
73
  return options;
69
74
  }
70
-
@@ -13,6 +13,8 @@ import {
13
13
  } from "../claude/settings.js";
14
14
  import { installClaudeStatusLine, uninstallClaudeStatusLine } from "../claude/install.js";
15
15
  import { refreshQuotaOnSessionStart } from "../claude/sessionStart.js";
16
+ import { checkForUpdates } from "./update.js";
17
+ import { getPackageVersion } from "../shared/packageInfo.js";
16
18
 
17
19
  function isNonEmptyString(value) {
18
20
  return typeof value === "string" && value.trim().length > 0;
@@ -66,9 +68,36 @@ const CONFIG_KEYS = {
66
68
  }
67
69
  };
68
70
 
69
- export async function handleCommand(args, output = process.stdout) {
71
+ export async function handleCommand(args, output = process.stdout, dependencies = {}) {
72
+ const getVersion = dependencies.getVersion || getPackageVersion;
73
+ const runUpdateCheck = dependencies.runUpdateCheck || checkForUpdates;
70
74
  const [command, subcommand, key, value] = args.positionals;
71
75
 
76
+ if (command === "version") {
77
+ output.write(`glm-quota-line ${await getVersion()}\n`);
78
+ return true;
79
+ }
80
+
81
+ if (command === "check-update") {
82
+ const result = await runUpdateCheck();
83
+ output.write(`glm-quota-line ${result.currentVersion}\n`);
84
+
85
+ if (result.status === "up-to-date") {
86
+ output.write(`latest: ${result.latestVersion}\nstatus: up to date\n`);
87
+ return true;
88
+ }
89
+
90
+ if (result.status === "update-available") {
91
+ output.write(
92
+ `latest: ${result.latestVersion}\nstatus: update available\nupgrade: ${result.upgradeCommand}\n`
93
+ );
94
+ return true;
95
+ }
96
+
97
+ output.write(`status: unable to check updates\nreason: ${result.errorMessage}\n`);
98
+ return true;
99
+ }
100
+
72
101
  if (command === "install") {
73
102
  const result = await installClaudeStatusLine(undefined, undefined, undefined, {
74
103
  force: Boolean(args.force)
package/src/cli/index.js CHANGED
@@ -7,6 +7,7 @@ import { formatStatus } from "../core/status/format.js";
7
7
  import { readStatusLineInput } from "../claude/input.js";
8
8
  import { readToolConfig } from "../claude/settings.js";
9
9
  import { resolveQuotaStatus } from "../core/quota/service.js";
10
+ import { getPackageVersion } from "../shared/packageInfo.js";
10
11
  import {
11
12
  isValidDisplayMode,
12
13
  isValidPalette,
@@ -20,8 +21,11 @@ function printHelp() {
20
21
  Usage:
21
22
  glm-quota-line [--style text|compact|bar] [--display left|used|both]
22
23
  [--theme plain|ansi] [--palette dark|mono]
24
+ glm-quota-line --version
23
25
  glm-quota-line install [--force]
24
26
  glm-quota-line uninstall
27
+ glm-quota-line version
28
+ glm-quota-line check-update
25
29
  glm-quota-line config set style <text|compact|bar>
26
30
  glm-quota-line config set display <left|used|both>
27
31
  glm-quota-line config set theme <plain|ansi>
@@ -35,6 +39,8 @@ Commands:
35
39
  install Install glm-quota-line into Claude Code statusLine.command and SessionStart hooks.
36
40
  install --force Replace an existing unmanaged status line and back it up.
37
41
  uninstall Remove the managed status line and SessionStart hooks, and restore a backup if one exists.
42
+ version Print the installed glm-quota-line version.
43
+ check-update Check npm for a newer version and print the upgrade command.
38
44
  config show Print the current persisted config. Stored tokens are redacted.
39
45
  config set ... Persist a display option or manual credential override.
40
46
  config unset ... Remove one persisted config key.
@@ -45,11 +51,14 @@ Options:
45
51
  --theme Color mode: plain or ansi.
46
52
  --palette ANSI palette: dark for dark terminals, mono for light terminals.
47
53
  --force Allow install to replace an unmanaged Claude status line.
54
+ -v, --version Show the installed version.
48
55
  -h, --help Show this help text.
49
56
 
50
57
  Examples:
51
58
  glm-quota-line
59
+ glm-quota-line --version
52
60
  glm-quota-line --style bar --theme ansi --palette dark
61
+ glm-quota-line check-update
53
62
  glm-quota-line config set style compact
54
63
  glm-quota-line config set theme ansi
55
64
  glm-quota-line config set auth-token <your-real-token>
@@ -101,6 +110,11 @@ export async function main() {
101
110
  return;
102
111
  }
103
112
 
113
+ if (args.version) {
114
+ process.stdout.write(`glm-quota-line ${await getPackageVersion()}\n`);
115
+ return;
116
+ }
117
+
104
118
  if (await handleCommand(args)) {
105
119
  return;
106
120
  }
@@ -0,0 +1,120 @@
1
+ import { readPackageInfo } from "../shared/packageInfo.js";
2
+
3
+ const DEFAULT_TIMEOUT_MS = 5000;
4
+ const DEFAULT_REGISTRY_URL = "https://registry.npmjs.org";
5
+
6
+ function parseVersion(version) {
7
+ const match = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?$/.exec(version);
8
+ if (!match) {
9
+ return null;
10
+ }
11
+
12
+ return {
13
+ major: Number.parseInt(match[1], 10),
14
+ minor: Number.parseInt(match[2], 10),
15
+ patch: Number.parseInt(match[3], 10),
16
+ prerelease: match[4] || ""
17
+ };
18
+ }
19
+
20
+ export function compareVersions(left, right) {
21
+ const parsedLeft = parseVersion(left);
22
+ const parsedRight = parseVersion(right);
23
+
24
+ if (!parsedLeft || !parsedRight) {
25
+ return left === right ? 0 : left.localeCompare(right);
26
+ }
27
+
28
+ for (const key of ["major", "minor", "patch"]) {
29
+ if (parsedLeft[key] !== parsedRight[key]) {
30
+ return parsedLeft[key] - parsedRight[key];
31
+ }
32
+ }
33
+
34
+ if (parsedLeft.prerelease === parsedRight.prerelease) {
35
+ return 0;
36
+ }
37
+
38
+ if (!parsedLeft.prerelease) {
39
+ return 1;
40
+ }
41
+
42
+ if (!parsedRight.prerelease) {
43
+ return -1;
44
+ }
45
+
46
+ return parsedLeft.prerelease.localeCompare(parsedRight.prerelease);
47
+ }
48
+
49
+ function normalizeRegistryUrl(value) {
50
+ return value.replace(/\/+$/u, "");
51
+ }
52
+
53
+ function toErrorMessage(error) {
54
+ if (error?.name === "AbortError") {
55
+ return "npm registry request timed out";
56
+ }
57
+
58
+ if (error?.name === "TypeError" && error?.message === "fetch failed") {
59
+ return "npm registry request failed";
60
+ }
61
+
62
+ if (typeof error?.message === "string" && error.message.trim()) {
63
+ return error.message.trim();
64
+ }
65
+
66
+ return "npm registry request failed";
67
+ }
68
+
69
+ export async function checkForUpdates(options = {}) {
70
+ const fetchImpl = options.fetchImpl || globalThis.fetch;
71
+ if (typeof fetchImpl !== "function") {
72
+ throw new Error("fetch is not available in this Node.js runtime");
73
+ }
74
+
75
+ const packageInfo = options.packageInfo || (await readPackageInfo());
76
+ const packageName = packageInfo.name || "glm-quota-line";
77
+ const currentVersion = packageInfo.version || "0.0.0";
78
+ const registryUrl = normalizeRegistryUrl(options.registryUrl || DEFAULT_REGISTRY_URL);
79
+ const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
80
+
81
+ const controller = new AbortController();
82
+ const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
83
+
84
+ try {
85
+ const response = await fetchImpl(`${registryUrl}/${encodeURIComponent(packageName)}/latest`, {
86
+ headers: {
87
+ accept: "application/json"
88
+ },
89
+ signal: controller.signal
90
+ });
91
+
92
+ if (!response.ok) {
93
+ throw new Error(`npm registry returned HTTP ${response.status}`);
94
+ }
95
+
96
+ const payload = await response.json();
97
+ const latestVersion = typeof payload?.version === "string" ? payload.version.trim() : "";
98
+ if (!latestVersion) {
99
+ throw new Error("npm registry response did not include a version");
100
+ }
101
+
102
+ return {
103
+ packageName,
104
+ currentVersion,
105
+ latestVersion,
106
+ status: compareVersions(currentVersion, latestVersion) < 0 ? "update-available" : "up-to-date",
107
+ upgradeCommand: `npm install -g ${packageName}`
108
+ };
109
+ } catch (error) {
110
+ return {
111
+ packageName,
112
+ currentVersion,
113
+ latestVersion: null,
114
+ status: "error",
115
+ errorMessage: toErrorMessage(error)
116
+ };
117
+ } finally {
118
+ clearTimeout(timeoutId);
119
+ }
120
+ }
@@ -1,24 +1,72 @@
1
1
  import fs from "node:fs/promises";
2
2
  import path from "node:path";
3
3
 
4
- function isSuccessCacheShape(value) {
4
+ function isValidQuotaShape(value) {
5
+ if (!value || typeof value.key !== "string") {
6
+ return false;
7
+ }
8
+
9
+ if (!Number.isFinite(value.leftPercent)) {
10
+ return false;
11
+ }
12
+
13
+ if (!Number.isFinite(value.usedPercent)) {
14
+ return false;
15
+ }
16
+
17
+ if ("nextResetTime" in value && !Number.isFinite(value.nextResetTime)) {
18
+ return false;
19
+ }
20
+
21
+ return true;
22
+ }
23
+
24
+ function isLegacyPercentSuccessCacheShape(value) {
25
+ if (!value || value.kind !== "success" || typeof value.level !== "string") {
26
+ return false;
27
+ }
28
+
29
+ if (value.display !== "percent") {
30
+ return false;
31
+ }
32
+
33
+ if (!Number.isFinite(value.leftPercent)) {
34
+ return false;
35
+ }
36
+
37
+ if ("usedPercent" in value && !Number.isFinite(value.usedPercent)) {
38
+ return false;
39
+ }
40
+
41
+ if ("nextResetTime" in value && !Number.isFinite(value.nextResetTime)) {
42
+ return false;
43
+ }
44
+
45
+ return true;
46
+ }
47
+
48
+ function isMultiQuotaSuccessCacheShape(value) {
5
49
  if (!value || value.kind !== "success" || typeof value.level !== "string") {
6
50
  return false;
7
51
  }
8
52
 
9
- if (!Number.isFinite(value.nextResetTime)) {
53
+ if (!Array.isArray(value.quotas) || value.quotas.length === 0) {
10
54
  return false;
11
55
  }
12
56
 
13
- if (value.display === "percent") {
14
- return Number.isFinite(value.leftPercent);
57
+ if (!value.quotas.every(isValidQuotaShape)) {
58
+ return false;
15
59
  }
16
60
 
17
- if (value.display === "absolute") {
18
- return Number.isFinite(value.remaining) && Number.isFinite(value.total);
61
+ if ("primaryQuotaKey" in value && typeof value.primaryQuotaKey !== "string") {
62
+ return false;
19
63
  }
20
64
 
21
- return false;
65
+ return true;
66
+ }
67
+
68
+ function isSuccessCacheShape(value) {
69
+ return isMultiQuotaSuccessCacheShape(value) || isLegacyPercentSuccessCacheShape(value);
22
70
  }
23
71
 
24
72
  function asFiniteNumber(value) {