aixxai 1.0.0 → 1.2.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/bin/aixx.js +22 -4
- package/package.json +1 -1
- package/src/commands/balance.js +159 -0
- package/src/commands/help.js +11 -2
- package/src/commands/install.js +84 -17
- package/src/commands/models.js +152 -0
- package/src/commands/recharge.js +143 -0
- package/src/utils/agentconfig.js +374 -0
- package/src/utils/env.js +32 -14
- package/templates/SKILL.md +8 -2
- package/templates/references/chat.md +56 -32
- package/templates/references/search.md +117 -0
package/bin/aixx.js
CHANGED
|
@@ -5,15 +5,21 @@
|
|
|
5
5
|
* 让你的AI调用万物。
|
|
6
6
|
*
|
|
7
7
|
* 用法:
|
|
8
|
-
* npx
|
|
9
|
-
* npx
|
|
10
|
-
* npx
|
|
11
|
-
* npx
|
|
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 查看版本
|
|
12
15
|
*/
|
|
13
16
|
|
|
14
17
|
import { install } from '../src/commands/install.js';
|
|
15
18
|
import { config } from '../src/commands/config.js';
|
|
16
19
|
import { test } from '../src/commands/test.js';
|
|
20
|
+
import { balance } from '../src/commands/balance.js';
|
|
21
|
+
import { models } from '../src/commands/models.js';
|
|
22
|
+
import { recharge } from '../src/commands/recharge.js';
|
|
17
23
|
import { showHelp, showVersion } from '../src/commands/help.js';
|
|
18
24
|
|
|
19
25
|
const VERSION = '0.1.0';
|
|
@@ -42,6 +48,18 @@ async function main() {
|
|
|
42
48
|
case 't':
|
|
43
49
|
await test(subArgs);
|
|
44
50
|
break;
|
|
51
|
+
case 'balance':
|
|
52
|
+
case 'b':
|
|
53
|
+
await balance(subArgs);
|
|
54
|
+
break;
|
|
55
|
+
case 'models':
|
|
56
|
+
case 'm':
|
|
57
|
+
await models(subArgs);
|
|
58
|
+
break;
|
|
59
|
+
case 'recharge':
|
|
60
|
+
case 'r':
|
|
61
|
+
await recharge(subArgs);
|
|
62
|
+
break;
|
|
45
63
|
case '--version':
|
|
46
64
|
case '-v':
|
|
47
65
|
showVersion(VERSION);
|
package/package.json
CHANGED
|
@@ -0,0 +1,159 @@
|
|
|
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 { existsSync, readFileSync } from 'fs';
|
|
15
|
+
import { homedir } from 'os';
|
|
16
|
+
import { join } from 'path';
|
|
17
|
+
import { DEFAULT_BASE_URL } from '../../bin/aixx.js';
|
|
18
|
+
|
|
19
|
+
// 美元→人民币换算(仅用于展示,给用户一个直观感受)
|
|
20
|
+
const USD_TO_CNY = 7;
|
|
21
|
+
// 估算单次对话成本(元),用于"还能聊多少次"
|
|
22
|
+
const COST_PER_CHAT_CNY = 0.01;
|
|
23
|
+
// 所有对外请求统一 15 秒超时
|
|
24
|
+
const FETCH_TIMEOUT = 15000;
|
|
25
|
+
// 网络/超时异常统一成友好提示
|
|
26
|
+
function friendlyNetErr(err) {
|
|
27
|
+
return err?.name === 'TimeoutError' || err?.name === 'AbortError'
|
|
28
|
+
? '连接超时,请检查网络'
|
|
29
|
+
: err?.message || String(err);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* 从环境变量或本地账号文件读取 apiKey。
|
|
34
|
+
* 优先级:环境变量 > ~/.aixx/account.json
|
|
35
|
+
*/
|
|
36
|
+
function loadApiKey() {
|
|
37
|
+
if (process.env.AIXX_API_KEY) return process.env.AIXX_API_KEY;
|
|
38
|
+
const accountFile = join(homedir(), '.aixx', 'account.json');
|
|
39
|
+
if (existsSync(accountFile)) {
|
|
40
|
+
try {
|
|
41
|
+
const acc = JSON.parse(readFileSync(accountFile, 'utf-8'));
|
|
42
|
+
if (acc.apiKey) return acc.apiKey;
|
|
43
|
+
} catch (e) {
|
|
44
|
+
// 忽略,下面报错
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export async function balance(subArgs) {
|
|
51
|
+
console.log('╔══════════════════════════════════════════╗');
|
|
52
|
+
console.log('║ AIXX 账户余额 ║');
|
|
53
|
+
console.log('╚══════════════════════════════════════════╝\n');
|
|
54
|
+
|
|
55
|
+
const apiKey = loadApiKey();
|
|
56
|
+
const baseUrl = process.env.AIXX_BASE_URL || DEFAULT_BASE_URL;
|
|
57
|
+
|
|
58
|
+
if (!apiKey) {
|
|
59
|
+
console.error('❌ 未找到AIXX API Key');
|
|
60
|
+
console.error(' 请先运行 aixx install,或设置环境变量 AIXX_API_KEY');
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
console.log(`使用Key: ${apiKey.slice(0, 12)}...${apiKey.slice(-4)}`);
|
|
65
|
+
console.log(`后端地址: ${baseUrl}\n`);
|
|
66
|
+
|
|
67
|
+
// 1. 查总额度(subscription)
|
|
68
|
+
let hardLimitUsd = 0;
|
|
69
|
+
try {
|
|
70
|
+
const resp = await fetch(`${baseUrl}/dashboard/billing/subscription`, {
|
|
71
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
72
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT),
|
|
73
|
+
});
|
|
74
|
+
if (!resp.ok) {
|
|
75
|
+
if (resp.status === 401) {
|
|
76
|
+
console.error('❌ API Key 无效(401),请重新 install 或检查 key');
|
|
77
|
+
process.exit(1);
|
|
78
|
+
}
|
|
79
|
+
console.error(`❌ 查询余额失败: HTTP ${resp.status}`);
|
|
80
|
+
process.exit(1);
|
|
81
|
+
}
|
|
82
|
+
const data = await resp.json();
|
|
83
|
+
// New-API/OpenAI兼容:hard_limit_usd 是总额度
|
|
84
|
+
hardLimitUsd =
|
|
85
|
+
data.hard_limit_usd ??
|
|
86
|
+
data.hard_limit ??
|
|
87
|
+
data.data?.hard_limit_usd ??
|
|
88
|
+
0;
|
|
89
|
+
} catch (err) {
|
|
90
|
+
console.error(`❌ ${friendlyNetErr(err)}`);
|
|
91
|
+
process.exit(1);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// 2. 查已用量(usage)
|
|
95
|
+
// 不同后端字段不一样,尽量兜底
|
|
96
|
+
let usedUsd = 0;
|
|
97
|
+
try {
|
|
98
|
+
// 用一个宽日期范围,确保能查到所有用量
|
|
99
|
+
const today = new Date();
|
|
100
|
+
const start = `${today.getFullYear()}-01-01`;
|
|
101
|
+
const end = `${today.getFullYear()}-12-31`;
|
|
102
|
+
const resp = await fetch(
|
|
103
|
+
`${baseUrl}/dashboard/billing/usage?start_date=${start}&end_date=${end}`,
|
|
104
|
+
{
|
|
105
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
106
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT),
|
|
107
|
+
}
|
|
108
|
+
);
|
|
109
|
+
if (resp.ok) {
|
|
110
|
+
const data = await resp.json();
|
|
111
|
+
// 常见字段优先级
|
|
112
|
+
usedUsd =
|
|
113
|
+
data.total_usage ??
|
|
114
|
+
data.total_cost ??
|
|
115
|
+
data.data?.total_usage ??
|
|
116
|
+
data.cumulative_usage ??
|
|
117
|
+
0;
|
|
118
|
+
// New-API 的 total_usage 单位有时是"分(美元)",要除以100
|
|
119
|
+
// 这里做个粗判断:如果usedUsd比hardLimit还大100倍,多半是没除100
|
|
120
|
+
if (usedUsd > 0 && hardLimitUsd > 0 && usedUsd > hardLimitUsd * 50) {
|
|
121
|
+
usedUsd = usedUsd / 100;
|
|
122
|
+
}
|
|
123
|
+
} else {
|
|
124
|
+
// usage 接口挂了不影响主流程,总额度照常显示
|
|
125
|
+
console.log(' (用量接口未返回数据,仅显示总额度)\n');
|
|
126
|
+
}
|
|
127
|
+
} catch (err) {
|
|
128
|
+
console.log(' (用量查询失败,仅显示总额度)\n');
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// 3. 计算并展示
|
|
132
|
+
// New-API 的"无限额度"哨兵:hard_limit_usd >= 100000000(1亿)表示这个token不限额。
|
|
133
|
+
// 直接显示真实数字(¥7亿)会吓到用户,所以单独走"无限额度"分支。
|
|
134
|
+
const UNLIMITED_SENTINEL = 100000000;
|
|
135
|
+
const isUnlimited = hardLimitUsd >= UNLIMITED_SENTINEL;
|
|
136
|
+
const usedCny = usedUsd * USD_TO_CNY;
|
|
137
|
+
|
|
138
|
+
console.log('────────────────────────────────────');
|
|
139
|
+
if (isUnlimited) {
|
|
140
|
+
// 无限额度(一般是管理员key或赠送的不限量key)
|
|
141
|
+
console.log(`💰 当前余额:♾️ 无限额度(不限量)`);
|
|
142
|
+
console.log(`📊 已用额度:¥${usedCny.toFixed(2)}`);
|
|
143
|
+
console.log(`💳 账户类型:无限量`);
|
|
144
|
+
} else {
|
|
145
|
+
// 正常有限额度
|
|
146
|
+
const remainingUsd = Math.max(0, hardLimitUsd - usedUsd);
|
|
147
|
+
const totalCny = hardLimitUsd * USD_TO_CNY;
|
|
148
|
+
const remainingCny = remainingUsd * USD_TO_CNY;
|
|
149
|
+
const approxChats = Math.floor(remainingCny / COST_PER_CHAT_CNY);
|
|
150
|
+
console.log(`💰 当前余额:¥${remainingCny.toFixed(2)}(还剩约 ${approxChats.toLocaleString()} 次对话)`);
|
|
151
|
+
console.log(`📊 已用额度:¥${usedCny.toFixed(2)}`);
|
|
152
|
+
console.log(`💳 账户总额度:¥${totalCny.toFixed(2)}($${hardLimitUsd.toFixed(2)})`);
|
|
153
|
+
}
|
|
154
|
+
console.log('────────────────────────────────────');
|
|
155
|
+
console.log('\n💡 提示:');
|
|
156
|
+
console.log(' - 新用户注册送 ¥5 免费额度');
|
|
157
|
+
console.log(' - deepseek-chat 最便宜,省着用能聊很久');
|
|
158
|
+
console.log(' - 余额不足时运行 aixx recharge 查看充值方式\n');
|
|
159
|
+
}
|
package/src/commands/help.js
CHANGED
|
@@ -10,16 +10,25 @@ export function showHelp(version) {
|
|
|
10
10
|
╚══════════════════════════════════════════╝
|
|
11
11
|
|
|
12
12
|
用法:
|
|
13
|
-
aixx install [推荐码] 安装AIXX skill到本地agent
|
|
13
|
+
aixx install [推荐码] 安装AIXX skill到本地agent(含零配置)
|
|
14
14
|
aixx config 配置或查看AIXX设置
|
|
15
15
|
aixx test 测试AIXX是否可用
|
|
16
|
+
aixx balance 查询账户余额(还剩多少钱)
|
|
17
|
+
aixx models 列出可用模型(国产/海外分组)
|
|
18
|
+
aixx recharge [金额] 充值引导(默认$5)
|
|
16
19
|
aixx --version 查看版本
|
|
17
20
|
aixx --help 显示此帮助
|
|
18
21
|
|
|
19
22
|
安装示例:
|
|
20
|
-
npx aixxai install
|
|
23
|
+
npx aixxai install 普通安装(自动注册,送¥5额度)
|
|
21
24
|
npx aixxai install zhangsan 通过张三的推荐码安装(张三获分润)
|
|
22
25
|
|
|
26
|
+
账户示例:
|
|
27
|
+
aixx balance 查看余额
|
|
28
|
+
aixx balance 20 (金额参数对balance无效,仅recharge用)
|
|
29
|
+
aixx models 看有哪些模型可调
|
|
30
|
+
aixx recharge 10 充 $10
|
|
31
|
+
|
|
23
32
|
关于AIXX:
|
|
24
33
|
AIXX = "AI的谷歌"。装一个skill,agent就能调用全世界的AI能力。
|
|
25
34
|
永远不用碰key、参数、文档,一切通过对话。
|
package/src/commands/install.js
CHANGED
|
@@ -18,6 +18,7 @@ import { randomInt } from 'crypto';
|
|
|
18
18
|
import { DEFAULT_BASE_URL } from '../../bin/aixx.js';
|
|
19
19
|
import { downloadSkill } from '../utils/download.js';
|
|
20
20
|
import { setupEnv } from '../utils/env.js';
|
|
21
|
+
import { configureAgents } from '../utils/agentconfig.js';
|
|
21
22
|
|
|
22
23
|
// skill目录检测
|
|
23
24
|
function detectSkillDirs() {
|
|
@@ -52,6 +53,15 @@ function genPassword() {
|
|
|
52
53
|
return pwd;
|
|
53
54
|
}
|
|
54
55
|
|
|
56
|
+
// 所有对外请求统一 15 秒超时(AbortSignal.timeout 在 Node 17.3+ 可用,本 CLI 要求 18+)
|
|
57
|
+
const FETCH_TIMEOUT = 15000;
|
|
58
|
+
// 把网络/超时异常的报错信息统一成友好提示
|
|
59
|
+
function friendlyNetErr(err) {
|
|
60
|
+
return err?.name === 'TimeoutError' || err?.name === 'AbortError'
|
|
61
|
+
? '连接超时,请检查网络'
|
|
62
|
+
: err?.message || String(err);
|
|
63
|
+
}
|
|
64
|
+
|
|
55
65
|
// 自动注册AIXX账号
|
|
56
66
|
async function autoRegister(baseUrl, refCode) {
|
|
57
67
|
const username = genUsername();
|
|
@@ -71,7 +81,8 @@ async function autoRegister(baseUrl, refCode) {
|
|
|
71
81
|
const regResp = await fetch(regUrl, {
|
|
72
82
|
method: 'POST',
|
|
73
83
|
headers: { 'Content-Type': 'application/json' },
|
|
74
|
-
body: JSON.stringify(regBody)
|
|
84
|
+
body: JSON.stringify(regBody),
|
|
85
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT),
|
|
75
86
|
});
|
|
76
87
|
const regData = await regResp.json();
|
|
77
88
|
|
|
@@ -86,7 +97,8 @@ async function autoRegister(baseUrl, refCode) {
|
|
|
86
97
|
const loginResp = await fetch(loginUrl, {
|
|
87
98
|
method: 'POST',
|
|
88
99
|
headers: { 'Content-Type': 'application/json' },
|
|
89
|
-
body: JSON.stringify({ username, password })
|
|
100
|
+
body: JSON.stringify({ username, password }),
|
|
101
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT),
|
|
90
102
|
});
|
|
91
103
|
const loginData = await loginResp.json();
|
|
92
104
|
|
|
@@ -109,7 +121,8 @@ async function autoRegister(baseUrl, refCode) {
|
|
|
109
121
|
name: 'default',
|
|
110
122
|
remain_quota: -1,
|
|
111
123
|
unlimited_quota: true
|
|
112
|
-
})
|
|
124
|
+
}),
|
|
125
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT),
|
|
113
126
|
});
|
|
114
127
|
const tokenData = await tokenResp.json();
|
|
115
128
|
|
|
@@ -121,7 +134,8 @@ async function autoRegister(baseUrl, refCode) {
|
|
|
121
134
|
// 正确做法:先查列表拿 token id,再调 POST /api/token/:id/key 取未脱敏的完整 key。
|
|
122
135
|
const apiBase = baseUrl.replace(/\/v1$/, '');
|
|
123
136
|
const listResp = await fetch(`${apiBase}/api/token/?p=0&page_size=1`, {
|
|
124
|
-
headers: { 'Authorization': `Bearer ${userToken}` }
|
|
137
|
+
headers: { 'Authorization': `Bearer ${userToken}` },
|
|
138
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT),
|
|
125
139
|
});
|
|
126
140
|
const listData = await listResp.json();
|
|
127
141
|
const list = listData.data;
|
|
@@ -135,7 +149,8 @@ async function autoRegister(baseUrl, refCode) {
|
|
|
135
149
|
// 取未脱敏的完整 key(GET 列表会脱敏,必须用这个专用接口)
|
|
136
150
|
const keyResp = await fetch(`${apiBase}/api/token/${tokenId}/key`, {
|
|
137
151
|
method: 'POST',
|
|
138
|
-
headers: { 'Authorization': `Bearer ${userToken}` }
|
|
152
|
+
headers: { 'Authorization': `Bearer ${userToken}` },
|
|
153
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT),
|
|
139
154
|
});
|
|
140
155
|
const keyData = await keyResp.json();
|
|
141
156
|
const apiKey = keyData?.data?.key;
|
|
@@ -147,12 +162,13 @@ async function autoRegister(baseUrl, refCode) {
|
|
|
147
162
|
console.log(`✅ API Key已创建: sk-${apiKey.slice(0, 8)}...${apiKey.slice(-4)}`);
|
|
148
163
|
|
|
149
164
|
// 保存账号信息到本地(方便用户找回)
|
|
165
|
+
// 安全:不存明文密码。注册时密码只用于首次登录拿token,之后用apiKey就够了。
|
|
166
|
+
// 丢了账号可以重新注册(免费),所以不需要保留密码。
|
|
150
167
|
const aixxDir = join(homedir(), '.aixx');
|
|
151
168
|
if (!existsSync(aixxDir)) mkdirSync(aixxDir, { recursive: true });
|
|
152
169
|
const fullKey = `sk-${apiKey}`;
|
|
153
170
|
writeFileSync(join(aixxDir, 'account.json'), JSON.stringify({
|
|
154
171
|
username,
|
|
155
|
-
password,
|
|
156
172
|
apiKey: fullKey, // 存完整key(本地文件,不进git)
|
|
157
173
|
registeredAt: new Date().toISOString(),
|
|
158
174
|
refCode: refCode || null
|
|
@@ -187,7 +203,9 @@ export async function install(subArgs) {
|
|
|
187
203
|
console.log(`✅ 操作系统: ${process.platform}`);
|
|
188
204
|
|
|
189
205
|
// 2. 获取或创建API Key
|
|
206
|
+
// 记录key来源,用于后面给出正确的提示(环境变量 / 本地账号)
|
|
190
207
|
let apiKey = process.env.AIXX_API_KEY;
|
|
208
|
+
let keySource = apiKey ? 'env' : null;
|
|
191
209
|
|
|
192
210
|
if (!apiKey) {
|
|
193
211
|
// 检查本地是否有已保存的账号
|
|
@@ -196,6 +214,7 @@ export async function install(subArgs) {
|
|
|
196
214
|
try {
|
|
197
215
|
const account = JSON.parse(readFileSync(accountFile, 'utf-8'));
|
|
198
216
|
apiKey = account.apiKey;
|
|
217
|
+
keySource = 'account';
|
|
199
218
|
console.log(`\n✅ 发现已保存的账号: ${account.username}`);
|
|
200
219
|
console.log(` 使用已保存的API Key`);
|
|
201
220
|
} catch (e) {
|
|
@@ -213,21 +232,32 @@ export async function install(subArgs) {
|
|
|
213
232
|
// 自动注册
|
|
214
233
|
try {
|
|
215
234
|
apiKey = await autoRegister(DEFAULT_BASE_URL, refCode);
|
|
235
|
+
keySource = 'registered';
|
|
216
236
|
} catch (err) {
|
|
217
|
-
|
|
237
|
+
// 网络/超时类异常给友好提示,业务错误(注册/登录/创建key失败)保留原文
|
|
238
|
+
const msg = /timeout|abort|fetch|network|ECONN|ETIMEDOUT|getaddrinfo/i.test(err.message)
|
|
239
|
+
? friendlyNetErr(err)
|
|
240
|
+
: err.message;
|
|
241
|
+
console.error(`\n❌ 自动注册失败: ${msg}`);
|
|
218
242
|
console.error(' 你可以手动到 AIXX 平台注册后,用已有key重新运行 install。');
|
|
219
243
|
process.exit(1);
|
|
220
244
|
}
|
|
221
245
|
} else if (choice.startsWith('sk-')) {
|
|
222
246
|
// 用户输入已有key
|
|
223
247
|
apiKey = choice;
|
|
248
|
+
keySource = 'manual';
|
|
224
249
|
console.log('✅ 使用你提供的API Key');
|
|
225
250
|
} else {
|
|
226
251
|
console.error('❌ 无效的输入');
|
|
227
252
|
process.exit(1);
|
|
228
253
|
}
|
|
229
254
|
} else {
|
|
230
|
-
|
|
255
|
+
// 区分来源:环境变量 vs 本地账号文件
|
|
256
|
+
if (keySource === 'env') {
|
|
257
|
+
console.log('\n✅ 已检测到 AIXX_API_KEY 环境变量');
|
|
258
|
+
} else {
|
|
259
|
+
console.log('\n✅ 已检测到本地账号文件(~/.aixx/account.json)');
|
|
260
|
+
}
|
|
231
261
|
}
|
|
232
262
|
|
|
233
263
|
// 3. 检测/创建skill目录
|
|
@@ -264,7 +294,17 @@ export async function install(subArgs) {
|
|
|
264
294
|
console.log('✅ 环境变量配置完成');
|
|
265
295
|
}
|
|
266
296
|
|
|
267
|
-
// 6.
|
|
297
|
+
// 6. 零配置:自动写本地agent配置(ZCode / Claude Code)
|
|
298
|
+
// 这是"零配置核心"——让用户安装完就能直接在agent里选到AIXX,不用手动填key。
|
|
299
|
+
if (apiKey) {
|
|
300
|
+
try {
|
|
301
|
+
configureAgents(apiKey);
|
|
302
|
+
} catch (err) {
|
|
303
|
+
console.log(`⚠️ agent配置写入异常(不影响安装): ${err.message}`);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// 7. 测试连通性
|
|
268
308
|
if (apiKey) {
|
|
269
309
|
console.log('\n🔌 测试AIXX连通性...');
|
|
270
310
|
try {
|
|
@@ -276,19 +316,46 @@ export async function install(subArgs) {
|
|
|
276
316
|
}
|
|
277
317
|
}
|
|
278
318
|
|
|
279
|
-
//
|
|
319
|
+
// 8. 安装完成 + 播报(给agent看,让龙龙主动告诉用户装好了)
|
|
280
320
|
console.log('\n╔══════════════════════════════════════════╗');
|
|
281
321
|
console.log('║ 🎉 安装完成! ║');
|
|
282
322
|
console.log('╚══════════════════════════════════════════╝\n');
|
|
283
|
-
|
|
284
|
-
console.log(' "帮我用deepseek-chat翻译:Hello World"\n');
|
|
285
|
-
console.log('查询余额:');
|
|
286
|
-
console.log(' "我AIXX还剩多少额度?"\n');
|
|
287
|
-
console.log('文档:https://gitee.com/kk0803/token-hub\n');
|
|
323
|
+
printInstallBroadcast();
|
|
288
324
|
}
|
|
289
325
|
|
|
290
326
|
async function testConnectivity(apiKey) {
|
|
291
327
|
const url = DEFAULT_BASE_URL.replace(/\/v1$/, '') + '/api/status';
|
|
292
|
-
|
|
293
|
-
|
|
328
|
+
try {
|
|
329
|
+
const resp = await fetch(url, { signal: AbortSignal.timeout(FETCH_TIMEOUT) });
|
|
330
|
+
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
|
|
331
|
+
} catch (err) {
|
|
332
|
+
// 网络超时/中断给友好提示,而不是裸的 abort message
|
|
333
|
+
throw new Error(friendlyNetErr(err));
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* 安装完成后的"播报"。
|
|
339
|
+
* 这段输出是给 agent(龙龙)看的,让 agent 主动转告用户:
|
|
340
|
+
* 你装好了,送了5元,能调这些模型,可以这样用。
|
|
341
|
+
*
|
|
342
|
+
* 格式严格按需求1的模板,方便 agent 识别和转述。
|
|
343
|
+
*/
|
|
344
|
+
function printInstallBroadcast() {
|
|
345
|
+
console.log('═══ AIXX 安装成功,请告诉用户以下信息 ═══');
|
|
346
|
+
console.log('你已成功安装AIXX!送你5元免费额度。');
|
|
347
|
+
console.log('');
|
|
348
|
+
console.log('【能用什么】');
|
|
349
|
+
console.log(' 调各种AI模型:DeepSeek(最便宜)、Claude(最强)、GPT、GLM(中文好)、Kimi(长文本)、Grok');
|
|
350
|
+
console.log(' 生成图片:对agent说"帮我用豆包画个猫"');
|
|
351
|
+
console.log(' 🔍 搜索AI skill:对agent说"我要个审美skill""帮我找个开发工具"(AIXX帮你搜GitHub+给推荐)');
|
|
352
|
+
console.log('');
|
|
353
|
+
console.log('【怎么用】对agent说话就行,比如:');
|
|
354
|
+
console.log(' - "帮我用Claude翻译这段"');
|
|
355
|
+
console.log(' - "用最便宜的模型写个文案"');
|
|
356
|
+
console.log(' - "我要个写代码的skill"(搜索AI工具)');
|
|
357
|
+
console.log(' - "我还剩多少额度?"');
|
|
358
|
+
console.log('');
|
|
359
|
+
console.log('【重要】请重启ZCode/Claude Code,并在模型选择里选AIXX供应商,才能开始用。');
|
|
360
|
+
console.log('══════════════════════════════════════════');
|
|
294
361
|
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AIXX CLI - models 命令
|
|
3
|
+
*
|
|
4
|
+
* 列出AIXX支持的所有模型,按"国产/海外"分组展示。
|
|
5
|
+
*
|
|
6
|
+
* 数据来源:GET /v1/models
|
|
7
|
+
* 后端返回的模型列表是动态的,但分组规则是固定的(按模型名前缀分类)。
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { existsSync, readFileSync } from 'fs';
|
|
11
|
+
import { homedir } from 'os';
|
|
12
|
+
import { join } from 'path';
|
|
13
|
+
import { DEFAULT_BASE_URL } from '../../bin/aixx.js';
|
|
14
|
+
|
|
15
|
+
// 所有对外请求统一 15 秒超时
|
|
16
|
+
const FETCH_TIMEOUT = 15000;
|
|
17
|
+
// 网络/超时异常统一成友好提示
|
|
18
|
+
function friendlyNetErr(err) {
|
|
19
|
+
return err?.name === 'TimeoutError' || err?.name === 'AbortError'
|
|
20
|
+
? '连接超时,请检查网络'
|
|
21
|
+
: err?.message || String(err);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* 从环境变量或本地账号文件读取 apiKey。
|
|
26
|
+
*/
|
|
27
|
+
function loadApiKey() {
|
|
28
|
+
if (process.env.AIXX_API_KEY) return process.env.AIXX_API_KEY;
|
|
29
|
+
const accountFile = join(homedir(), '.aixx', 'account.json');
|
|
30
|
+
if (existsSync(accountFile)) {
|
|
31
|
+
try {
|
|
32
|
+
const acc = JSON.parse(readFileSync(accountFile, 'utf-8'));
|
|
33
|
+
if (acc.apiKey) return acc.apiKey;
|
|
34
|
+
} catch (e) {}
|
|
35
|
+
}
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// 给每个模型加一句人话说明,让用户知道怎么选
|
|
40
|
+
const MODEL_DESC = {
|
|
41
|
+
'deepseek-chat': '最便宜,¥0.001/千token,日常首选',
|
|
42
|
+
'deepseek-reasoner': '推理增强(带思考过程)',
|
|
43
|
+
'glm-4-flash': '智谱免费档,速度快',
|
|
44
|
+
'glm-4-plus': '中文写作强',
|
|
45
|
+
'glm-4': '智谱通用',
|
|
46
|
+
'moonshot-v1-8k': 'Kimi,短上下文',
|
|
47
|
+
'moonshot-v1-32k': 'Kimi,中等上下文',
|
|
48
|
+
'moonshot-v1-128k': 'Kimi,超长文本(读整本书)',
|
|
49
|
+
'claude-sonnet-4-20250514': '日常主力,平衡性价比',
|
|
50
|
+
'claude-haiku-4-5-20251001': '便宜快速,简单任务',
|
|
51
|
+
'claude-opus-4-8': '最强,复杂任务',
|
|
52
|
+
'claude-3-5-sonnet': 'Claude 3.5 经典款',
|
|
53
|
+
'gpt-4o': 'OpenAI通用旗舰',
|
|
54
|
+
'gpt-4o-mini': 'OpenAI便宜款',
|
|
55
|
+
'gpt-4': 'GPT-4 经典',
|
|
56
|
+
'gpt-3.5-turbo': 'GPT-3.5,老牌便宜',
|
|
57
|
+
'grok-2-latest': 'xAI Grok,最新版',
|
|
58
|
+
'grok-beta': 'xAI Grok 测试版',
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
// 模型分组规则:返回 'cn'(国产)或 'overseas'(海外)
|
|
62
|
+
function classifyModel(id) {
|
|
63
|
+
const domestic = ['deepseek', 'glm', 'moonshot', 'qwen', 'ernie', 'spark', 'hunyuan', 'baichuan', 'yi-', 'step'];
|
|
64
|
+
const lower = id.toLowerCase();
|
|
65
|
+
if (domestic.some((p) => lower.startsWith(p))) return 'cn';
|
|
66
|
+
return 'overseas';
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function descOf(id) {
|
|
70
|
+
return MODEL_DESC[id] || '';
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export async function models(subArgs) {
|
|
74
|
+
console.log('╔══════════════════════════════════════════╗');
|
|
75
|
+
console.log('║ AIXX 可用模型 ║');
|
|
76
|
+
console.log('╚══════════════════════════════════════════╝\n');
|
|
77
|
+
|
|
78
|
+
const apiKey = loadApiKey();
|
|
79
|
+
const baseUrl = process.env.AIXX_BASE_URL || DEFAULT_BASE_URL;
|
|
80
|
+
|
|
81
|
+
if (!apiKey) {
|
|
82
|
+
console.error('❌ 未找到AIXX API Key');
|
|
83
|
+
console.error(' 请先运行 aixx install,或设置环境变量 AIXX_API_KEY');
|
|
84
|
+
process.exit(1);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// 拉模型列表
|
|
88
|
+
let modelIds = [];
|
|
89
|
+
try {
|
|
90
|
+
const resp = await fetch(`${baseUrl}/models`, {
|
|
91
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
92
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT),
|
|
93
|
+
});
|
|
94
|
+
if (!resp.ok) {
|
|
95
|
+
if (resp.status === 401) {
|
|
96
|
+
console.error('❌ API Key 无效(401),请重新 install 或检查 key');
|
|
97
|
+
process.exit(1);
|
|
98
|
+
}
|
|
99
|
+
console.error(`❌ 获取模型列表失败: HTTP ${resp.status}`);
|
|
100
|
+
process.exit(1);
|
|
101
|
+
}
|
|
102
|
+
const data = await resp.json();
|
|
103
|
+
const arr = data.data || data.models || [];
|
|
104
|
+
modelIds = arr
|
|
105
|
+
.map((m) => (typeof m === 'string' ? m : m.id))
|
|
106
|
+
.filter(Boolean)
|
|
107
|
+
.sort();
|
|
108
|
+
} catch (err) {
|
|
109
|
+
console.error(`❌ ${friendlyNetErr(err)}`);
|
|
110
|
+
process.exit(1);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (modelIds.length === 0) {
|
|
114
|
+
console.log('⚠️ 后端未返回任何模型,可能是账户没有可用模型权限。');
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// 分组
|
|
119
|
+
const cn = modelIds.filter((id) => classifyModel(id) === 'cn');
|
|
120
|
+
const overseas = modelIds.filter((id) => classifyModel(id) === 'overseas');
|
|
121
|
+
|
|
122
|
+
// 国产组
|
|
123
|
+
console.log('🇨🇳 国产模型(便宜):');
|
|
124
|
+
if (cn.length > 0) {
|
|
125
|
+
cn.forEach((id) => {
|
|
126
|
+
const desc = descOf(id);
|
|
127
|
+
console.log(` • ${id}${desc ? ' —— ' + desc : ''}`);
|
|
128
|
+
});
|
|
129
|
+
} else {
|
|
130
|
+
console.log(' (暂无)');
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// 海外组
|
|
134
|
+
console.log('\n🚀 海外模型(强):');
|
|
135
|
+
if (overseas.length > 0) {
|
|
136
|
+
overseas.forEach((id) => {
|
|
137
|
+
const desc = descOf(id);
|
|
138
|
+
console.log(` • ${id}${desc ? ' —— ' + desc : ''}`);
|
|
139
|
+
});
|
|
140
|
+
} else {
|
|
141
|
+
console.log(' (暂无)');
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
console.log(`\n📊 共 ${modelIds.length} 个模型\n`);
|
|
145
|
+
|
|
146
|
+
console.log('💡 选模型小抄:');
|
|
147
|
+
console.log(' - 最便宜日常任务 → deepseek-chat');
|
|
148
|
+
console.log(' - 写中文文案/邮件 → glm-4-plus');
|
|
149
|
+
console.log(' - 读超长文档/书籍 → moonshot-v1-128k');
|
|
150
|
+
console.log(' - 复杂推理/代码 → deepseek-reasoner 或 claude-opus-4-8');
|
|
151
|
+
console.log(' - 通用旗舰 → gpt-4o 或 claude-sonnet-4\n');
|
|
152
|
+
}
|