aihezu 2.8.9 → 2.8.10

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.
Files changed (2) hide show
  1. package/commands/usage.js +21 -153
  2. package/package.json +1 -1
package/commands/usage.js CHANGED
@@ -1,7 +1,7 @@
1
1
  const fs = require('fs');
2
2
  const os = require('os');
3
3
  const path = require('path');
4
- const { postJson, getJson } = require('../lib/http');
4
+ const { getJson } = require('../lib/http');
5
5
  const platform = require('../lib/platform');
6
6
 
7
7
  function normalizeHttpUrl(url) {
@@ -295,9 +295,9 @@ function usageHint(current, limit) {
295
295
  return '';
296
296
  }
297
297
 
298
- function getCodexUsageBase(baseUrl) {
299
- // 用户的 Codex base_url 形如 https://xxx.aihezu.dev/openai 或 https://xxx.aihezu.dev
300
- // usage 接口位于 /v1/usage,需要去掉可能存在的 /openai 后缀
298
+ function getUsageApiBase(baseUrl) {
299
+ // 去掉路径中的 /openai 后缀(Codex base_url 常见格式 https://xxx/openai
300
+ // Claude base_url 不会带,等价于 no-op)
301
301
  try {
302
302
  const url = new URL(baseUrl);
303
303
  let pathname = url.pathname.replace(/\/+$/, '');
@@ -310,17 +310,24 @@ function getCodexUsageBase(baseUrl) {
310
310
  }
311
311
  }
312
312
 
313
- async function queryCodexUsage(baseUrl, authToken) {
313
+ async function queryUnifiedUsage(baseUrl, authToken) {
314
314
  try {
315
- const apiBase = getCodexUsageBase(baseUrl);
315
+ const apiBase = getUsageApiBase(baseUrl);
316
316
  const usageUrl = `${apiBase}/v1/usage`;
317
317
  const res = await getJson(usageUrl, {
318
318
  headers: { Authorization: `Bearer ${authToken}` }
319
319
  });
320
320
 
321
321
  if (res.statusCode < 200 || res.statusCode >= 300) {
322
- const msg = res.json && (res.json.message || res.json.error || res.json.code);
323
- return { error: msg ? `HTTP ${res.statusCode} - ${msg}` : `HTTP ${res.statusCode}` };
322
+ let detail = '';
323
+ const j = res.json;
324
+ if (j) {
325
+ if (typeof j.message === 'string') detail = j.message;
326
+ else if (typeof j.error === 'string') detail = j.error;
327
+ else if (j.error && typeof j.error.message === 'string') detail = j.error.message;
328
+ else if (typeof j.code === 'string') detail = j.code;
329
+ }
330
+ return { error: detail ? `HTTP ${res.statusCode} - ${detail}` : `HTTP ${res.statusCode}` };
324
331
  }
325
332
 
326
333
  if (!res.json || typeof res.json !== 'object') {
@@ -333,7 +340,7 @@ async function queryCodexUsage(baseUrl, authToken) {
333
340
  }
334
341
  }
335
342
 
336
- function displayCodexUsageStats(stats, origin, source) {
343
+ function displayUnifiedUsageStats(stats, origin, source) {
337
344
  const subscription = stats.subscription || {};
338
345
  const today = (stats.usage && stats.usage.today) || {};
339
346
  const total = (stats.usage && stats.usage.total) || {};
@@ -456,141 +463,6 @@ function displayCodexUsageStats(stats, origin, source) {
456
463
  console.log('');
457
464
  }
458
465
 
459
- async function queryUsage(baseUrl, authToken) {
460
- try {
461
- const origin = new URL(baseUrl).origin;
462
- const keyIdUrl = `${origin}/apiStats/api/get-key-id`;
463
- const statsUrl = `${origin}/apiStats/api/user-stats`;
464
-
465
- const keyIdRes = await postJson(keyIdUrl, { apiKey: authToken });
466
- if (keyIdRes.statusCode < 200 || keyIdRes.statusCode >= 300) {
467
- return { error: `HTTP ${keyIdRes.statusCode}` };
468
- }
469
-
470
- const apiId = keyIdRes?.json?.data?.id || keyIdRes?.json?.id || null;
471
- if (!apiId) {
472
- return { error: '未找到 API Key ID' };
473
- }
474
-
475
- const statsRes = await postJson(statsUrl, { apiId });
476
- if (statsRes.statusCode < 200 || statsRes.statusCode >= 300) {
477
- return { error: `HTTP ${statsRes.statusCode}` };
478
- }
479
-
480
- if (!statsRes.json || typeof statsRes.json !== 'object') {
481
- return { error: '返回内容不是合法的 JSON' };
482
- }
483
-
484
- const stats = (statsRes?.json?.data) ? statsRes.json.data : (statsRes.json || {});
485
- return { stats, origin };
486
- } catch (error) {
487
- return { error: error.message };
488
- }
489
- }
490
-
491
- function displayUsageStats(stats, origin, source) {
492
- const limits = (stats && typeof stats === 'object' && stats.limits && typeof stats.limits === 'object')
493
- ? stats.limits
494
- : {};
495
-
496
- const currentDailyCost = (stats.currentDailyCost !== undefined) ? stats.currentDailyCost : limits.currentDailyCost;
497
- const dailyCostLimit = (stats.dailyCostLimit !== undefined) ? stats.dailyCostLimit : limits.dailyCostLimit;
498
- const currentWindowCost = (stats.currentWindowCost !== undefined) ? stats.currentWindowCost : limits.currentWindowCost;
499
- const rateLimitCost = (stats.rateLimitCost !== undefined) ? stats.rateLimitCost : limits.rateLimitCost;
500
- const windowStartTime = (stats.windowStartTime !== undefined) ? stats.windowStartTime : limits.windowStartTime;
501
- const windowEndTime = (stats.windowEndTime !== undefined) ? stats.windowEndTime : limits.windowEndTime;
502
- const windowRemainingSeconds = (() => {
503
- const raw = (stats.windowRemainingSeconds !== undefined)
504
- ? stats.windowRemainingSeconds
505
- : limits.windowRemainingSeconds;
506
- const provided = asNumber(raw);
507
- if (provided !== null) return provided;
508
- const endMs = normalizeEpochMs(windowEndTime);
509
- if (endMs === null) return null;
510
- return Math.max(0, Math.floor((endMs - Date.now()) / 1000));
511
- })();
512
- const windowDurationMinutes = (() => {
513
- const startMs = normalizeEpochMs(windowStartTime);
514
- const endMs = normalizeEpochMs(windowEndTime);
515
- if (startMs !== null && endMs !== null && endMs > startMs) {
516
- return Math.round((endMs - startMs) / 60000);
517
- }
518
- const raw = (stats.rateLimitWindow !== undefined) ? stats.rateLimitWindow : limits.rateLimitWindow;
519
- const minutes = asNumber(raw);
520
- if (minutes !== null) return minutes;
521
- return null;
522
- })();
523
-
524
- const dailyRemaining = (() => {
525
- const c = asNumber(currentDailyCost);
526
- const l = asNumber(dailyCostLimit);
527
- if (c === null || l === null) return null;
528
- return l - c;
529
- })();
530
-
531
- const windowRemaining = (() => {
532
- const c = asNumber(currentWindowCost);
533
- const l = asNumber(rateLimitCost);
534
- if (c === null || l === null) return null;
535
- return l - c;
536
- })();
537
-
538
- // 提取日请求次数和 Token 用量(API 中 currentWindowRequests/Tokens 实际为日数据)
539
- const currentDailyRequests = (stats.currentDailyRequests !== undefined) ? stats.currentDailyRequests
540
- : (limits.currentDailyRequests !== undefined) ? limits.currentDailyRequests
541
- : (stats.currentWindowRequests !== undefined) ? stats.currentWindowRequests : limits.currentWindowRequests;
542
- const currentDailyTokens = (stats.currentDailyTokens !== undefined) ? stats.currentDailyTokens
543
- : (limits.currentDailyTokens !== undefined) ? limits.currentDailyTokens
544
- : (stats.currentWindowTokens !== undefined) ? stats.currentWindowTokens : limits.currentWindowTokens;
545
-
546
- console.log(`域名: ${origin}`);
547
- console.log(`来源: ${source}`);
548
- console.log('');
549
- console.log(
550
- `日额度: ${formatCost(currentDailyCost)} / ${formatCost(dailyCostLimit)} (${formatPercent(currentDailyCost, dailyCostLimit)}) ` +
551
- `${renderBar(currentDailyCost, dailyCostLimit)} ${usageHint(currentDailyCost, dailyCostLimit)}`.trimEnd()
552
- );
553
- if (dailyRemaining !== null) {
554
- console.log(`日剩余: ${formatCost(dailyRemaining)}`);
555
- }
556
- if (asNumber(currentDailyRequests) !== null || asNumber(currentDailyTokens) !== null) {
557
- const parts = [];
558
- if (asNumber(currentDailyRequests) !== null) parts.push(`请求 ${formatNumber(currentDailyRequests)} 次`);
559
- if (asNumber(currentDailyTokens) !== null) parts.push(`Token ${formatCompactNumber(currentDailyTokens)}`);
560
- console.log(`日统计: ${parts.join(' | ')}`);
561
- }
562
-
563
- console.log('');
564
- console.log(
565
- `窗口: ${formatCost(currentWindowCost)} / ${formatCost(rateLimitCost)} (${formatPercent(currentWindowCost, rateLimitCost)}) ` +
566
- `${renderBar(currentWindowCost, rateLimitCost)} ${usageHint(currentWindowCost, rateLimitCost)}`.trimEnd()
567
- );
568
- if (windowRemaining !== null) {
569
- console.log(`窗口剩余: ${formatCost(windowRemaining)}`);
570
- }
571
- const windowStartText = formatDateTime(windowStartTime);
572
- const windowEndText = formatDateTime(windowEndTime);
573
- if (windowStartText !== '-' || windowEndText !== '-') {
574
- if (windowStartText !== '-' && windowEndText !== '-') {
575
- console.log(`时间窗口: ${windowStartText} ~ ${windowEndText}`);
576
- } else if (windowStartText !== '-') {
577
- console.log(`时间窗口开始: ${windowStartText}`);
578
- } else {
579
- console.log(`时间窗口结束: ${windowEndText}`);
580
- }
581
- }
582
- const windowDurationText = formatDurationMinutes(windowDurationMinutes);
583
- if (windowDurationText !== '-') {
584
- console.log(`周时间窗口: ${windowDurationText}`);
585
- }
586
- const resetCountdownText = formatDurationSeconds(windowRemainingSeconds);
587
- if (resetCountdownText !== '-') {
588
- console.log(`窗口剩余时间: ${resetCountdownText} (距重置)`);
589
- }
590
-
591
- console.log('');
592
- }
593
-
594
466
  function showUsageHelp() {
595
467
  console.log('用法: aihezu usage [service] [--json] [--key <apiKey>]');
596
468
  console.log('');
@@ -688,10 +560,10 @@ async function usageCommand(args = []) {
688
560
  const effectiveKey = overrideKey || config.authToken;
689
561
  const sourceLabel = overrideKey ? `命令行 Key (${config.source})` : config.source;
690
562
  console.log(`[查询中] ${sourceLabel} - Token: ${maskToken(effectiveKey)}`);
691
- const promise = service.name === 'Codex'
692
- ? queryCodexUsage(config.baseUrl, effectiveKey)
693
- : queryUsage(config.baseUrl, effectiveKey);
694
- return { sourceLabel, promise };
563
+ return {
564
+ sourceLabel,
565
+ promise: queryUnifiedUsage(config.baseUrl, effectiveKey)
566
+ };
695
567
  });
696
568
 
697
569
  const results = await Promise.allSettled(requests.map(item => item.promise));
@@ -712,11 +584,7 @@ async function usageCommand(args = []) {
712
584
  });
713
585
  } else {
714
586
  if (!outputJson) {
715
- if (service.name === 'Codex') {
716
- displayCodexUsageStats(result.stats, result.origin, sourceLabel);
717
- } else {
718
- displayUsageStats(result.stats, result.origin, sourceLabel);
719
- }
587
+ displayUnifiedUsageStats(result.stats, result.origin, sourceLabel);
720
588
  }
721
589
  allResults[service.name].push({
722
590
  source: sourceLabel,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aihezu",
3
- "version": "2.8.9",
3
+ "version": "2.8.10",
4
4
  "description": "AI 开发环境配置工具 - 支持 Claude Code, Codex, Google Gemini 的本地化配置、代理设置与缓存清理",
5
5
  "main": "bin/aihezu.js",
6
6
  "bin": {