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.
@@ -0,0 +1,143 @@
1
+ /**
2
+ * AIXX CLI - recharge 命令
3
+ *
4
+ * 显示充值方式。优先尝试调后端 creem/pay 接口生成在线充值链接;
5
+ * 如果后端不支持或失败,则给出通用引导(联系客服/后台充值)。
6
+ */
7
+
8
+ import { existsSync, readFileSync } from 'fs';
9
+ import { homedir } from 'os';
10
+ import { join } from 'path';
11
+ import { DEFAULT_BASE_URL } from '../../bin/aixx.js';
12
+
13
+ // 所有对外请求统一 15 秒超时
14
+ const FETCH_TIMEOUT = 15000;
15
+
16
+ /**
17
+ * 从环境变量或本地账号文件读取 apiKey。
18
+ */
19
+ function loadApiKey() {
20
+ if (process.env.AIXX_API_KEY) return process.env.AIXX_API_KEY;
21
+ const accountFile = join(homedir(), '.aixx', 'account.json');
22
+ if (existsSync(accountFile)) {
23
+ try {
24
+ const acc = JSON.parse(readFileSync(accountFile, 'utf-8'));
25
+ if (acc.apiKey) return acc.apiKey;
26
+ } catch (e) {}
27
+ }
28
+ return null;
29
+ }
30
+
31
+ /**
32
+ * 尝试调后端的 creem 支付接口生成充值链接。
33
+ * New-API 的在线充值常见接口:
34
+ * POST /api/user/creem/pay { amount, ... }
35
+ * 不同版本字段不一,失败就回退到手动引导。
36
+ */
37
+ async function tryGetCreemLink(apiKey, baseUrl, amount) {
38
+ const apiBase = baseUrl.replace(/\/v1$/, '');
39
+ const candidates = [
40
+ {
41
+ url: `${apiBase}/api/user/creem/pay`,
42
+ body: { amount, currency: 'USD', gateway: 'creem' },
43
+ },
44
+ {
45
+ url: `${apiBase}/api/user/topup`,
46
+ body: { amount, top_up_code: '', payment_method: 'creem' },
47
+ },
48
+ ];
49
+
50
+ for (const c of candidates) {
51
+ try {
52
+ const resp = await fetch(c.url, {
53
+ method: 'POST',
54
+ headers: {
55
+ Authorization: `Bearer ${apiKey}`,
56
+ 'Content-Type': 'application/json',
57
+ },
58
+ body: JSON.stringify(c.body),
59
+ signal: AbortSignal.timeout(FETCH_TIMEOUT),
60
+ });
61
+ if (!resp.ok) continue;
62
+ const data = await resp.json().catch(() => null);
63
+ if (!data) continue;
64
+ // 多种可能的字段名兜底
65
+ const link =
66
+ data.data?.url ||
67
+ data.data?.payment_url ||
68
+ data.data?.checkout_url ||
69
+ data.url ||
70
+ data.payment_url;
71
+ if (link) return link;
72
+ } catch (e) {
73
+ // 超时单独提示一次(其余错误静默试下一个候选接口)
74
+ if (e?.name === 'TimeoutError' || e?.name === 'AbortError') {
75
+ console.log(' ⚠️ 连接超时,请检查网络');
76
+ }
77
+ }
78
+ }
79
+ return null;
80
+ }
81
+
82
+ export async function recharge(subArgs) {
83
+ console.log('╔══════════════════════════════════════════╗');
84
+ console.log('║ AIXX 充值 ║');
85
+ console.log('╚══════════════════════════════════════════╝\n');
86
+
87
+ const apiKey = loadApiKey();
88
+ const baseUrl = process.env.AIXX_BASE_URL || DEFAULT_BASE_URL;
89
+
90
+ // 从参数取金额,默认 $5(最低)
91
+ let amount = 5;
92
+ if (subArgs[0]) {
93
+ const n = Number(subArgs[0]);
94
+ if (!Number.isNaN(n) && n > 0) amount = n;
95
+ }
96
+
97
+ if (!apiKey) {
98
+ console.log('⚠️ 未检测到AIXX API Key,以下为通用充值方式:\n');
99
+ console.log('充值方式:');
100
+ console.log(' 信用卡 / ApplePay:登录 AIXX 后台 → 充值 → Creem');
101
+ console.log(' USDT 加密货币:联系客服或后台提交');
102
+ console.log(`\n 最低充值 $5`);
103
+ console.log(`\n🌐 后台地址:${baseUrl.replace(/\/v1$/, '')}`);
104
+ return;
105
+ }
106
+
107
+ console.log(`使用Key: ${apiKey.slice(0, 12)}...${apiKey.slice(-4)}`);
108
+ console.log(`充值金额:$${amount}\n`);
109
+
110
+ // 尝试生成在线支付链接
111
+ console.log('🔄 正在生成在线充值链接...');
112
+ const link = await tryGetCreemLink(apiKey, baseUrl, amount);
113
+
114
+ console.log('\n────────────────────────────────────');
115
+ console.log('💳 充值方式:');
116
+ console.log('────────────────────────────────────');
117
+
118
+ if (link) {
119
+ console.log('\n ① 信用卡 / ApplePay(推荐,即时到账):');
120
+ console.log(` ${link}`);
121
+ console.log('\n 复制链接到浏览器打开即可支付。');
122
+ } else {
123
+ console.log('\n ① 信用卡 / ApplePay:');
124
+ console.log(' 自动生成链接失败,请直接登录后台充值:');
125
+ console.log(` ${baseUrl.replace(/\/v1$/, '')} → 充值`);
126
+ }
127
+
128
+ console.log('\n ② USDT 加密货币:');
129
+ console.log(' 联系 AIXX 客服,或后台提交 USDT 充值订单');
130
+ console.log(' (后续会支持自动生成钱包地址)');
131
+
132
+ console.log('\n ③ 微信/支付宝:');
133
+ console.log(' 联系 AIXX 客服走人工充值');
134
+
135
+ console.log('\n────────────────────────────────────');
136
+ console.log(`最低充值 $5(约 ¥${(amount * 7).toFixed(0)})`);
137
+ console.log(`充值后额度即时到账,可直接使用。\n`);
138
+
139
+ console.log('💡 充值提示:');
140
+ console.log(' - 充 $5 用 deepseek-chat 能聊 5 万次以上');
141
+ console.log(' - 充 $20 够用 Claude/GPT 跑一个月');
142
+ console.log(' - 推荐码注册的用户充值后推荐人有分润\n');
143
+ }
@@ -0,0 +1,374 @@
1
+ /**
2
+ * agent配置写入工具
3
+ *
4
+ * 负责把AIXX的供应商配置写进本地agent的配置文件,实现"零配置"。
5
+ * 当前支持两个agent:
6
+ * 1. ZCode -> ~/.zcode/v2/config.json (加两个供应商:AIXX-Claude / AIXX-OpenAI)
7
+ * 2. Claude Code-> ~/.claude/settings.json (往env块写 ANTHROPIC_BASE_URL / ANTHROPIC_AUTH_TOKEN)
8
+ *
9
+ * ⛔ 绝对禁止改写 ZCode 的 setting.json(~/.zcode/v2/setting.json)!
10
+ * 历史教训(2026-08-09 重大事故,见 token-hub/项目体系/TIMELINE.md 会话7):
11
+ * 早期版本曾把 setting.json 的 providerFamilyDomain 改成 "aixx",而 ZCode
12
+ * 只接受内置家族名(zai/bigmodel),非法值直接导致 ZCode 崩溃、任务/项目丢失。
13
+ * 本工具从此只允许操作 ZCode 的 config.json(加供应商),绝不碰 setting.json。
14
+ *
15
+ * 三条铁律(K哥交代的安全要求):
16
+ * 1. 写入前必须备份原文件到 ~/.aixx/backups/
17
+ * 2. 只做 JSON 读-改-写,绝不整体覆盖
18
+ * 3. 所有写入都加 "_aixx_managed": true 标记,重复 install 只更新 key 不重建(幂等)
19
+ *
20
+ * 纯Node标准库,零依赖。
21
+ */
22
+
23
+ import {
24
+ existsSync,
25
+ readFileSync,
26
+ writeFileSync,
27
+ mkdirSync,
28
+ copyFileSync,
29
+ } from 'fs';
30
+ import { homedir } from 'os';
31
+ import { join, dirname } from 'path';
32
+ import { randomUUID } from 'crypto';
33
+
34
+ // AIXX后端地址(去掉/v1,因为ZCode的baseURL要写到主机根,OpenAI的写到/v1)
35
+ // ⚠️ 安全警告:当前用HTTP明文传输API Key(http://)。
36
+ const AIXX_HOST = 'http://14.103.27.195:8080';
37
+
38
+ // 用 _aixx_managed 标记找供应商时,按"作用域"区分,避免Claude/OpenAI两条记混
39
+ const MANAGED_FLAG = '_aixx_managed';
40
+
41
+ /**
42
+ * 生成时间戳字符串,用于备份文件名
43
+ * 形如 20260808-153045
44
+ */
45
+ function timestamp() {
46
+ const d = new Date();
47
+ const pad = (n) => String(n).padStart(2, '0');
48
+ return (
49
+ d.getFullYear() +
50
+ pad(d.getMonth() + 1) +
51
+ pad(d.getDate()) +
52
+ '-' +
53
+ pad(d.getHours()) +
54
+ pad(d.getMinutes()) +
55
+ pad(d.getSeconds())
56
+ );
57
+ }
58
+
59
+ /**
60
+ * 备份一个文件到 ~/.aixx/backups/<原名>-<时间戳>.json
61
+ * 如果源文件不存在,直接返回null(没东西可备份,属正常)。
62
+ * 如果同一秒内多次备份(时间戳相同),自动加序号后缀避免覆盖。
63
+ * 返回值:
64
+ * - string:备份文件路径(成功)
65
+ * - null:源文件不存在(无需备份,可继续写入)
66
+ * - false:源文件存在但备份失败(铁律1:必须中止写入)
67
+ */
68
+ function backupFile(srcPath) {
69
+ if (!existsSync(srcPath)) return null;
70
+ const backupDir = join(homedir(), '.aixx', 'backups');
71
+ if (!existsSync(backupDir)) mkdirSync(backupDir, { recursive: true });
72
+ const base = srcPath
73
+ .split(/[\\/]/)
74
+ .pop()
75
+ .replace(/\.json$/, '');
76
+ // 时间戳精确到秒;若已存在同名备份,加序号 -1, -2 ... 避免覆盖
77
+ let dest = join(backupDir, `${base}-${timestamp()}.json`);
78
+ let seq = 1;
79
+ while (existsSync(dest)) {
80
+ dest = join(backupDir, `${base}-${timestamp()}-${seq}.json`);
81
+ seq++;
82
+ }
83
+ try {
84
+ copyFileSync(srcPath, dest);
85
+ return dest;
86
+ } catch (e) {
87
+ // 铁律1:备份失败绝不继续写入。返回false让调用方中止。
88
+ console.log(` ⚠️ 备份失败: ${e.message}`);
89
+ return false;
90
+ }
91
+ }
92
+
93
+ /**
94
+ * 安全读取并解析JSON文件。
95
+ * 失败返回 null(绝不抛出,调用方自行处理)。
96
+ */
97
+ function readJsonSafe(filePath) {
98
+ try {
99
+ const raw = readFileSync(filePath, 'utf-8');
100
+ return JSON.parse(raw);
101
+ } catch (e) {
102
+ return null;
103
+ }
104
+ }
105
+
106
+ /**
107
+ * 在 ZCode config.json 的 provider 里,找出带 _aixx_managed 且 scope 匹配的供应商key。
108
+ * 返回供应商在config里的key(如 "aixx-claude-xxxx-uuid"),找不到返回null。
109
+ */
110
+ function findManagedProvider(config, scope) {
111
+ const providers = config?.provider;
112
+ if (!providers || typeof providers !== 'object') return null;
113
+ for (const [key, val] of Object.entries(providers)) {
114
+ if (val && val[MANAGED_FLAG] === scope) {
115
+ return key;
116
+ }
117
+ }
118
+ return null;
119
+ }
120
+
121
+ /**
122
+ * AIXX-Claude 供应商的模型清单(Anthropic协议,调Claude系)
123
+ * limit.context / limit.output 是 ZCode 必填字段,单位 token
124
+ */
125
+ function buildClaudeModels() {
126
+ return {
127
+ 'claude-sonnet-4-20250514': { limit: { context: 200000, output: 8192 } },
128
+ 'claude-haiku-4-5-20251001': { limit: { context: 200000, output: 8192 } },
129
+ 'claude-opus-4-8': { limit: { context: 200000, output: 8192 } },
130
+ };
131
+ }
132
+
133
+ /**
134
+ * AIXX-OpenAI 供应商的模型清单(OpenAI协议,调国产/DeepSeek/GLM/Kimi)
135
+ */
136
+ function buildOpenaiModels() {
137
+ return {
138
+ 'deepseek-chat': { limit: { context: 64000, output: 8192 } },
139
+ 'deepseek-reasoner': { limit: { context: 64000, output: 8192 } },
140
+ 'glm-4-flash': { limit: { context: 128000, output: 4096 } },
141
+ 'glm-4-plus': { limit: { context: 128000, output: 4096 } },
142
+ 'moonshot-v1-8k': { limit: { context: 8000, output: 4096 } },
143
+ };
144
+ }
145
+
146
+ /**
147
+ * 构造一个 AIXX-Claude 供应商对象(Anthropic协议)
148
+ */
149
+ function buildClaudeProvider(apiKey) {
150
+ return {
151
+ name: 'AIXX(Claude)',
152
+ kind: 'anthropic',
153
+ options: {
154
+ apiKey,
155
+ baseURL: AIXX_HOST,
156
+ apiKeyRequired: true,
157
+ },
158
+ enabled: true,
159
+ source: 'custom',
160
+ [MANAGED_FLAG]: 'claude', // 幂等标记 + 作用域
161
+ models: buildClaudeModels(),
162
+ };
163
+ }
164
+
165
+ /**
166
+ * 构造一个 AIXX-OpenAI 供应商对象(OpenAI协议)
167
+ */
168
+ function buildOpenaiProvider(apiKey) {
169
+ return {
170
+ name: 'AIXX(国产/DeepSeek)',
171
+ kind: 'openai',
172
+ options: {
173
+ apiKey,
174
+ baseURL: AIXX_HOST + '/v1',
175
+ apiKeyRequired: true,
176
+ },
177
+ enabled: true,
178
+ source: 'custom',
179
+ [MANAGED_FLAG]: 'openai', // 幂等标记 + 作用域
180
+ models: buildOpenaiModels(),
181
+ };
182
+ }
183
+
184
+ /**
185
+ * 写入 ZCode 配置。
186
+ *
187
+ * 行为:
188
+ * - 如果 ~/.zcode/v2/config.json 不存在 → 直接返回false(没装ZCode)
189
+ * - 备份原文件
190
+ * - 读-改-写:往 provider 里塞两个AIXX供应商
191
+ * - 幂等:如果已有 _aixx_managed 供应商,只更新 apiKey(保留用户可能改过的name/enabled)
192
+ * - 任何一步失败都不崩溃,返回false
193
+ *
194
+ * 返回 { configured: true/false, note: '...' }
195
+ */
196
+ export function configureZCode(apiKey) {
197
+ const zcodeConfigPath = join(homedir(), '.zcode', 'v2', 'config.json');
198
+
199
+ if (!existsSync(zcodeConfigPath)) {
200
+ return { configured: false, note: '未检测到ZCode(~/.zcode/v2/config.json不存在),跳过' };
201
+ }
202
+
203
+ // 读取并解析
204
+ const config = readJsonSafe(zcodeConfigPath);
205
+ if (!config || typeof config !== 'object') {
206
+ console.log(' ⚠️ ZCode config.json 解析失败,跳过ZCode配置(避免破坏文件)');
207
+ return { configured: false, note: 'ZCode config.json 解析失败' };
208
+ }
209
+
210
+ // 确保 provider 对象存在
211
+ if (!config.provider || typeof config.provider !== 'object') {
212
+ config.provider = {};
213
+ }
214
+
215
+ // 备份原文件
216
+ const backup = backupFile(zcodeConfigPath);
217
+ if (backup === false) {
218
+ // 铁律1:备份失败,绝不写入(避免破坏用户现有配置)
219
+ console.log(' ⚠️ 配置备份失败,请手动备份后再试(已跳过ZCode配置)');
220
+ return { configured: false, note: '配置备份失败,请手动备份后再试' };
221
+ }
222
+ if (backup) {
223
+ console.log(` 📦 已备份ZCode配置: ${backup}`);
224
+ }
225
+
226
+ let updated = 0;
227
+
228
+ // --- AIXX-Claude 供应商 ---
229
+ const claudeKey = findManagedProvider(config, 'claude');
230
+ if (claudeKey) {
231
+ // 幂等:只更新key(如果用户改过name/enabled,保留)
232
+ const existing = config.provider[claudeKey];
233
+ existing.options = existing.options || {};
234
+ existing.options.apiKey = apiKey;
235
+ existing.options.baseURL = AIXX_HOST;
236
+ existing.options.apiKeyRequired = true;
237
+ // 模型清单刷成最新(用户不太会改,但万一后端加了新模型能跟上)
238
+ existing.models = buildClaudeModels();
239
+ existing.kind = 'anthropic';
240
+ existing[MANAGED_FLAG] = 'claude';
241
+ console.log(' ✏️ 更新已有AIXX(Claude)供应商(key已刷新)');
242
+ updated++;
243
+ } else {
244
+ // 新增:用一个稳定的UUID做key
245
+ const newKey = `aixx-claude-${randomUUID()}`;
246
+ config.provider[newKey] = buildClaudeProvider(apiKey);
247
+ console.log(' ➕ 新增AIXX(Claude)供应商');
248
+ updated++;
249
+ }
250
+
251
+ // --- AIXX-OpenAI 供应商 ---
252
+ const openaiKey = findManagedProvider(config, 'openai');
253
+ if (openaiKey) {
254
+ const existing = config.provider[openaiKey];
255
+ existing.options = existing.options || {};
256
+ existing.options.apiKey = apiKey;
257
+ existing.options.baseURL = AIXX_HOST + '/v1';
258
+ existing.options.apiKeyRequired = true;
259
+ existing.models = buildOpenaiModels();
260
+ existing.kind = 'openai';
261
+ existing[MANAGED_FLAG] = 'openai';
262
+ console.log(' ✏️ 更新已有AIXX(国产/DeepSeek)供应商(key已刷新)');
263
+ updated++;
264
+ } else {
265
+ const newKey = `aixx-openai-${randomUUID()}`;
266
+ config.provider[newKey] = buildOpenaiProvider(apiKey);
267
+ console.log(' ➕ 新增AIXX(国产/DeepSeek)供应商');
268
+ updated++;
269
+ }
270
+
271
+ // 写回
272
+ try {
273
+ writeFileSync(zcodeConfigPath, JSON.stringify(config, null, 2));
274
+ return {
275
+ configured: true,
276
+ note: `已配置ZCode供应商,请重启ZCode并在模型选择里选AIXX`,
277
+ updated,
278
+ };
279
+ } catch (e) {
280
+ console.log(` ⚠️ 写入ZCode配置失败: ${e.message}`);
281
+ return { configured: false, note: `写入失败: ${e.message}` };
282
+ }
283
+ }
284
+
285
+ /**
286
+ * 写入 Claude Code 配置。
287
+ *
288
+ * 往 ~/.claude/settings.json 的 env 块写:
289
+ * ANTHROPIC_BASE_URL / ANTHROPIC_AUTH_TOKEN
290
+ *
291
+ * 注意:这会覆盖用户已有的 Claude 配置(如果已经配过别的baseURL/token)。
292
+ * 所以要先检测是否有冲突,冲突时只更新key但保留提示。
293
+ *
294
+ * 返回 { configured: true/false, note: '...', overwritten: true/false }
295
+ */
296
+ export function configureClaudeCode(apiKey) {
297
+ const claudeSettingsPath = join(homedir(), '.claude', 'settings.json');
298
+
299
+ if (!existsSync(claudeSettingsPath)) {
300
+ return { configured: false, note: '未检测到Claude Code(~/.claude/settings.json不存在),跳过' };
301
+ }
302
+
303
+ const settings = readJsonSafe(claudeSettingsPath);
304
+ if (!settings || typeof settings !== 'object') {
305
+ console.log(' ⚠️ Claude settings.json 解析失败,跳过Claude Code配置');
306
+ return { configured: false, note: 'Claude settings.json 解析失败' };
307
+ }
308
+
309
+ // 备份
310
+ const backup = backupFile(claudeSettingsPath);
311
+ if (backup === false) {
312
+ // 铁律1:备份失败,绝不写入(避免破坏用户现有配置)
313
+ console.log(' ⚠️ 配置备份失败,请手动备份后再试(已跳过Claude Code配置)');
314
+ return { configured: false, note: '配置备份失败,请手动备份后再试' };
315
+ }
316
+ if (backup) {
317
+ console.log(` 📦 已备份Claude Code配置: ${backup}`);
318
+ }
319
+
320
+ // 检测是否已有冲突配置(不是AIXX的baseURL)
321
+ let overwritten = false;
322
+ const oldBase = settings?.env?.ANTHROPIC_BASE_URL;
323
+ if (oldBase && oldBase !== AIXX_HOST) {
324
+ overwritten = true;
325
+ }
326
+
327
+ // 幂等 + 写入
328
+ settings.env = settings.env || {};
329
+ settings.env.ANTHROPIC_BASE_URL = AIXX_HOST;
330
+ settings.env.ANTHROPIC_AUTH_TOKEN = apiKey;
331
+
332
+ try {
333
+ writeFileSync(claudeSettingsPath, JSON.stringify(settings, null, 2));
334
+ return {
335
+ configured: true,
336
+ note: overwritten
337
+ ? '已配置Claude Code(覆盖了你原有的Claude供应商,已备份)'
338
+ : '已配置Claude Code',
339
+ overwritten,
340
+ };
341
+ } catch (e) {
342
+ console.log(` ⚠️ 写入Claude Code配置失败: ${e.message}`);
343
+ return { configured: false, note: `写入失败: ${e.message}` };
344
+ }
345
+ }
346
+
347
+ /**
348
+ * 入口:自动检测并配置所有能找到的agent。
349
+ * 由 install.js 在拿到apiKey后调用。
350
+ *
351
+ * 返回 { zcode, claude } 两个结果对象。
352
+ */
353
+ export function configureAgents(apiKey) {
354
+ console.log('\n🔌 配置本地agent(零配置核心)...');
355
+
356
+ const zcode = configureZCode(apiKey);
357
+ if (zcode.configured) {
358
+ console.log(` ✅ ZCode: ${zcode.note}`);
359
+ } else {
360
+ console.log(` ⏭️ ZCode: ${zcode.note}`);
361
+ }
362
+
363
+ const claude = configureClaudeCode(apiKey);
364
+ if (claude.configured) {
365
+ console.log(` ✅ Claude Code: ${claude.note}`);
366
+ if (claude.overwritten) {
367
+ console.log(' ⚠️ 注意:你原有的Claude供应商被AIXX覆盖了。如需恢复,去备份目录找原配置。');
368
+ }
369
+ } else {
370
+ console.log(` ⏭️ Claude Code: ${claude.note}`);
371
+ }
372
+
373
+ return { zcode, claude };
374
+ }
package/src/utils/env.js CHANGED
@@ -36,14 +36,28 @@ export function setupEnv(apiKey, baseUrl = DEFAULT_BASE_URL) {
36
36
  `export AIXX_BASE_URL="${baseUrl}"`,
37
37
  ].join('\n');
