@zhin.js/scaffold-wizard 0.1.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/LICENSE +21 -0
- package/README.md +67 -0
- package/lib/adapter-configurers.d.ts +19 -0
- package/lib/adapter-configurers.d.ts.map +1 -0
- package/lib/adapter-configurers.js +502 -0
- package/lib/adapter-configurers.js.map +1 -0
- package/lib/adapter.d.ts +37 -0
- package/lib/adapter.d.ts.map +1 -0
- package/lib/adapter.js +458 -0
- package/lib/adapter.js.map +1 -0
- package/lib/ai.d.ts +88 -0
- package/lib/ai.d.ts.map +1 -0
- package/lib/ai.js +440 -0
- package/lib/ai.js.map +1 -0
- package/lib/apply.d.ts +14 -0
- package/lib/apply.d.ts.map +1 -0
- package/lib/apply.js +86 -0
- package/lib/apply.js.map +1 -0
- package/lib/database.d.ts +3 -0
- package/lib/database.d.ts.map +1 -0
- package/lib/database.js +260 -0
- package/lib/database.js.map +1 -0
- package/lib/index.d.ts +11 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +8 -0
- package/lib/index.js.map +1 -0
- package/lib/project-deps.d.ts +18 -0
- package/lib/project-deps.d.ts.map +1 -0
- package/lib/project-deps.js +49 -0
- package/lib/project-deps.js.map +1 -0
- package/lib/types.d.ts +22 -0
- package/lib/types.d.ts.map +1 -0
- package/lib/types.js +9 -0
- package/lib/types.js.map +1 -0
- package/package.json +48 -0
- package/src/adapter-configurers.ts +550 -0
- package/src/adapter.ts +524 -0
- package/src/ai.ts +497 -0
- package/src/apply.ts +99 -0
- package/src/database.ts +267 -0
- package/src/index.ts +51 -0
- package/src/project-deps.ts +49 -0
- package/src/types.ts +32 -0
package/src/ai.ts
ADDED
|
@@ -0,0 +1,497 @@
|
|
|
1
|
+
import inquirer from 'inquirer';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
|
|
4
|
+
export interface AISetupConfig {
|
|
5
|
+
enabled: boolean;
|
|
6
|
+
defaultProvider?: string;
|
|
7
|
+
providers?: Record<string, { apiKey?: string; host?: string; models?: string[]; baseUrl?: string }>;
|
|
8
|
+
sessions?: { useDatabase: boolean; maxHistory: number; expireMs: number };
|
|
9
|
+
context?: { enabled: boolean; maxRecentMessages: number; summaryThreshold: number; keepAfterSummary: number };
|
|
10
|
+
agent?: {
|
|
11
|
+
execSecurity: 'deny' | 'allowlist' | 'full';
|
|
12
|
+
execPreset: 'readonly' | 'network' | 'development' | 'custom';
|
|
13
|
+
execAllowlist: string[];
|
|
14
|
+
phaseTrace: boolean;
|
|
15
|
+
toolSearch: boolean;
|
|
16
|
+
};
|
|
17
|
+
trigger?: {
|
|
18
|
+
respondToAt: boolean;
|
|
19
|
+
respondToPrivate: boolean;
|
|
20
|
+
prefixes: string[];
|
|
21
|
+
ignorePrefixes: string[];
|
|
22
|
+
timeout: number;
|
|
23
|
+
};
|
|
24
|
+
memoryMcp?: boolean;
|
|
25
|
+
mcpServers?: Array<{
|
|
26
|
+
name: string;
|
|
27
|
+
transport: 'stdio' | 'streamable-http' | 'sse';
|
|
28
|
+
command?: string;
|
|
29
|
+
args?: string[];
|
|
30
|
+
url?: string;
|
|
31
|
+
}>;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// 提供商信息
|
|
35
|
+
const PROVIDERS = [
|
|
36
|
+
{
|
|
37
|
+
name: 'OpenAI (GPT-4o, 推荐)',
|
|
38
|
+
value: 'openai',
|
|
39
|
+
defaultModel: 'gpt-4o',
|
|
40
|
+
hint: '需 OpenAI API Key;国内可用代理 baseUrl',
|
|
41
|
+
keyPlaceholder: 'sk-...',
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
name: 'Anthropic (Claude)',
|
|
45
|
+
value: 'anthropic',
|
|
46
|
+
defaultModel: 'claude-sonnet-4-20250514',
|
|
47
|
+
hint: '需 Anthropic API Key',
|
|
48
|
+
keyPlaceholder: 'sk-ant-...',
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
name: 'DeepSeek',
|
|
52
|
+
value: 'deepseek',
|
|
53
|
+
defaultModel: 'deepseek-chat',
|
|
54
|
+
hint: '性价比高,国内直连',
|
|
55
|
+
keyPlaceholder: 'sk-...',
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
name: 'Moonshot (月之暗面)',
|
|
59
|
+
value: 'moonshot',
|
|
60
|
+
defaultModel: 'moonshot-v1-8k',
|
|
61
|
+
hint: 'Kimi 同款 API',
|
|
62
|
+
keyPlaceholder: 'sk-...',
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
name: '智谱 AI (GLM)',
|
|
66
|
+
value: 'zhipu',
|
|
67
|
+
defaultModel: 'glm-4-flash',
|
|
68
|
+
hint: '国内直连,open.bigmodel.cn',
|
|
69
|
+
keyPlaceholder: '...',
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
name: 'Ollama (本地部署)',
|
|
73
|
+
value: 'ollama',
|
|
74
|
+
defaultModel: 'qwen3:8b',
|
|
75
|
+
hint: '本地运行,无需 API Key;先 ollama pull 模型',
|
|
76
|
+
keyPlaceholder: '',
|
|
77
|
+
},
|
|
78
|
+
] as const;
|
|
79
|
+
|
|
80
|
+
export const RECOMMENDED_AI_DEFAULTS = {
|
|
81
|
+
sessions: {
|
|
82
|
+
useDatabase: true,
|
|
83
|
+
maxHistory: 100,
|
|
84
|
+
expireMs: 7 * 24 * 60 * 60 * 1000,
|
|
85
|
+
},
|
|
86
|
+
context: {
|
|
87
|
+
enabled: true,
|
|
88
|
+
maxRecentMessages: 100,
|
|
89
|
+
summaryThreshold: 50,
|
|
90
|
+
keepAfterSummary: 10,
|
|
91
|
+
},
|
|
92
|
+
agent: {
|
|
93
|
+
execSecurity: 'deny' as const,
|
|
94
|
+
execPreset: 'custom' as const,
|
|
95
|
+
execAllowlist: [] as string[],
|
|
96
|
+
phaseTrace: false,
|
|
97
|
+
toolSearch: false,
|
|
98
|
+
},
|
|
99
|
+
trigger: {
|
|
100
|
+
ignorePrefixes: ['/', '!', '!'],
|
|
101
|
+
timeout: 60_000,
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* AI 配置引导向导
|
|
107
|
+
*/
|
|
108
|
+
export async function configureAI(): Promise<AISetupConfig> {
|
|
109
|
+
console.log('');
|
|
110
|
+
console.log(chalk.blue('🤖 配置 AI 智能体'));
|
|
111
|
+
console.log(chalk.gray(' 启用后将预装 @modelcontextprotocol/sdk,支持 MCP 与 memory 扩展。'));
|
|
112
|
+
console.log(chalk.gray(' 会话持久化默认开启,将自动配置 SQLite 数据库(若尚未选择)。'));
|
|
113
|
+
|
|
114
|
+
const { enableAI } = await inquirer.prompt([
|
|
115
|
+
{
|
|
116
|
+
type: 'confirm',
|
|
117
|
+
name: 'enableAI',
|
|
118
|
+
message: '是否启用 AI 智能体?(大模型对话 + 工具调用)',
|
|
119
|
+
default: true,
|
|
120
|
+
}
|
|
121
|
+
]);
|
|
122
|
+
|
|
123
|
+
if (!enableAI) {
|
|
124
|
+
return { enabled: false };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const { provider } = await inquirer.prompt([
|
|
128
|
+
{
|
|
129
|
+
type: 'select',
|
|
130
|
+
name: 'provider',
|
|
131
|
+
message: '选择 AI 提供商:',
|
|
132
|
+
choices: PROVIDERS.map(p => ({
|
|
133
|
+
name: `${p.name} — ${p.hint}`,
|
|
134
|
+
value: p.value,
|
|
135
|
+
})),
|
|
136
|
+
pageSize: 10,
|
|
137
|
+
}
|
|
138
|
+
]);
|
|
139
|
+
|
|
140
|
+
const providerInfo = PROVIDERS.find(p => p.value === provider)!;
|
|
141
|
+
console.log(chalk.gray(` ${providerInfo.hint}`));
|
|
142
|
+
|
|
143
|
+
let providerConfig: Record<string, any> = {};
|
|
144
|
+
const envVarName = `AI_API_KEY`;
|
|
145
|
+
|
|
146
|
+
if (provider === 'ollama') {
|
|
147
|
+
const ollamaConfig = await inquirer.prompt([
|
|
148
|
+
{
|
|
149
|
+
type: 'input',
|
|
150
|
+
name: 'host',
|
|
151
|
+
message: 'Ollama 地址:',
|
|
152
|
+
default: 'http://127.0.0.1:11434',
|
|
153
|
+
validate: (input: string) => {
|
|
154
|
+
if (!input.trim()) return '地址不能为空';
|
|
155
|
+
if (!/^https?:\/\//.test(input.trim())) return '请输入 http:// 或 https:// 开头的地址';
|
|
156
|
+
return true;
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
type: 'input',
|
|
161
|
+
name: 'model',
|
|
162
|
+
message: '模型名称(需已 ollama pull):',
|
|
163
|
+
default: providerInfo.defaultModel,
|
|
164
|
+
validate: (input: string) => (input.trim() ? true : '模型名不能为空'),
|
|
165
|
+
}
|
|
166
|
+
]);
|
|
167
|
+
providerConfig = {
|
|
168
|
+
host: ollamaConfig.host.trim(),
|
|
169
|
+
models: [ollamaConfig.model.trim()],
|
|
170
|
+
};
|
|
171
|
+
} else {
|
|
172
|
+
const cloudConfig = await inquirer.prompt([
|
|
173
|
+
{
|
|
174
|
+
type: 'input',
|
|
175
|
+
name: 'apiKey',
|
|
176
|
+
message: `${providerInfo.name.split(' (')[0]} API Key:`,
|
|
177
|
+
validate: (input: string) => {
|
|
178
|
+
if (!input.trim()) return 'API Key 不能为空(可稍后在 .env 中填写 AI_API_KEY)';
|
|
179
|
+
return true;
|
|
180
|
+
}
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
type: 'input',
|
|
184
|
+
name: 'model',
|
|
185
|
+
message: '模型名称:',
|
|
186
|
+
default: providerInfo.defaultModel,
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
type: 'input',
|
|
190
|
+
name: 'baseUrl',
|
|
191
|
+
message: '自定义 API 地址(留空使用官方默认):',
|
|
192
|
+
default: '',
|
|
193
|
+
}
|
|
194
|
+
]);
|
|
195
|
+
|
|
196
|
+
providerConfig = {
|
|
197
|
+
apiKey: `\${${envVarName}}`,
|
|
198
|
+
models: [cloudConfig.model?.trim() || providerInfo.defaultModel],
|
|
199
|
+
...(cloudConfig.baseUrl?.trim() ? { baseUrl: cloudConfig.baseUrl.trim() } : {}),
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
(providerConfig as any).__envApiKey = cloudConfig.apiKey.trim();
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
console.log('');
|
|
206
|
+
console.log(chalk.blue('⚡ 配置 AI 触发方式'));
|
|
207
|
+
console.log(chalk.gray(' 决定 Bot 何时自动调用大模型;命令(/ 开头)不会被 AI 拦截。'));
|
|
208
|
+
|
|
209
|
+
const triggerConfig = await inquirer.prompt([
|
|
210
|
+
{
|
|
211
|
+
type: 'confirm',
|
|
212
|
+
name: 'respondToAt',
|
|
213
|
+
message: '响应 @机器人 的消息?',
|
|
214
|
+
default: true,
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
type: 'confirm',
|
|
218
|
+
name: 'respondToPrivate',
|
|
219
|
+
message: '响应私聊消息?',
|
|
220
|
+
default: true,
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
type: 'input',
|
|
224
|
+
name: 'prefixes',
|
|
225
|
+
message: '触发前缀(逗号分隔,如 #,ai:):',
|
|
226
|
+
default: '#',
|
|
227
|
+
}
|
|
228
|
+
]);
|
|
229
|
+
|
|
230
|
+
const prefixes = triggerConfig.prefixes
|
|
231
|
+
.split(',')
|
|
232
|
+
.map((p: string) => p.trim())
|
|
233
|
+
.filter(Boolean);
|
|
234
|
+
|
|
235
|
+
console.log('');
|
|
236
|
+
console.log(chalk.blue('🛡️ 配置 Agent 安全与能力'));
|
|
237
|
+
console.log(chalk.gray(' 生产环境建议 execSecurity: deny;开发调试可选 allowlist。'));
|
|
238
|
+
|
|
239
|
+
const agentConfig = await inquirer.prompt([
|
|
240
|
+
{
|
|
241
|
+
type: 'select',
|
|
242
|
+
name: 'execSecurity',
|
|
243
|
+
message: '命令执行安全策略:',
|
|
244
|
+
choices: [
|
|
245
|
+
{ name: 'deny — 禁止执行 shell(推荐)', value: 'deny' },
|
|
246
|
+
{ name: 'allowlist — 仅允许白名单命令', value: 'allowlist' },
|
|
247
|
+
{ name: 'full — 允许任意命令(仅本地调试)', value: 'full' },
|
|
248
|
+
],
|
|
249
|
+
default: 'deny',
|
|
250
|
+
},
|
|
251
|
+
{
|
|
252
|
+
type: 'confirm',
|
|
253
|
+
name: 'memoryMcp',
|
|
254
|
+
message: '启用本地知识图谱 memory MCP?(data/knowledge-graph.jsonl)',
|
|
255
|
+
default: false,
|
|
256
|
+
},
|
|
257
|
+
{
|
|
258
|
+
type: 'confirm',
|
|
259
|
+
name: 'toolSearch',
|
|
260
|
+
message: '启用 deferred + Worker 工具编排(toolSearch,Advanced)?',
|
|
261
|
+
default: false,
|
|
262
|
+
},
|
|
263
|
+
{
|
|
264
|
+
type: 'confirm',
|
|
265
|
+
name: 'phaseTrace',
|
|
266
|
+
message: '输出 Agent 阶段日志(便于排障)?',
|
|
267
|
+
default: false,
|
|
268
|
+
},
|
|
269
|
+
]);
|
|
270
|
+
|
|
271
|
+
const execPreset = agentConfig.execSecurity === 'deny'
|
|
272
|
+
? 'readonly'
|
|
273
|
+
: agentConfig.execSecurity === 'allowlist'
|
|
274
|
+
? 'development'
|
|
275
|
+
: 'custom';
|
|
276
|
+
|
|
277
|
+
return {
|
|
278
|
+
enabled: true,
|
|
279
|
+
defaultProvider: provider,
|
|
280
|
+
providers: {
|
|
281
|
+
[provider]: providerConfig,
|
|
282
|
+
},
|
|
283
|
+
sessions: RECOMMENDED_AI_DEFAULTS.sessions,
|
|
284
|
+
context: RECOMMENDED_AI_DEFAULTS.context,
|
|
285
|
+
agent: {
|
|
286
|
+
execSecurity: agentConfig.execSecurity,
|
|
287
|
+
execPreset,
|
|
288
|
+
execAllowlist: [],
|
|
289
|
+
phaseTrace: agentConfig.phaseTrace,
|
|
290
|
+
toolSearch: agentConfig.toolSearch,
|
|
291
|
+
},
|
|
292
|
+
trigger: {
|
|
293
|
+
respondToAt: triggerConfig.respondToAt,
|
|
294
|
+
respondToPrivate: triggerConfig.respondToPrivate,
|
|
295
|
+
prefixes: prefixes.length > 0 ? prefixes : ['#'],
|
|
296
|
+
ignorePrefixes: RECOMMENDED_AI_DEFAULTS.trigger.ignorePrefixes,
|
|
297
|
+
timeout: RECOMMENDED_AI_DEFAULTS.trigger.timeout,
|
|
298
|
+
},
|
|
299
|
+
memoryMcp: agentConfig.memoryMcp,
|
|
300
|
+
mcpServers: [],
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* 获取 AI 配置需要写入 .env 的环境变量
|
|
306
|
+
*/
|
|
307
|
+
export function generateAIEnvVars(config: AISetupConfig): string {
|
|
308
|
+
if (!config.enabled || !config.providers) return '';
|
|
309
|
+
|
|
310
|
+
const envVars: string[] = ['', '# AI 配置'];
|
|
311
|
+
|
|
312
|
+
for (const [, providerConfig] of Object.entries(config.providers)) {
|
|
313
|
+
const realKey = (providerConfig as any).__envApiKey;
|
|
314
|
+
if (realKey) {
|
|
315
|
+
envVars.push(`AI_API_KEY=${realKey}`);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
return envVars.length > 1 ? envVars.join('\n') : '';
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* 生成 YAML 格式的 AI 配置段
|
|
324
|
+
*/
|
|
325
|
+
export function generateAIConfigYaml(config: AISetupConfig): string {
|
|
326
|
+
if (!config.enabled) return '';
|
|
327
|
+
|
|
328
|
+
const lines: string[] = [
|
|
329
|
+
'',
|
|
330
|
+
'ai:',
|
|
331
|
+
` defaultProvider: ${config.defaultProvider}`,
|
|
332
|
+
' providers:',
|
|
333
|
+
];
|
|
334
|
+
|
|
335
|
+
if (config.providers) {
|
|
336
|
+
for (const [name, providerConfig] of Object.entries(config.providers)) {
|
|
337
|
+
lines.push(` ${name}:`);
|
|
338
|
+
if (providerConfig.apiKey) {
|
|
339
|
+
lines.push(` apiKey: ${providerConfig.apiKey}`);
|
|
340
|
+
}
|
|
341
|
+
if (providerConfig.host) {
|
|
342
|
+
lines.push(` host: ${providerConfig.host}`);
|
|
343
|
+
}
|
|
344
|
+
if (providerConfig.models) {
|
|
345
|
+
lines.push(' models:');
|
|
346
|
+
for (const model of providerConfig.models) {
|
|
347
|
+
lines.push(` - ${model}`);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
if (providerConfig.baseUrl) {
|
|
351
|
+
lines.push(` baseUrl: ${providerConfig.baseUrl}`);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
if (config.trigger) {
|
|
357
|
+
lines.push(' trigger:');
|
|
358
|
+
lines.push(` respondToAt: ${config.trigger.respondToAt}`);
|
|
359
|
+
lines.push(` respondToPrivate: ${config.trigger.respondToPrivate}`);
|
|
360
|
+
lines.push(' prefixes:');
|
|
361
|
+
for (const prefix of config.trigger.prefixes) {
|
|
362
|
+
lines.push(` - "${prefix}"`);
|
|
363
|
+
}
|
|
364
|
+
if (config.trigger.ignorePrefixes.length > 0) {
|
|
365
|
+
lines.push(' ignorePrefixes:');
|
|
366
|
+
for (const prefix of config.trigger.ignorePrefixes) {
|
|
367
|
+
lines.push(` - "${prefix}"`);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
lines.push(` timeout: ${config.trigger.timeout}`);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
if (config.sessions) {
|
|
374
|
+
lines.push(' sessions:');
|
|
375
|
+
lines.push(` useDatabase: ${config.sessions.useDatabase}`);
|
|
376
|
+
lines.push(` maxHistory: ${config.sessions.maxHistory}`);
|
|
377
|
+
lines.push(` expireMs: ${config.sessions.expireMs}`);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
if (config.context) {
|
|
381
|
+
lines.push(' context:');
|
|
382
|
+
lines.push(` enabled: ${config.context.enabled}`);
|
|
383
|
+
lines.push(` maxRecentMessages: ${config.context.maxRecentMessages}`);
|
|
384
|
+
lines.push(` summaryThreshold: ${config.context.summaryThreshold}`);
|
|
385
|
+
lines.push(` keepAfterSummary: ${config.context.keepAfterSummary}`);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
if (config.agent) {
|
|
389
|
+
lines.push(' agent:');
|
|
390
|
+
lines.push(` execSecurity: ${config.agent.execSecurity}`);
|
|
391
|
+
lines.push(` execPreset: ${config.agent.execPreset}`);
|
|
392
|
+
lines.push(` phaseTrace: ${config.agent.phaseTrace}`);
|
|
393
|
+
lines.push(` toolSearch: ${config.agent.toolSearch}`);
|
|
394
|
+
lines.push(' execAllowlist:');
|
|
395
|
+
for (const command of config.agent.execAllowlist) {
|
|
396
|
+
lines.push(` - "${command}"`);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
lines.push(` memoryMcp: ${config.memoryMcp ?? false}`);
|
|
401
|
+
|
|
402
|
+
return lines.join('\n');
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* 生成 TOML 格式的 AI 配置段
|
|
407
|
+
*/
|
|
408
|
+
export function generateAIConfigToml(config: AISetupConfig): string {
|
|
409
|
+
if (!config.enabled) return '';
|
|
410
|
+
|
|
411
|
+
const lines: string[] = [
|
|
412
|
+
'',
|
|
413
|
+
'[ai]',
|
|
414
|
+
`defaultProvider = "${config.defaultProvider}"`,
|
|
415
|
+
`memoryMcp = ${config.memoryMcp ?? false}`,
|
|
416
|
+
];
|
|
417
|
+
|
|
418
|
+
if (config.providers) {
|
|
419
|
+
for (const [name, providerConfig] of Object.entries(config.providers)) {
|
|
420
|
+
lines.push('', `[ai.providers.${name}]`);
|
|
421
|
+
if (providerConfig.apiKey) lines.push(`apiKey = "${providerConfig.apiKey}"`);
|
|
422
|
+
if (providerConfig.host) lines.push(`host = "${providerConfig.host}"`);
|
|
423
|
+
if (providerConfig.models?.length) {
|
|
424
|
+
lines.push(`models = ${JSON.stringify(providerConfig.models)}`);
|
|
425
|
+
}
|
|
426
|
+
if (providerConfig.baseUrl) lines.push(`baseUrl = "${providerConfig.baseUrl}"`);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
if (config.trigger) {
|
|
431
|
+
lines.push('', '[ai.trigger]');
|
|
432
|
+
lines.push(`respondToAt = ${config.trigger.respondToAt}`);
|
|
433
|
+
lines.push(`respondToPrivate = ${config.trigger.respondToPrivate}`);
|
|
434
|
+
lines.push(`prefixes = ${JSON.stringify(config.trigger.prefixes)}`);
|
|
435
|
+
lines.push(`ignorePrefixes = ${JSON.stringify(config.trigger.ignorePrefixes)}`);
|
|
436
|
+
lines.push(`timeout = ${config.trigger.timeout}`);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
if (config.sessions) {
|
|
440
|
+
lines.push('', '[ai.sessions]');
|
|
441
|
+
lines.push(`useDatabase = ${config.sessions.useDatabase}`);
|
|
442
|
+
lines.push(`maxHistory = ${config.sessions.maxHistory}`);
|
|
443
|
+
lines.push(`expireMs = ${config.sessions.expireMs}`);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
if (config.context) {
|
|
447
|
+
lines.push('', '[ai.context]');
|
|
448
|
+
lines.push(`enabled = ${config.context.enabled}`);
|
|
449
|
+
lines.push(`maxRecentMessages = ${config.context.maxRecentMessages}`);
|
|
450
|
+
lines.push(`summaryThreshold = ${config.context.summaryThreshold}`);
|
|
451
|
+
lines.push(`keepAfterSummary = ${config.context.keepAfterSummary}`);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
if (config.agent) {
|
|
455
|
+
lines.push('', '[ai.agent]');
|
|
456
|
+
lines.push(`execSecurity = "${config.agent.execSecurity}"`);
|
|
457
|
+
lines.push(`execPreset = "${config.agent.execPreset}"`);
|
|
458
|
+
lines.push(`execAllowlist = ${JSON.stringify(config.agent.execAllowlist)}`);
|
|
459
|
+
lines.push(`phaseTrace = ${config.agent.phaseTrace}`);
|
|
460
|
+
lines.push(`toolSearch = ${config.agent.toolSearch}`);
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
return lines.join('\n');
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* 生成 JSON 格式的 AI 配置段
|
|
468
|
+
*/
|
|
469
|
+
export function generateAIConfigJSON(config: AISetupConfig): string {
|
|
470
|
+
if (!config.enabled) return '';
|
|
471
|
+
|
|
472
|
+
const obj: any = {
|
|
473
|
+
defaultProvider: config.defaultProvider,
|
|
474
|
+
providers: {},
|
|
475
|
+
sessions: config.sessions,
|
|
476
|
+
context: config.context,
|
|
477
|
+
agent: config.agent,
|
|
478
|
+
trigger: config.trigger ? {
|
|
479
|
+
...config.trigger,
|
|
480
|
+
} : undefined,
|
|
481
|
+
memoryMcp: config.memoryMcp ?? false,
|
|
482
|
+
mcpServers: config.mcpServers ?? [],
|
|
483
|
+
};
|
|
484
|
+
|
|
485
|
+
if (config.providers) {
|
|
486
|
+
for (const [name, providerConfig] of Object.entries(config.providers)) {
|
|
487
|
+
const clean: any = {};
|
|
488
|
+
if (providerConfig.apiKey) clean.apiKey = providerConfig.apiKey;
|
|
489
|
+
if (providerConfig.host) clean.host = providerConfig.host;
|
|
490
|
+
if (providerConfig.models) clean.models = providerConfig.models;
|
|
491
|
+
if (providerConfig.baseUrl) clean.baseUrl = providerConfig.baseUrl;
|
|
492
|
+
obj.providers[name] = clean;
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
return `"ai": ${JSON.stringify(obj, null, 4).replace(/^/gm, ' ').trimStart()}`;
|
|
497
|
+
}
|
package/src/apply.ts
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import fs from 'fs-extra';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import type { AdapterSetupResult, AISetupConfig, DatabaseConfig, InitOptions } from './types.js';
|
|
4
|
+
import { generateAdapterEnvVars, getAdapterDependencies } from './adapter.js';
|
|
5
|
+
import { generateAIEnvVars } from './ai.js';
|
|
6
|
+
import { ensureDatabaseForAdapters, ensureDatabaseForAI, getAIDependencies } from './project-deps.js';
|
|
7
|
+
|
|
8
|
+
/** Host 插件(Webhook / Console 必需) */
|
|
9
|
+
export const CONSOLE_HOST_PLUGINS = [
|
|
10
|
+
'@zhin.js/host-router',
|
|
11
|
+
'@zhin.js/host-api',
|
|
12
|
+
] as const;
|
|
13
|
+
|
|
14
|
+
export function applyDatabaseToConfig(config: Record<string, unknown>, database: DatabaseConfig): void {
|
|
15
|
+
config.database = database;
|
|
16
|
+
if (database.dialect === 'sqlite') {
|
|
17
|
+
config.inbox = { enabled: true };
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function applyAdaptersToConfig(config: Record<string, unknown>, result: AdapterSetupResult): void {
|
|
22
|
+
const plugins = Array.isArray(config.plugins) ? [...config.plugins as string[]] : [];
|
|
23
|
+
for (const plugin of result.plugins) {
|
|
24
|
+
if (!plugins.includes(plugin)) plugins.push(plugin);
|
|
25
|
+
}
|
|
26
|
+
if (result.plugins.some((p: string) => p !== '@zhin.js/adapter-sandbox')) {
|
|
27
|
+
for (const hostPlugin of CONSOLE_HOST_PLUGINS) {
|
|
28
|
+
if (!plugins.includes(hostPlugin)) plugins.push(hostPlugin);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
config.plugins = plugins;
|
|
32
|
+
|
|
33
|
+
const bots = Array.isArray(config.bots) ? [...config.bots as Record<string, unknown>[]] : [];
|
|
34
|
+
bots.push(...result.bots);
|
|
35
|
+
config.bots = bots;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function applyAIToConfig(config: Record<string, unknown>, ai: AISetupConfig): void {
|
|
39
|
+
config.ai = ai;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export async function appendWizardEnvVars(
|
|
43
|
+
projectDir: string,
|
|
44
|
+
adapters?: AdapterSetupResult,
|
|
45
|
+
ai?: AISetupConfig,
|
|
46
|
+
): Promise<void> {
|
|
47
|
+
const extra =
|
|
48
|
+
(adapters ? generateAdapterEnvVars(adapters) : '') +
|
|
49
|
+
(ai ? generateAIEnvVars(ai) : '');
|
|
50
|
+
if (!extra) return;
|
|
51
|
+
|
|
52
|
+
const envPath = path.join(projectDir, '.env');
|
|
53
|
+
const existing = (await fs.pathExists(envPath)) ? await fs.readFile(envPath, 'utf-8') : '';
|
|
54
|
+
await fs.writeFile(envPath, existing.replace(/\s*$/, '') + extra + '\n');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function collectWizardDependencies(
|
|
58
|
+
options: Pick<InitOptions, 'adapters' | 'ai'>,
|
|
59
|
+
): Record<string, string> {
|
|
60
|
+
return {
|
|
61
|
+
...(options.adapters ? getAdapterDependencies(options.adapters) : {}),
|
|
62
|
+
...getAIDependencies(options.ai),
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export async function mergeDependenciesIntoPackageJson(
|
|
67
|
+
projectDir: string,
|
|
68
|
+
deps: Record<string, string>,
|
|
69
|
+
): Promise<boolean> {
|
|
70
|
+
if (Object.keys(deps).length === 0) return false;
|
|
71
|
+
const pkgPath = path.join(projectDir, 'package.json');
|
|
72
|
+
if (!(await fs.pathExists(pkgPath))) return false;
|
|
73
|
+
|
|
74
|
+
const pkg = await fs.readJson(pkgPath);
|
|
75
|
+
pkg.dependencies = pkg.dependencies ?? {};
|
|
76
|
+
let changed = false;
|
|
77
|
+
for (const [name, version] of Object.entries(deps)) {
|
|
78
|
+
if (pkg.dependencies[name] === version) continue;
|
|
79
|
+
pkg.dependencies[name] = version;
|
|
80
|
+
changed = true;
|
|
81
|
+
}
|
|
82
|
+
if (changed) {
|
|
83
|
+
await fs.writeJson(pkgPath, pkg, { spaces: 2 });
|
|
84
|
+
}
|
|
85
|
+
return changed;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** 交互向导结果写入 InitOptions 并补全 database 等前置条件 */
|
|
89
|
+
export function finalizeWizardOptions(options: InitOptions): void {
|
|
90
|
+
ensureDatabaseForAI(options);
|
|
91
|
+
ensureDatabaseForAdapters(options);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** 将 InitOptions 中的 wizard 结果合并进已有 zhin 配置对象 */
|
|
95
|
+
export function applyWizardOptionsToConfig(config: Record<string, unknown>, options: InitOptions): void {
|
|
96
|
+
if (options.database) applyDatabaseToConfig(config, options.database);
|
|
97
|
+
if (options.adapters) applyAdaptersToConfig(config, options.adapters);
|
|
98
|
+
if (options.ai) applyAIToConfig(config, options.ai);
|
|
99
|
+
}
|