ccus-cli 0.1.24 → 0.1.26
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/dist/cli.js +14 -3
- package/dist/lib/api-mode.js +68 -9
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -982,7 +982,7 @@ async function handleApiConfig(options) {
|
|
|
982
982
|
` provider: ${next.provider}`,
|
|
983
983
|
` token 环境变量: ${next.tokenEnv}`,
|
|
984
984
|
` token 兜底: ${maskSecret(next.token)}`,
|
|
985
|
-
` 当前生效 token: ${maskSecret((0, api_mode_1.
|
|
985
|
+
` 当前生效 token: ${maskSecret((0, api_mode_1.resolveApiTokenWithSettings)(next))}`,
|
|
986
986
|
` 缓存 TTL: ${formatDurationMs(next.cacheTtlMs)}`,
|
|
987
987
|
` 请求超时: ${formatDurationMs(next.timeoutMs)}`,
|
|
988
988
|
` User-Agent: ${next.userAgent}`,
|
|
@@ -1011,8 +1011,19 @@ async function handleApiTest(options) {
|
|
|
1011
1011
|
return;
|
|
1012
1012
|
}
|
|
1013
1013
|
process.stdout.write(`正在用 provider=${config.provider} 拉取额度(超时 ${formatDurationMs(config.timeoutMs)})...\n`);
|
|
1014
|
+
// 环境变量和 config.token 都拿不到 token 时,回退到 ~/.claude/settings.json 的 env 字段。
|
|
1015
|
+
// 手动跑 api test 时不在 Claude Code 进程树下,环境变量里通常没有 ANTHROPIC_AUTH_TOKEN,
|
|
1016
|
+
// 而 settings.json 的 env 是 Claude Code 持久化这些变量的地方,正好作为来源。
|
|
1017
|
+
let env = process.env;
|
|
1018
|
+
if (!(0, api_mode_1.resolveApiToken)(config, env)) {
|
|
1019
|
+
const settingsToken = (0, api_mode_1.readClaudeSettingsEnvTokenSync)(config.tokenEnv);
|
|
1020
|
+
if (settingsToken) {
|
|
1021
|
+
env = { ...process.env, [config.tokenEnv]: settingsToken };
|
|
1022
|
+
(0, debug_1.debugLog)("api-mode", `token from settings.json env.${config.tokenEnv}`);
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1014
1025
|
try {
|
|
1015
|
-
const quota = await (0, api_mode_1.fetchQuota)(config,
|
|
1026
|
+
const quota = await (0, api_mode_1.fetchQuota)(config, env);
|
|
1016
1027
|
const fiveHour = quota.fiveHour !== null ? `${quota.fiveHour.toFixed(1)}%` : "--";
|
|
1017
1028
|
const sevenDay = quota.sevenDay !== null ? `${quota.sevenDay.toFixed(1)}%` : "--";
|
|
1018
1029
|
process.stdout.write(`✅ 5h ${fiveHour} | 7d ${sevenDay}${quota.level ? ` | level ${quota.level}` : ""}\n`);
|
|
@@ -1042,7 +1053,7 @@ async function handleApiStatus(options) {
|
|
|
1042
1053
|
const lines = [
|
|
1043
1054
|
` 启用: ${config.enabled ? "是" : "否"}`,
|
|
1044
1055
|
` provider: ${config.provider}`,
|
|
1045
|
-
` 当前生效 token: ${maskSecret((0, api_mode_1.
|
|
1056
|
+
` 当前生效 token: ${maskSecret((0, api_mode_1.resolveApiTokenWithSettings)(config))}`,
|
|
1046
1057
|
` 缓存 TTL: ${formatDurationMs(config.cacheTtlMs)}`,
|
|
1047
1058
|
` 缓存状态: ${ageLabel}`,
|
|
1048
1059
|
];
|
package/dist/lib/api-mode.js
CHANGED
|
@@ -8,6 +8,8 @@ exports.defaultApiConfig = defaultApiConfig;
|
|
|
8
8
|
exports.readApiConfig = readApiConfig;
|
|
9
9
|
exports.writeApiConfig = writeApiConfig;
|
|
10
10
|
exports.resolveApiToken = resolveApiToken;
|
|
11
|
+
exports.readClaudeSettingsEnvTokenSync = readClaudeSettingsEnvTokenSync;
|
|
12
|
+
exports.resolveApiTokenWithSettings = resolveApiTokenWithSettings;
|
|
11
13
|
exports.extractCustomQuota = extractCustomQuota;
|
|
12
14
|
exports.runExtractor = runExtractor;
|
|
13
15
|
exports.normalizeExtractorResult = normalizeExtractorResult;
|
|
@@ -117,6 +119,41 @@ function resolveApiToken(config, env = process.env) {
|
|
|
117
119
|
}
|
|
118
120
|
return typeof config.token === "string" && config.token.trim() !== "" ? config.token : null;
|
|
119
121
|
}
|
|
122
|
+
/**
|
|
123
|
+
* 从 ~/.claude/settings.json 的 env 字段读 tokenEnv 指定的那个键。
|
|
124
|
+
*
|
|
125
|
+
* Claude Code 启动子进程时会把 settings.json 的 env 注入环境变量,但用户在终端手动跑
|
|
126
|
+
* `ccus api test` 时不在 Claude Code 进程树下,环境变量里通常没有 ANTHROPIC_AUTH_TOKEN ——
|
|
127
|
+
* 这时回退到 settings.json(env 是 Claude Code 持久化这些变量的地方)读取。
|
|
128
|
+
* 只读 env 字段(apiKeyHelper 需要 spawn 子进程、有副作用,不在此支持)。任何缺失/异常返回 null。
|
|
129
|
+
*/
|
|
130
|
+
function readClaudeSettingsEnvTokenSync(tokenEnv, settingsPath = (0, paths_1.getClaudeSettingsPath)()) {
|
|
131
|
+
try {
|
|
132
|
+
const raw = node_fs_1.default.readFileSync(settingsPath, "utf8");
|
|
133
|
+
const parsed = JSON.parse(raw);
|
|
134
|
+
if (!isRecord(parsed)) {
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
const envField = parsed.env;
|
|
138
|
+
if (!isRecord(envField)) {
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
const value = envField[tokenEnv];
|
|
142
|
+
return typeof value === "string" && value.trim() !== "" ? value : null;
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* 三层回退解析 token:环境变量 → config.token → ~/.claude/settings.json 的 env 字段。
|
|
150
|
+
*
|
|
151
|
+
* 仅供 `api test` / `api status` / `api config` 这类手动命令显示与判断用;statusline 高频路径
|
|
152
|
+
* 仍走纯 `resolveApiToken`(不读文件),行为不变。
|
|
153
|
+
*/
|
|
154
|
+
function resolveApiTokenWithSettings(config, env = process.env) {
|
|
155
|
+
return resolveApiToken(config, env) ?? readClaudeSettingsEnvTokenSync(config.tokenEnv);
|
|
156
|
+
}
|
|
120
157
|
/** 把 header 值里的 {{token}} / {{apikey}} 占位符替换成实际 token;token 为空则替换为空串。 */
|
|
121
158
|
function renderHeader(value, token) {
|
|
122
159
|
return value.replaceAll(/{{\s*(token|apikey)\s*}}/gi, token ?? "");
|
|
@@ -124,9 +161,11 @@ function renderHeader(value, token) {
|
|
|
124
161
|
/**
|
|
125
162
|
* 智谱 GLM Coding Plan 的内置 extractor 脚本(zhipu provider 的预设)。
|
|
126
163
|
*
|
|
127
|
-
* data.limits 里筛 type==="TOKENS_LIMIT"
|
|
128
|
-
*
|
|
129
|
-
*
|
|
164
|
+
* data.limits 里筛 type==="TOKENS_LIMIT",用 number 字段识别桶位:5h 桶 number===5、周桶 number===1。
|
|
165
|
+
* 不能用 nextResetTime 大小排序区分——5h 与周窗口的重置时刻互不相关,实测周桶 nextResetTime 可早于 5h,
|
|
166
|
+
* 升序会把周桶当 5h、5h 当周桶,5h/7d 互换(曾因「缺 nextResetTime 归 5h」启发式反复互换)。
|
|
167
|
+
* 5h 桶 = number===5 的那条,周桶 = 余下第一条;number 缺失(老接口)才退回 nextResetTime 稳定排序
|
|
168
|
+
* (有值优先、缺值按原序,不强制归前/后)。对照 deluo/glm-quota-line 的同类实现。
|
|
130
169
|
* success !== true / code !== 200 / 缺 data.limits 时返回会让归一化判 null 的值。
|
|
131
170
|
* zhipu 不再单独维护提取函数,与 custom 走同一条 `runExtractor` 路径,只是 extractor 用这组内置脚本。
|
|
132
171
|
*/
|
|
@@ -140,13 +179,33 @@ exports.ZHIPU_EXTRACTOR = `function(response) {
|
|
|
140
179
|
if (!Array.isArray(limits)) return null;
|
|
141
180
|
const tokenLimits = limits
|
|
142
181
|
.filter((l) => l && l.type === "TOKENS_LIMIT")
|
|
143
|
-
.map((l) => ({ p: l.percentage, r: l.nextResetTime }))
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
182
|
+
.map((l, i) => ({ p: l.percentage, n: l.number, r: l.nextResetTime, i }));
|
|
183
|
+
if (tokenLimits.length === 0) return null;
|
|
184
|
+
// 5h 桶 number===5、周桶 number===1。nextResetTime 大小不可靠(周桶重置可早于 5h),
|
|
185
|
+
// 必须用 number 显式识别 5h,否则两槽位互换。
|
|
186
|
+
const fiveHour = tokenLimits.find((l) => l.n === 5);
|
|
187
|
+
let first, second;
|
|
188
|
+
if (fiveHour) {
|
|
189
|
+
first = fiveHour;
|
|
190
|
+
second = tokenLimits.find((l) => l !== fiveHour);
|
|
191
|
+
} else if (tokenLimits.length === 1) {
|
|
192
|
+
first = tokenLimits[0];
|
|
193
|
+
} else {
|
|
194
|
+
// 老接口无 number:退回 nextResetTime 稳定排序(有值优先,缺值按原序,不强制归前/后)。
|
|
195
|
+
tokenLimits.sort((a, b) => {
|
|
196
|
+
const ar = typeof a.r === "number";
|
|
197
|
+
const br = typeof b.r === "number";
|
|
198
|
+
if (ar && br && a.r !== b.r) return a.r - b.r;
|
|
199
|
+
if (ar && !br) return -1;
|
|
200
|
+
if (!ar && br) return 1;
|
|
201
|
+
return a.i - b.i;
|
|
202
|
+
});
|
|
203
|
+
first = tokenLimits[0];
|
|
204
|
+
second = tokenLimits[1];
|
|
205
|
+
}
|
|
147
206
|
return {
|
|
148
|
-
fiveHour:
|
|
149
|
-
sevenDay:
|
|
207
|
+
fiveHour: first ? first.p : null,
|
|
208
|
+
sevenDay: second ? second.p : null,
|
|
150
209
|
level: typeof data.level === "string" && data.level !== "" ? data.level : null,
|
|
151
210
|
};
|
|
152
211
|
}`;
|