38
38
 
39
- // 检测shell类型
40
- const shellConfigs = [
41
- join(home, '.zshrc'),
42
- join(home, '.bashrc'),
43
- join(home, '.bash_profile'),
44
- ];
39
+ // 检测当前用的shell(优先看$SHELL,比按文件存在猜更准)
40
+ // Mac默认zsh,Linux多是bash。$SHELL是用户登录shell,最可靠。
41
+ const currentShell = process.env.SHELL || '';
42
+ let shellConfigs;
43
+ if (currentShell.includes('zsh')) {
44
+ // zsh用户:写.zshrc(Mac默认)
45
+ shellConfigs = [join(home, '.zshrc')];
46
+ } else if (currentShell.includes('bash')) {
47
+ // bash用户:Linux写.bashrc,Mac写.bash_profile(Mac的bash读.bash_profile)
48
+ shellConfigs = platform === 'darwin'
49
+ ? [join(home, '.bash_profile'), join(home, '.bashrc')]
50
+ : [join(home, '.bashrc'), join(home, '.bash_profile')];
51
+ } else {
52
+ // 检测不到(如fish等),按文件存在兜底
53
+ shellConfigs = [
54
+ join(home, '.zshrc'),
55
+ join(home, '.bashrc'),
56
+ join(home, '.bash_profile'),
57
+ ];
58
+ }
45
59
 
