aixxai 1.4.3 → 1.4.4
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 +45 -45
- package/bin/aixx.js +88 -88
- package/package.json +1 -1
- package/src/commands/balance.js +147 -147
- package/src/commands/config.js +68 -68
- package/src/commands/help.js +43 -43
- package/src/commands/install.js +383 -383
- package/src/commands/models.js +140 -140
- package/src/commands/recharge.js +131 -131
- package/src/commands/test.js +101 -101
- package/src/utils/download.js +147 -147
- package/src/utils/env.js +136 -136
- package/templates/QUICKSTART.md +100 -100
- package/templates/SKILL.md +137 -137
- package/templates/references/account.md +68 -68
- package/templates/references/apps.md +208 -208
- package/templates/references/chat.md +135 -133
- package/templates/references/data.md +151 -151
- package/templates/references/notify.md +53 -53
- package/templates/references/search.md +96 -96
package/README.md
CHANGED
|
@@ -1,45 +1,45 @@
|
|
|
1
|
-
# AIXX CLI
|
|
2
|
-
|
|
3
|
-
> 让你的AI调用万物。一个命令,agent就能调用全世界的AI能力。
|
|
4
|
-
|
|
5
|
-
## 安装
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
npx aixxai install
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
带推荐码安装(推荐人获分润):
|
|
12
|
-
|
|
13
|
-
```bash
|
|
14
|
-
npx aixxai install zhangsan
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
## 命令
|
|
18
|
-
|
|
19
|
-
| 命令 | 说明 |
|
|
20
|
-
|---|---|
|
|
21
|
-
| `aixx install [推荐码]` | 安装AIXX skill到本地agent |
|
|
22
|
-
| `aixx config` | 查看当前配置 |
|
|
23
|
-
| `aixx test` | 测试AIXX连通性+调用模型 |
|
|
24
|
-
| `aixx --version` | 查看版本 |
|
|
25
|
-
| `aixx --help` | 帮助 |
|
|
26
|
-
|
|
27
|
-
## 当前状态(1.4.0)
|
|
28
|
-
|
|
29
|
-
- ✅ CLI代码完成,本地测试通过
|
|
30
|
-
- ⏳ 待发布到npm(等注册)
|
|
31
|
-
- ✅ 后端已部署:http://14.103.27.195:8080
|
|
32
|
-
- ✅ 已接通:DeepSeek / GLM / Kimi
|
|
33
|
-
|
|
34
|
-
## 技术栈
|
|
35
|
-
|
|
36
|
-
- Node.js (>=18) + ES Modules
|
|
37
|
-
- 纯原生API,零运行时依赖
|
|
38
|
-
- skill文件内嵌在包里(templates/),网络挂了也能装
|
|
39
|
-
|
|
40
|
-
## 文档
|
|
41
|
-
|
|
42
|
-
完整文档:https://gitee.com/kk0803/token-hub
|
|
43
|
-
|
|
44
|
-
---
|
|
45
|
-
维护者:龙龙(AIXX PM)| 2026-08-08
|
|
1
|
+
# AIXX CLI
|
|
2
|
+
|
|
3
|
+
> 让你的AI调用万物。一个命令,agent就能调用全世界的AI能力。
|
|
4
|
+
|
|
5
|
+
## 安装
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npx aixxai install
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
带推荐码安装(推荐人获分润):
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npx aixxai install zhangsan
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## 命令
|
|
18
|
+
|
|
19
|
+
| 命令 | 说明 |
|
|
20
|
+
|---|---|
|
|
21
|
+
| `aixx install [推荐码]` | 安装AIXX skill到本地agent |
|
|
22
|
+
| `aixx config` | 查看当前配置 |
|
|
23
|
+
| `aixx test` | 测试AIXX连通性+调用模型 |
|
|
24
|
+
| `aixx --version` | 查看版本 |
|
|
25
|
+
| `aixx --help` | 帮助 |
|
|
26
|
+
|
|
27
|
+
## 当前状态(1.4.0)
|
|
28
|
+
|
|
29
|
+
- ✅ CLI代码完成,本地测试通过
|
|
30
|
+
- ⏳ 待发布到npm(等注册)
|
|
31
|
+
- ✅ 后端已部署:http://14.103.27.195:8080
|
|
32
|
+
- ✅ 已接通:DeepSeek / GLM / Kimi
|
|
33
|
+
|
|
34
|
+
## 技术栈
|
|
35
|
+
|
|
36
|
+
- Node.js (>=18) + ES Modules
|
|
37
|
+
- 纯原生API,零运行时依赖
|
|
38
|
+
- skill文件内嵌在包里(templates/),网络挂了也能装
|
|
39
|
+
|
|
40
|
+
## 文档
|
|
41
|
+
|
|
42
|
+
完整文档:https://gitee.com/kk0803/token-hub
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
维护者:龙龙(AIXX PM)| 2026-08-08
|
package/bin/aixx.js
CHANGED
|
@@ -1,88 +1,88 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* AIXX CLI
|
|
5
|
-
* 让你的AI调用万物。
|
|
6
|
-
*
|
|
7
|
-
* 用法:
|
|
8
|
-
* npx aixxai install [推荐码] 安装AIXX skill到本地agent(含零配置)
|
|
9
|
-
* npx aixxai config 配置/查看AIXX
|
|
10
|
-
* npx aixxai test 测试AIXX是否可用
|
|
11
|
-
* npx aixxai balance 查询账户余额
|
|
12
|
-
* npx aixxai models 列出可用模型(分组)
|
|
13
|
-
* npx aixxai recharge [金额] 充值引导
|
|
14
|
-
* npx aixxai --version 查看版本
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
import { readFileSync } from 'fs';
|
|
18
|
-
import { fileURLToPath } from 'url';
|
|
19
|
-
import { dirname, join } from 'path';
|
|
20
|
-
import { install } from '../src/commands/install.js';
|
|
21
|
-
import { config } from '../src/commands/config.js';
|
|
22
|
-
import { test } from '../src/commands/test.js';
|
|
23
|
-
import { balance } from '../src/commands/balance.js';
|
|
24
|
-
import { models } from '../src/commands/models.js';
|
|
25
|
-
import { recharge } from '../src/commands/recharge.js';
|
|
26
|
-
import { showHelp, showVersion } from '../src/commands/help.js';
|
|
27
|
-
|
|
28
|
-
// 版本号从 package.json 读(单一来源,别再硬编码)
|
|
29
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
30
|
-
const VERSION = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf-8')).version;
|
|
31
|
-
|
|
32
|
-
// 默认AIXX后端地址
|
|
33
|
-
// ⚠️ 安全警告:当前用HTTP明文传输API Key(http://),存在凭据被嗅探风险。
|
|
34
|
-
// TODO(安全):后端配置好HTTPS证书后,把下面的 URL 改为 'https://14.103.27.195:8080/v1'。
|
|
35
|
-
// 改完后请全局搜索 "安全警告" 同步更新 env.js 与 templates/QUICKSTART.md 中的同名地址。
|
|
36
|
-
export const DEFAULT_BASE_URL = 'http://14.103.27.195:8080/v1';
|
|
37
|
-
|
|
38
|
-
async function main() {
|
|
39
|
-
const args = process.argv.slice(2);
|
|
40
|
-
const command = args[0];
|
|
41
|
-
const subArgs = args.slice(1);
|
|
42
|
-
|
|
43
|
-
switch (command) {
|
|
44
|
-
case 'install':
|
|
45
|
-
case 'i':
|
|
46
|
-
await install(subArgs);
|
|
47
|
-
break;
|
|
48
|
-
case 'config':
|
|
49
|
-
case 'c':
|
|
50
|
-
await config(subArgs);
|
|
51
|
-
break;
|
|
52
|
-
case 'test':
|
|
53
|
-
case 't':
|
|
54
|
-
await test(subArgs);
|
|
55
|
-
break;
|
|
56
|
-
case 'balance':
|
|
57
|
-
case 'b':
|
|
58
|
-
await balance(subArgs);
|
|
59
|
-
break;
|
|
60
|
-
case 'models':
|
|
61
|
-
case 'm':
|
|
62
|
-
await models(subArgs);
|
|
63
|
-
break;
|
|
64
|
-
case 'recharge':
|
|
65
|
-
case 'r':
|
|
66
|
-
await recharge(subArgs);
|
|
67
|
-
break;
|
|
68
|
-
case '--version':
|
|
69
|
-
case '-v':
|
|
70
|
-
showVersion(VERSION);
|
|
71
|
-
break;
|
|
72
|
-
case '--help':
|
|
73
|
-
case '-h':
|
|
74
|
-
case 'help':
|
|
75
|
-
case undefined:
|
|
76
|
-
showHelp(VERSION);
|
|
77
|
-
break;
|
|
78
|
-
default:
|
|
79
|
-
console.error(`未知命令: ${command}`);
|
|
80
|
-
console.error('运行 aixx --help 查看可用命令');
|
|
81
|
-
process.exit(1);
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
main().catch(err => {
|
|
86
|
-
console.error('❌ 发生错误:', err.message);
|
|
87
|
-
process.exit(1);
|
|
88
|
-
});
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* AIXX CLI
|
|
5
|
+
* 让你的AI调用万物。
|
|
6
|
+
*
|
|
7
|
+
* 用法:
|
|
8
|
+
* npx aixxai install [推荐码] 安装AIXX skill到本地agent(含零配置)
|
|
9
|
+
* npx aixxai config 配置/查看AIXX
|
|
10
|
+
* npx aixxai test 测试AIXX是否可用
|
|
11
|
+
* npx aixxai balance 查询账户余额
|
|
12
|
+
* npx aixxai models 列出可用模型(分组)
|
|
13
|
+
* npx aixxai recharge [金额] 充值引导
|
|
14
|
+
* npx aixxai --version 查看版本
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { readFileSync } from 'fs';
|
|
18
|
+
import { fileURLToPath } from 'url';
|
|
19
|
+
import { dirname, join } from 'path';
|
|
20
|
+
import { install } from '../src/commands/install.js';
|
|
21
|
+
import { config } from '../src/commands/config.js';
|
|
22
|
+
import { test } from '../src/commands/test.js';
|
|
23
|
+
import { balance } from '../src/commands/balance.js';
|
|
24
|
+
import { models } from '../src/commands/models.js';
|
|
25
|
+
import { recharge } from '../src/commands/recharge.js';
|
|
26
|
+
import { showHelp, showVersion } from '../src/commands/help.js';
|
|
27
|
+
|
|
28
|
+
// 版本号从 package.json 读(单一来源,别再硬编码)
|
|
29
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
30
|
+
const VERSION = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf-8')).version;
|
|
31
|
+
|
|
32
|
+
// 默认AIXX后端地址
|
|
33
|
+
// ⚠️ 安全警告:当前用HTTP明文传输API Key(http://),存在凭据被嗅探风险。
|
|
34
|
+
// TODO(安全):后端配置好HTTPS证书后,把下面的 URL 改为 'https://14.103.27.195:8080/v1'。
|
|
35
|
+
// 改完后请全局搜索 "安全警告" 同步更新 env.js 与 templates/QUICKSTART.md 中的同名地址。
|
|
36
|
+
export const DEFAULT_BASE_URL = 'http://14.103.27.195:8080/v1';
|
|
37
|
+
|
|
38
|
+
async function main() {
|
|
39
|
+
const args = process.argv.slice(2);
|
|
40
|
+
const command = args[0];
|
|
41
|
+
const subArgs = args.slice(1);
|
|
42
|
+
|
|
43
|
+
switch (command) {
|
|
44
|
+
case 'install':
|
|
45
|
+
case 'i':
|
|
46
|
+
await install(subArgs);
|
|
47
|
+
break;
|
|
48
|
+
case 'config':
|
|
49
|
+
case 'c':
|
|
50
|
+
await config(subArgs);
|
|
51
|
+
break;
|
|
52
|
+
case 'test':
|
|
53
|
+
case 't':
|
|
54
|
+
await test(subArgs);
|
|
55
|
+
break;
|
|
56
|
+
case 'balance':
|
|
57
|
+
case 'b':
|
|
58
|
+
await balance(subArgs);
|
|
59
|
+
break;
|
|
60
|
+
case 'models':
|
|
61
|
+
case 'm':
|
|
62
|
+
await models(subArgs);
|
|
63
|
+
break;
|
|
64
|
+
case 'recharge':
|
|
65
|
+
case 'r':
|
|
66
|
+
await recharge(subArgs);
|
|
67
|
+
break;
|
|
68
|
+
case '--version':
|
|
69
|
+
case '-v':
|
|
70
|
+
showVersion(VERSION);
|
|
71
|
+
break;
|
|
72
|
+
case '--help':
|
|
73
|
+
case '-h':
|
|
74
|
+
case 'help':
|
|
75
|
+
case undefined:
|
|
76
|
+
showHelp(VERSION);
|
|
77
|
+
break;
|
|
78
|
+
default:
|
|
79
|
+
console.error(`未知命令: ${command}`);
|
|
80
|
+
console.error('运行 aixx --help 查看可用命令');
|
|
81
|
+
process.exit(1);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
main().catch(err => {
|
|
86
|
+
console.error('❌ 发生错误:', err.message);
|
|
87
|
+
process.exit(1);
|
|
88
|
+
});
|
package/package.json
CHANGED
package/src/commands/balance.js
CHANGED
|
@@ -1,147 +1,147 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* AIXX CLI - balance 命令
|
|
3
|
-
*
|
|
4
|
-
* 查询当前账户余额、已用额度。
|
|
5
|
-
*
|
|
6
|
-
* 数据来源(New-API兼容接口):
|
|
7
|
-
* 1. GET /v1/dashboard/billing/subscription → hard_limit_usd(总额度,美元)
|
|
8
|
-
* 2. GET /v1/dashboard/billing/usage → 已用量(可能多种格式,做兜底)
|
|
9
|
-
*
|
|
10
|
-
* 展示时换算成人民币(按 1 USD ≈ 7 元,只是给用户一个直观感受)。
|
|
11
|
-
* "还剩约N次对话":按每次对话约 ¥0.01 估算(最便宜的deepseek-chat单次几厘到几分)。
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
import { DEFAULT_BASE_URL } from '../../bin/aixx.js';
|
|
15
|
-
import { loadApiKey, maskKey } from '../utils/env.js';
|
|
16
|
-
|
|
17
|
-
// 美元→人民币换算(仅用于展示,给用户一个直观感受)
|
|
18
|
-
const USD_TO_CNY = 7;
|
|
19
|
-
// 估算单次对话成本(元),用于"还能聊多少次"
|
|
20
|
-
const COST_PER_CHAT_CNY = 0.01;
|
|
21
|
-
// 所有对外请求统一 15 秒超时
|
|
22
|
-
const FETCH_TIMEOUT = 15000;
|
|
23
|
-
// 网络/超时异常统一成友好提示
|
|
24
|
-
function friendlyNetErr(err) {
|
|
25
|
-
return err?.name === 'TimeoutError' || err?.name === 'AbortError'
|
|
26
|
-
? '连接超时,请检查网络'
|
|
27
|
-
: err?.message || String(err);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* apiKey 读取统一走 src/utils/env.js 的 loadApiKey:
|
|
32
|
-
* 环境变量 AIXX_API_KEY → ~/.aixx/config.json → ~/.aixx/account.json
|
|
33
|
-
*/
|
|
34
|
-
|
|
35
|
-
export async function balance(subArgs) {
|
|
36
|
-
console.log('╔══════════════════════════════════════════╗');
|
|
37
|
-
console.log('║ AIXX 账户余额 ║');
|
|
38
|
-
console.log('╚══════════════════════════════════════════╝\n');
|
|
39
|
-
|
|
40
|
-
const apiKey = loadApiKey();
|
|
41
|
-
const baseUrl = process.env.AIXX_BASE_URL || DEFAULT_BASE_URL;
|
|
42
|
-
|
|
43
|
-
if (!apiKey) {
|
|
44
|
-
console.error('❌ 未找到AIXX API Key');
|
|
45
|
-
console.error(' 请先运行 npx aixxai install,或设置环境变量 AIXX_API_KEY');
|
|
46
|
-
process.exit(1);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
console.log(`使用Key: ${maskKey(apiKey)}`);
|
|
50
|
-
console.log(`后端地址: ${baseUrl}\n`);
|
|
51
|
-
|
|
52
|
-
// 1. 查总额度(subscription)
|
|
53
|
-
let hardLimitUsd = 0;
|
|
54
|
-
try {
|
|
55
|
-
const resp = await fetch(`${baseUrl}/dashboard/billing/subscription`, {
|
|
56
|
-
headers: { Authorization: `Bearer ${apiKey}` },
|
|
57
|
-
signal: AbortSignal.timeout(FETCH_TIMEOUT),
|
|
58
|
-
});
|
|
59
|
-
if (!resp.ok) {
|
|
60
|
-
if (resp.status === 401) {
|
|
61
|
-
console.error('❌ API Key 无效(401),请重新 install 或检查 key');
|
|
62
|
-
process.exit(1);
|
|
63
|
-
}
|
|
64
|
-
console.error(`❌ 查询余额失败: HTTP ${resp.status}`);
|
|
65
|
-
process.exit(1);
|
|
66
|
-
}
|
|
67
|
-
const data = await resp.json();
|
|
68
|
-
// New-API/OpenAI兼容:hard_limit_usd 是总额度
|
|
69
|
-
hardLimitUsd =
|
|
70
|
-
data.hard_limit_usd ??
|
|
71
|
-
data.hard_limit ??
|
|
72
|
-
data.data?.hard_limit_usd ??
|
|
73
|
-
0;
|
|
74
|
-
} catch (err) {
|
|
75
|
-
console.error(`❌ ${friendlyNetErr(err)}`);
|
|
76
|
-
process.exit(1);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// 2. 查已用量(usage)
|
|
80
|
-
// 不同后端字段不一样,尽量兜底
|
|
81
|
-
let usedUsd = 0;
|
|
82
|
-
try {
|
|
83
|
-
// 用一个宽日期范围,确保能查到所有用量
|
|
84
|
-
const today = new Date();
|
|
85
|
-
const start = `${today.getFullYear()}-01-01`;
|
|
86
|
-
const end = `${today.getFullYear()}-12-31`;
|
|
87
|
-
const resp = await fetch(
|
|
88
|
-
`${baseUrl}/dashboard/billing/usage?start_date=${start}&end_date=${end}`,
|
|
89
|
-
{
|
|
90
|
-
headers: { Authorization: `Bearer ${apiKey}` },
|
|
91
|
-
signal: AbortSignal.timeout(FETCH_TIMEOUT),
|
|
92
|
-
}
|
|
93
|
-
);
|
|
94
|
-
if (resp.ok) {
|
|
95
|
-
const data = await resp.json();
|
|
96
|
-
// 常见字段优先级
|
|
97
|
-
usedUsd =
|
|
98
|
-
data.total_usage ??
|
|
99
|
-
data.total_cost ??
|
|
100
|
-
data.data?.total_usage ??
|
|
101
|
-
data.cumulative_usage ??
|
|
102
|
-
0;
|
|
103
|
-
// New-API 的 total_usage 单位有时是"分(美元)",要除以100
|
|
104
|
-
// 这里做个粗判断:如果usedUsd比hardLimit还大100倍,多半是没除100
|
|
105
|
-
if (usedUsd > 0 && hardLimitUsd > 0 && usedUsd > hardLimitUsd * 50) {
|
|
106
|
-
usedUsd = usedUsd / 100;
|
|
107
|
-
}
|
|
108
|
-
} else {
|
|
109
|
-
// usage 接口挂了不影响主流程,总额度照常显示
|
|
110
|
-
console.log(' (用量接口未返回数据,仅显示总额度)\n');
|
|
111
|
-
}
|
|
112
|
-
} catch (err) {
|
|
113
|
-
console.log(' (用量查询失败,仅显示总额度)\n');
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// 3. 计算并展示
|
|
117
|
-
// New-API 的"无限额度"哨兵:hard_limit_usd >= 100000000(1亿)表示这个token不限额。
|
|
118
|
-
// 直接显示真实数字(¥7亿)会吓到用户,所以单独走"无限额度"分支。
|
|
119
|
-
const UNLIMITED_SENTINEL = 100000000;
|
|
120
|
-
const isUnlimited = hardLimitUsd >= UNLIMITED_SENTINEL;
|
|
121
|
-
const usedCny = usedUsd * USD_TO_CNY;
|
|
122
|
-
|
|
123
|
-
console.log('────────────────────────────────────');
|
|
124
|
-
if (isUnlimited) {
|
|
125
|
-
// unlimited token(New-API对unlimited_quota=true的token返回1亿作为标记)
|
|
126
|
-
// 实际是"按账户余额扣费",不是真的无限。账户余额是注册送的¥5起,用完就停。
|
|
127
|
-
// billing接口拿不到真实账户余额(它对unlimited token只返回1亿标记),
|
|
128
|
-
// 所以这里如实告知用户,引导去后台查精确余额。
|
|
129
|
-
console.log(`💰 计费方式:按账户余额扣费(注册送¥5起,用完需充值)`);
|
|
130
|
-
console.log(`📊 已用额度:¥${usedCny.toFixed(2)}`);
|
|
131
|
-
console.log(`💡 精确余额请登录AIXX后台查看:${baseUrl.replace(/\/v1$/, '')}`);
|
|
132
|
-
} else {
|
|
133
|
-
// 正常有限额度
|
|
134
|
-
const remainingUsd = Math.max(0, hardLimitUsd - usedUsd);
|
|
135
|
-
const totalCny = hardLimitUsd * USD_TO_CNY;
|
|
136
|
-
const remainingCny = remainingUsd * USD_TO_CNY;
|
|
137
|
-
const approxChats = Math.floor(remainingCny / COST_PER_CHAT_CNY);
|
|
138
|
-
console.log(`💰 当前余额:¥${remainingCny.toFixed(2)}(还剩约 ${approxChats.toLocaleString()} 次对话)`);
|
|
139
|
-
console.log(`📊 已用额度:¥${usedCny.toFixed(2)}`);
|
|
140
|
-
console.log(`💳 账户总额度:¥${totalCny.toFixed(2)}($${hardLimitUsd.toFixed(2)})`);
|
|
141
|
-
}
|
|
142
|
-
console.log('────────────────────────────────────');
|
|
143
|
-
console.log('\n💡 提示:');
|
|
144
|
-
console.log(' - 新用户注册送 ¥5 免费额度');
|
|
145
|
-
console.log(' - deepseek-chat 最便宜,省着用能聊很久');
|
|
146
|
-
console.log(' - 余额不足时运行 aixx recharge 查看充值方式\n');
|
|
147
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* AIXX CLI - balance 命令
|
|
3
|
+
*
|
|
4
|
+
* 查询当前账户余额、已用额度。
|
|
5
|
+
*
|
|
6
|
+
* 数据来源(New-API兼容接口):
|
|
7
|
+
* 1. GET /v1/dashboard/billing/subscription → hard_limit_usd(总额度,美元)
|
|
8
|
+
* 2. GET /v1/dashboard/billing/usage → 已用量(可能多种格式,做兜底)
|
|
9
|
+
*
|
|
10
|
+
* 展示时换算成人民币(按 1 USD ≈ 7 元,只是给用户一个直观感受)。
|
|
11
|
+
* "还剩约N次对话":按每次对话约 ¥0.01 估算(最便宜的deepseek-chat单次几厘到几分)。
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { DEFAULT_BASE_URL } from '../../bin/aixx.js';
|
|
15
|
+
import { loadApiKey, maskKey } from '../utils/env.js';
|
|
16
|
+
|
|
17
|
+
// 美元→人民币换算(仅用于展示,给用户一个直观感受)
|
|
18
|
+
const USD_TO_CNY = 7;
|
|
19
|
+
// 估算单次对话成本(元),用于"还能聊多少次"
|
|
20
|
+
const COST_PER_CHAT_CNY = 0.01;
|
|
21
|
+
// 所有对外请求统一 15 秒超时
|
|
22
|
+
const FETCH_TIMEOUT = 15000;
|
|
23
|
+
// 网络/超时异常统一成友好提示
|
|
24
|
+
function friendlyNetErr(err) {
|
|
25
|
+
return err?.name === 'TimeoutError' || err?.name === 'AbortError'
|
|
26
|
+
? '连接超时,请检查网络'
|
|
27
|
+
: err?.message || String(err);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* apiKey 读取统一走 src/utils/env.js 的 loadApiKey:
|
|
32
|
+
* 环境变量 AIXX_API_KEY → ~/.aixx/config.json → ~/.aixx/account.json
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
export async function balance(subArgs) {
|
|
36
|
+
console.log('╔══════════════════════════════════════════╗');
|
|
37
|
+
console.log('║ AIXX 账户余额 ║');
|
|
38
|
+
console.log('╚══════════════════════════════════════════╝\n');
|
|
39
|
+
|
|
40
|
+
const apiKey = loadApiKey();
|
|
41
|
+
const baseUrl = process.env.AIXX_BASE_URL || DEFAULT_BASE_URL;
|
|
42
|
+
|
|
43
|
+
if (!apiKey) {
|
|
44
|
+
console.error('❌ 未找到AIXX API Key');
|
|
45
|
+
console.error(' 请先运行 npx aixxai install,或设置环境变量 AIXX_API_KEY');
|
|
46
|
+
process.exit(1);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
console.log(`使用Key: ${maskKey(apiKey)}`);
|
|
50
|
+
console.log(`后端地址: ${baseUrl}\n`);
|
|
51
|
+
|
|
52
|
+
// 1. 查总额度(subscription)
|
|
53
|
+
let hardLimitUsd = 0;
|
|
54
|
+
try {
|
|
55
|
+
const resp = await fetch(`${baseUrl}/dashboard/billing/subscription`, {
|
|
56
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
57
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT),
|
|
58
|
+
});
|
|
59
|
+
if (!resp.ok) {
|
|
60
|
+
if (resp.status === 401) {
|
|
61
|
+
console.error('❌ API Key 无效(401),请重新 install 或检查 key');
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
console.error(`❌ 查询余额失败: HTTP ${resp.status}`);
|
|
65
|
+
process.exit(1);
|
|
66
|
+
}
|
|
67
|
+
const data = await resp.json();
|
|
68
|
+
// New-API/OpenAI兼容:hard_limit_usd 是总额度
|
|
69
|
+
hardLimitUsd =
|
|
70
|
+
data.hard_limit_usd ??
|
|
71
|
+
data.hard_limit ??
|
|
72
|
+
data.data?.hard_limit_usd ??
|
|
73
|
+
0;
|
|
74
|
+
} catch (err) {
|
|
75
|
+
console.error(`❌ ${friendlyNetErr(err)}`);
|
|
76
|
+
process.exit(1);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// 2. 查已用量(usage)
|
|
80
|
+
// 不同后端字段不一样,尽量兜底
|
|
81
|
+
let usedUsd = 0;
|
|
82
|
+
try {
|
|
83
|
+
// 用一个宽日期范围,确保能查到所有用量
|
|
84
|
+
const today = new Date();
|
|
85
|
+
const start = `${today.getFullYear()}-01-01`;
|
|
86
|
+
const end = `${today.getFullYear()}-12-31`;
|
|
87
|
+
const resp = await fetch(
|
|
88
|
+
`${baseUrl}/dashboard/billing/usage?start_date=${start}&end_date=${end}`,
|
|
89
|
+
{
|
|
90
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
91
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT),
|
|
92
|
+
}
|
|
93
|
+
);
|
|
94
|
+
if (resp.ok) {
|
|
95
|
+
const data = await resp.json();
|
|
96
|
+
// 常见字段优先级
|
|
97
|
+
usedUsd =
|
|
98
|
+
data.total_usage ??
|
|
99
|
+
data.total_cost ??
|
|
100
|
+
data.data?.total_usage ??
|
|
101
|
+
data.cumulative_usage ??
|
|
102
|
+
0;
|
|
103
|
+
// New-API 的 total_usage 单位有时是"分(美元)",要除以100
|
|
104
|
+
// 这里做个粗判断:如果usedUsd比hardLimit还大100倍,多半是没除100
|
|
105
|
+
if (usedUsd > 0 && hardLimitUsd > 0 && usedUsd > hardLimitUsd * 50) {
|
|
106
|
+
usedUsd = usedUsd / 100;
|
|
107
|
+
}
|
|
108
|
+
} else {
|
|
109
|
+
// usage 接口挂了不影响主流程,总额度照常显示
|
|
110
|
+
console.log(' (用量接口未返回数据,仅显示总额度)\n');
|
|
111
|
+
}
|
|
112
|
+
} catch (err) {
|
|
113
|
+
console.log(' (用量查询失败,仅显示总额度)\n');
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// 3. 计算并展示
|
|
117
|
+
// New-API 的"无限额度"哨兵:hard_limit_usd >= 100000000(1亿)表示这个token不限额。
|
|
118
|
+
// 直接显示真实数字(¥7亿)会吓到用户,所以单独走"无限额度"分支。
|
|
119
|
+
const UNLIMITED_SENTINEL = 100000000;
|
|
120
|
+
const isUnlimited = hardLimitUsd >= UNLIMITED_SENTINEL;
|
|
121
|
+
const usedCny = usedUsd * USD_TO_CNY;
|
|
122
|
+
|
|
123
|
+
console.log('────────────────────────────────────');
|
|
124
|
+
if (isUnlimited) {
|
|
125
|
+
// unlimited token(New-API对unlimited_quota=true的token返回1亿作为标记)
|
|
126
|
+
// 实际是"按账户余额扣费",不是真的无限。账户余额是注册送的¥5起,用完就停。
|
|
127
|
+
// billing接口拿不到真实账户余额(它对unlimited token只返回1亿标记),
|
|
128
|
+
// 所以这里如实告知用户,引导去后台查精确余额。
|
|
129
|
+
console.log(`💰 计费方式:按账户余额扣费(注册送¥5起,用完需充值)`);
|
|
130
|
+
console.log(`📊 已用额度:¥${usedCny.toFixed(2)}`);
|
|
131
|
+
console.log(`💡 精确余额请登录AIXX后台查看:${baseUrl.replace(/\/v1$/, '')}`);
|
|
132
|
+
} else {
|
|
133
|
+
// 正常有限额度
|
|
134
|
+
const remainingUsd = Math.max(0, hardLimitUsd - usedUsd);
|
|
135
|
+
const totalCny = hardLimitUsd * USD_TO_CNY;
|
|
136
|
+
const remainingCny = remainingUsd * USD_TO_CNY;
|
|
137
|
+
const approxChats = Math.floor(remainingCny / COST_PER_CHAT_CNY);
|
|
138
|
+
console.log(`💰 当前余额:¥${remainingCny.toFixed(2)}(还剩约 ${approxChats.toLocaleString()} 次对话)`);
|
|
139
|
+
console.log(`📊 已用额度:¥${usedCny.toFixed(2)}`);
|
|
140
|
+
console.log(`💳 账户总额度:¥${totalCny.toFixed(2)}($${hardLimitUsd.toFixed(2)})`);
|
|
141
|
+
}
|
|
142
|
+
console.log('────────────────────────────────────');
|
|
143
|
+
console.log('\n💡 提示:');
|
|
144
|
+
console.log(' - 新用户注册送 ¥5 免费额度');
|
|
145
|
+
console.log(' - deepseek-chat 最便宜,省着用能聊很久');
|
|
146
|
+
console.log(' - 余额不足时运行 aixx recharge 查看充值方式\n');
|
|
147
|
+
}
|