aixxai 1.4.4 → 1.5.1
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 +88 -88
- package/package.json +1 -1
- package/src/utils/agentconfig.js +2 -2
- package/src/utils/download.js +147 -147
- package/src/utils/env.js +135 -136
- package/templates/QUICKSTART.md +100 -100
- package/templates/SKILL.md +137 -137
- package/templates/references/account.md +68 -68
- package/templates/references/aixx-shared.md +1 -1
- package/templates/references/apps.md +199 -208
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
|
-
//
|
|
35
|
-
// 改完后请全局搜索 "安全警告" 同步更新 env.js 与 templates/QUICKSTART.md 中的同名地址。
|
|
36
|
-
export const DEFAULT_BASE_URL = '
|
|
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
|
+
// 会话16换门:统一走域名网关入口(详见 src/utils/env.js 注释)
|
|
35
|
+
// 改完后请全局搜索 "安全警告" 同步更新 env.js 与 templates/QUICKSTART.md 中的同名地址。
|
|
36
|
+
export const DEFAULT_BASE_URL = 'https://baodan.run/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/utils/agentconfig.js
CHANGED
|
@@ -32,8 +32,8 @@ import { join, dirname } from 'path';
|
|
|
32
32
|
import { randomUUID } from 'crypto';
|
|
33
33
|
|
|
34
34
|
// AIXX后端地址(去掉/v1,因为ZCode的baseURL要写到主机根,OpenAI的写到/v1)
|
|
35
|
-
//
|
|
36
|
-
const AIXX_HOST = '
|
|
35
|
+
// 会话16换门:域名网关入口(HTTPS加密),旧IP:8080仅存量兼容
|
|
36
|
+
const AIXX_HOST = 'https://baodan.run';
|
|
37
37
|
|
|
38
38
|
// 用 _aixx_managed 标记找供应商时,按"作用域"区分,避免Claude/OpenAI两条记混
|
|
39
39
|
const MANAGED_FLAG = '_aixx_managed';
|
package/src/utils/download.js
CHANGED
|
@@ -1,147 +1,147 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* skill文件下载工具
|
|
3
|
-
*
|
|
4
|
-
* 策略:skill文件内嵌在CLI包里(templates/目录),
|
|
5
|
-
* install时从包内复制到目标目录。
|
|
6
|
-
* 这样即使网络挂了也能完成安装。
|
|
7
|
-
*
|
|
8
|
-
* 注:templates/目录在发布时一起打包进npm包
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import { copyFileSync, existsSync, mkdirSync, readdirSync, statSync, writeFileSync } from 'fs';
|
|
12
|
-
import { join, dirname, relative } from 'path';
|
|
13
|
-
import { fileURLToPath } from 'url';
|
|
14
|
-
|
|
15
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
16
|
-
|
|
17
|
-
// 最小化的skill内容(确保即使templates目录缺失也能安装)
|
|
18
|
-
function getMinimalSkill() {
|
|
19
|
-
return {
|
|
20
|
-
'SKILL.md': [
|
|
21
|
-
'---',
|
|
22
|
-
'name: aixx',
|
|
23
|
-
'version: 0.1.0',
|
|
24
|
-
'description: "AIXX - 让你的AI调用万物。一个skill,agent就能调用全世界的AI能力。"',
|
|
25
|
-
'metadata:',
|
|
26
|
-
' category: "productivity"',
|
|
27
|
-
' requires:',
|
|
28
|
-
' env: ["AIXX_API_KEY", "AIXX_BASE_URL"]',
|
|
29
|
-
'---',
|
|
30
|
-
'',
|
|
31
|
-
'# AIXX Skill',
|
|
32
|
-
'',
|
|
33
|
-
'你是 AI Agent。通过 AIXX 中转站调用各种 AI 能力。',
|
|
34
|
-
'用户永远不用碰 key、参数、文档——一切通过你(agent)和 AIXX 对接。',
|
|
35
|
-
'',
|
|
36
|
-
'## 触发规则',
|
|
37
|
-
'',
|
|
38
|
-
'当用户提出需要LLM能力的请求时(翻译/写作/分析/问答/代码等),',
|
|
39
|
-
'先调AIXX(通常更便宜或更强)。',
|
|
40
|
-
'',
|
|
41
|
-
'## 调用方式(兼容OpenAI接口)',
|
|
42
|
-
'',
|
|
43
|
-
'- POST {AIXX_BASE_URL}/chat/completions',
|
|
44
|
-
'- Header: Authorization: Bearer {AIXX_API_KEY}',
|
|
45
|
-
'- Body: {"model":"deepseek-chat","messages":[...]}',
|
|
46
|
-
'',
|
|
47
|
-
'## 默认模型选择',
|
|
48
|
-
'',
|
|
49
|
-
'- 简短任务/翻译/问答 -> deepseek-chat(最便宜)',
|
|
50
|
-
'- 长文档 -> moonshot-v1-128k(Kimi长文本)',
|
|
51
|
-
'- 推理 -> deepseek-reasoner',
|
|
52
|
-
'- 中文写作 -> deepseek-chat(glm渠道维护中)',
|
|
53
|
-
'',
|
|
54
|
-
'## 查余额',
|
|
55
|
-
'',
|
|
56
|
-
'调 {AIXX_BASE_URL}/dashboard/billing/subscription',
|
|
57
|
-
'',
|
|
58
|
-
'完整文档:https://gitee.com/kk0803/token-hub',
|
|
59
|
-
''
|
|
60
|
-
].join('\n'),
|
|
61
|
-
|
|
62
|
-
'QUICKSTART.md': [
|
|
63
|
-
'# AIXX 快速开始',
|
|
64
|
-
'',
|
|
65
|
-
'## 配置环境变量',
|
|
66
|
-
'',
|
|
67
|
-
'```bash',
|
|
68
|
-
'export AIXX_API_KEY="sk-你的key"',
|
|
69
|
-
'export AIXX_BASE_URL="
|
|
70
|
-
'```',
|
|
71
|
-
'',
|
|
72
|
-
'## 验证',
|
|
73
|
-
'',
|
|
74
|
-
'让agent说:"帮我用deepseek-chat翻译:Hello World"',
|
|
75
|
-
'',
|
|
76
|
-
'完整文档:https://gitee.com/kk0803/token-hub',
|
|
77
|
-
''
|
|
78
|
-
].join('\n'),
|
|
79
|
-
|
|
80
|
-
'INSTALLED.md': [
|
|
81
|
-
'# AIXX Skill 已安装',
|
|
82
|
-
'',
|
|
83
|
-
'这个skill由 `npx aixxai install` 安装。',
|
|
84
|
-
'',
|
|
85
|
-
'## 配置',
|
|
86
|
-
'',
|
|
87
|
-
'确保设置了环境变量:',
|
|
88
|
-
'- AIXX_API_KEY (你的AIXX key)',
|
|
89
|
-
'- AIXX_BASE_URL (默认
|
|
90
|
-
'',
|
|
91
|
-
'## 验证',
|
|
92
|
-
'',
|
|
93
|
-
'运行: npx aixxai test',
|
|
94
|
-
''
|
|
95
|
-
].join('\n')
|
|
96
|
-
};
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
export async function downloadSkill(targetDir) {
|
|
100
|
-
if (!existsSync(targetDir)) {
|
|
101
|
-
mkdirSync(targetDir, { recursive: true });
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// 优先从templates目录复制(完整版skill,支持子目录递归,如 references/)
|
|
105
|
-
const templatesDir = join(__dirname, '..', '..', 'templates');
|
|
106
|
-
let usedTemplates = false;
|
|
107
|
-
|
|
108
|
-
if (existsSync(templatesDir)) {
|
|
109
|
-
// 递归复制:templates/ 下的文件按相对路径镜像到 targetDir/
|
|
110
|
-
// 这样 references/chat.md 等子目录文件能正确落到 targetDir/references/chat.md
|
|
111
|
-
const stack = [templatesDir];
|
|
112
|
-
while (stack.length > 0) {
|
|
113
|
-
const current = stack.pop();
|
|
114
|
-
for (const entry of readdirSync(current)) {
|
|
115
|
-
const src = join(current, entry);
|
|
116
|
-
const rel = relative(templatesDir, src);
|
|
117
|
-
const dest = join(targetDir, rel);
|
|
118
|
-
try {
|
|
119
|
-
if (statSync(src).isDirectory()) {
|
|
120
|
-
if (!existsSync(dest)) mkdirSync(dest, { recursive: true });
|
|
121
|
-
stack.push(src);
|
|
122
|
-
} else {
|
|
123
|
-
mkdirSync(dirname(dest), { recursive: true });
|
|
124
|
-
copyFileSync(src, dest);
|
|
125
|
-
usedTemplates = true;
|
|
126
|
-
}
|
|
127
|
-
} catch (e) {
|
|
128
|
-
// 单个文件复制失败,继续尝试其他文件;最终若整体失败再回退到最小版
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// 如果templates目录不存在或复制失败,用内嵌的最小版
|
|
135
|
-
if (!usedTemplates) {
|
|
136
|
-
const minimal = getMinimalSkill();
|
|
137
|
-
for (const [filename, content] of Object.entries(minimal)) {
|
|
138
|
-
writeFileSync(join(targetDir, filename), content);
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
// 确保INSTALLED.md存在(安装标记)
|
|
143
|
-
const installedMarker = join(targetDir, 'INSTALLED.md');
|
|
144
|
-
if (!existsSync(installedMarker)) {
|
|
145
|
-
writeFileSync(installedMarker, getMinimalSkill()['INSTALLED.md']);
|
|
146
|
-
}
|
|
147
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* skill文件下载工具
|
|
3
|
+
*
|
|
4
|
+
* 策略:skill文件内嵌在CLI包里(templates/目录),
|
|
5
|
+
* install时从包内复制到目标目录。
|
|
6
|
+
* 这样即使网络挂了也能完成安装。
|
|
7
|
+
*
|
|
8
|
+
* 注:templates/目录在发布时一起打包进npm包
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { copyFileSync, existsSync, mkdirSync, readdirSync, statSync, writeFileSync } from 'fs';
|
|
12
|
+
import { join, dirname, relative } from 'path';
|
|
13
|
+
import { fileURLToPath } from 'url';
|
|
14
|
+
|
|
15
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
16
|
+
|
|
17
|
+
// 最小化的skill内容(确保即使templates目录缺失也能安装)
|
|
18
|
+
function getMinimalSkill() {
|
|
19
|
+
return {
|
|
20
|
+
'SKILL.md': [
|
|
21
|
+
'---',
|
|
22
|
+
'name: aixx',
|
|
23
|
+
'version: 0.1.0',
|
|
24
|
+
'description: "AIXX - 让你的AI调用万物。一个skill,agent就能调用全世界的AI能力。"',
|
|
25
|
+
'metadata:',
|
|
26
|
+
' category: "productivity"',
|
|
27
|
+
' requires:',
|
|
28
|
+
' env: ["AIXX_API_KEY", "AIXX_BASE_URL"]',
|
|
29
|
+
'---',
|
|
30
|
+
'',
|
|
31
|
+
'# AIXX Skill',
|
|
32
|
+
'',
|
|
33
|
+
'你是 AI Agent。通过 AIXX 中转站调用各种 AI 能力。',
|
|
34
|
+
'用户永远不用碰 key、参数、文档——一切通过你(agent)和 AIXX 对接。',
|
|
35
|
+
'',
|
|
36
|
+
'## 触发规则',
|
|
37
|
+
'',
|
|
38
|
+
'当用户提出需要LLM能力的请求时(翻译/写作/分析/问答/代码等),',
|
|
39
|
+
'先调AIXX(通常更便宜或更强)。',
|
|
40
|
+
'',
|
|
41
|
+
'## 调用方式(兼容OpenAI接口)',
|
|
42
|
+
'',
|
|
43
|
+
'- POST {AIXX_BASE_URL}/chat/completions',
|
|
44
|
+
'- Header: Authorization: Bearer {AIXX_API_KEY}',
|
|
45
|
+
'- Body: {"model":"deepseek-chat","messages":[...]}',
|
|
46
|
+
'',
|
|
47
|
+
'## 默认模型选择',
|
|
48
|
+
'',
|
|
49
|
+
'- 简短任务/翻译/问答 -> deepseek-chat(最便宜)',
|
|
50
|
+
'- 长文档 -> moonshot-v1-128k(Kimi长文本)',
|
|
51
|
+
'- 推理 -> deepseek-reasoner',
|
|
52
|
+
'- 中文写作 -> deepseek-chat(glm渠道维护中)',
|
|
53
|
+
'',
|
|
54
|
+
'## 查余额',
|
|
55
|
+
'',
|
|
56
|
+
'调 {AIXX_BASE_URL}/dashboard/billing/subscription',
|
|
57
|
+
'',
|
|
58
|
+
'完整文档:https://gitee.com/kk0803/token-hub',
|
|
59
|
+
''
|
|
60
|
+
].join('\n'),
|
|
61
|
+
|
|
62
|
+
'QUICKSTART.md': [
|
|
63
|
+
'# AIXX 快速开始',
|
|
64
|
+
'',
|
|
65
|
+
'## 配置环境变量',
|
|
66
|
+
'',
|
|
67
|
+
'```bash',
|
|
68
|
+
'export AIXX_API_KEY="sk-你的key"',
|
|
69
|
+
'export AIXX_BASE_URL="https://baodan.run/v1"',
|
|
70
|
+
'```',
|
|
71
|
+
'',
|
|
72
|
+
'## 验证',
|
|
73
|
+
'',
|
|
74
|
+
'让agent说:"帮我用deepseek-chat翻译:Hello World"',
|
|
75
|
+
'',
|
|
76
|
+
'完整文档:https://gitee.com/kk0803/token-hub',
|
|
77
|
+
''
|
|
78
|
+
].join('\n'),
|
|
79
|
+
|
|
80
|
+
'INSTALLED.md': [
|
|
81
|
+
'# AIXX Skill 已安装',
|
|
82
|
+
'',
|
|
83
|
+
'这个skill由 `npx aixxai install` 安装。',
|
|
84
|
+
'',
|
|
85
|
+
'## 配置',
|
|
86
|
+
'',
|
|
87
|
+
'确保设置了环境变量:',
|
|
88
|
+
'- AIXX_API_KEY (你的AIXX key)',
|
|
89
|
+
'- AIXX_BASE_URL (默认 https://baodan.run/v1)',
|
|
90
|
+
'',
|
|
91
|
+
'## 验证',
|
|
92
|
+
'',
|
|
93
|
+
'运行: npx aixxai test',
|
|
94
|
+
''
|
|
95
|
+
].join('\n')
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export async function downloadSkill(targetDir) {
|
|
100
|
+
if (!existsSync(targetDir)) {
|
|
101
|
+
mkdirSync(targetDir, { recursive: true });
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// 优先从templates目录复制(完整版skill,支持子目录递归,如 references/)
|
|
105
|
+
const templatesDir = join(__dirname, '..', '..', 'templates');
|
|
106
|
+
let usedTemplates = false;
|
|
107
|
+
|
|
108
|
+
if (existsSync(templatesDir)) {
|
|
109
|
+
// 递归复制:templates/ 下的文件按相对路径镜像到 targetDir/
|
|
110
|
+
// 这样 references/chat.md 等子目录文件能正确落到 targetDir/references/chat.md
|
|
111
|
+
const stack = [templatesDir];
|
|
112
|
+
while (stack.length > 0) {
|
|
113
|
+
const current = stack.pop();
|
|
114
|
+
for (const entry of readdirSync(current)) {
|
|
115
|
+
const src = join(current, entry);
|
|
116
|
+
const rel = relative(templatesDir, src);
|
|
117
|
+
const dest = join(targetDir, rel);
|
|
118
|
+
try {
|
|
119
|
+
if (statSync(src).isDirectory()) {
|
|
120
|
+
if (!existsSync(dest)) mkdirSync(dest, { recursive: true });
|
|
121
|
+
stack.push(src);
|
|
122
|
+
} else {
|
|
123
|
+
mkdirSync(dirname(dest), { recursive: true });
|
|
124
|
+
copyFileSync(src, dest);
|
|
125
|
+
usedTemplates = true;
|
|
126
|
+
}
|
|
127
|
+
} catch (e) {
|
|
128
|
+
// 单个文件复制失败,继续尝试其他文件;最终若整体失败再回退到最小版
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// 如果templates目录不存在或复制失败,用内嵌的最小版
|
|
135
|
+
if (!usedTemplates) {
|
|
136
|
+
const minimal = getMinimalSkill();
|
|
137
|
+
for (const [filename, content] of Object.entries(minimal)) {
|
|
138
|
+
writeFileSync(join(targetDir, filename), content);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// 确保INSTALLED.md存在(安装标记)
|
|
143
|
+
const installedMarker = join(targetDir, 'INSTALLED.md');
|
|
144
|
+
if (!existsSync(installedMarker)) {
|
|
145
|
+
writeFileSync(installedMarker, getMinimalSkill()['INSTALLED.md']);
|
|
146
|
+
}
|
|
147
|
+
}
|