46
- let written = false;
60
+ let writtenFile = null;
47
61
  for (const conf of shellConfigs) {
48
62
  if (existsSync(conf)) {
49
63
  // 检查是否已配置过(避免重复)
@@ -51,20 +65,24 @@ export function setupEnv(apiKey, baseUrl = DEFAULT_BASE_URL) {
51
65
  if (!existing.includes('AIXX_API_KEY')) {
52
66
  appendFileSync(conf, envLines);
53
67
  console.log(` → 已写入 ${conf}`);
68
+ } else {
69
+ console.log(` → ${conf} 已有AIXX配置,跳过`);
54
70
  }
55
- written = true;
71
+ writtenFile = conf;
56
72
  break;
57
73
  }
58
74
  }
59
75
 
60
- if (!written) {
61
- // 没找到现成配置文件,写.bashrc
62
- const bashrc = join(home, '.bashrc');
63
- appendFileSync(bashrc, envLines);
64
- console.log(` → 已写入 ${bashrc}`);
76
+ if (!writtenFile) {
77
+ // 没找到现成配置文件,按当前shell创建一个
78
+ const fallback = currentShell.includes('zsh') ? join(home, '.zshrc') : join(home, '.bashrc');
79
+ appendFileSync(fallback, envLines);
80
+ console.log(` → 已创建并写入 ${fallback}`);
81
+ writtenFile = fallback;
65
82
  }
66
83
 
67
- console.log(' ⚠️ 请运行 source ~/.bashrc 或 source ~/.zshrc 让配置生效');
84
+ const rcName = writtenFile.split(/[\\/]/).pop();
85
+ console.log(` ⚠️ 请运行 source ~/${rcName} 让配置生效(或重开终端)`);
68
86
  } else {
69
87
  // Windows: 写入用户环境变量(用setx)
70
88
  console.log(' Windows用户请手动设置环境变量,或运行以下命令(PowerShell):');
@@ -20,9 +20,9 @@ metadata:
20
20
 
21
21
  AIXX = "AI的谷歌"。装一个skill,agent就能:
22
22
  - 🤖 **调用各种LLM**:DeepSeek/GLM/Kimi/Claude/GPT等,自动选最优
23
- - 🎨 **生成图片/视频**(2.0)
23
+ - 🎨 **生成图片**:豆包生图(doubao-seedream)
24
+ - 🔍 **搜索 AI skill/工具**:用户说"我要个审美skill",AIXX去GitHub搜+给中文推荐理由+安装方式(**已上线**)
24
25
  - 📊 **查询数据**:社媒数据(TikHub)、电商数据(JustOne)(2.0)
25
- - 🔍 **搜索GitHub开源工具**,推荐最优+给安装指南(2.0)
26
26
 
27
27
  **核心价值**:用户不碰key、不碰参数、不碰文档。一切通过对话。
28
28
 
@@ -35,6 +35,7 @@ AIXX = "AI的谷歌"。装一个skill,agent就能:
35
35
  - 指定了特定模型的请求("用Claude翻译""用DeepSeek分析")
36
36
  - 查询用量/花费/余额("我花了多少""还剩多少额度")
37
37
  - 切换模型("用最便宜的""切到GPT")
38
+ - **找/推荐 AI skill/工具/agent**("我要个审美skill""帮我找个开发工具""推荐个翻译agent")→ 走搜索(见 search.md)
38
39
 
39
40
  ### 不触发AIXX的场景(用agent自己能力)
40
41
  - 简单闲聊、确认、日常对话
@@ -66,6 +67,11 @@ agent判断:需要AI能力吗?
66
67
  - 入口:[`./references/chat.md`](./references/chat.md)
67
68
  - 覆盖:翻译、写作、分析、问答、代码等,自动选模型或指定模型
68
69
 
70
+ - **搜索 AI skill/工具**(AIXX的"AI的谷歌")
71
+ - 入口:[`./references/search.md`](./references/search.md)
72
+ - 覆盖:找skill、推荐工具、发现新AI能力,给中文推荐理由+安装方式
73
+ - 场景:用户说"我要个X的skill""帮我找个Y工具""推荐个Z agent"
74
+
69
75
  - **用量查询与账户管理**
70
76
  - 入口:[`./references/account.md`](./references/account.md)
71
77
  - 覆盖:查余额、查花费、查用量、切换默认模型