@zhin.js/scaffold-wizard 0.1.3 → 0.1.5

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/src/adapter.ts CHANGED
@@ -3,18 +3,18 @@ import chalk from 'chalk';
3
3
  import {
4
4
  ADAPTERS_DOCS_URL,
5
5
  adapterDocsUrl,
6
- configureDiscordBot,
7
- configureGitHubBot,
6
+ configureDiscordEndpoint,
7
+ configureGitHubEndpoint,
8
8
  configureOneBot11Bot,
9
9
  configureQQBot,
10
- configureSlackBot,
11
- configureTelegramBot,
10
+ configureSlackEndpoint,
11
+ configureTelegramEndpoint,
12
12
  } from './adapter-configurers.js';
13
13
 
14
14
  export interface AdapterSetupResult {
15
15
  packages: string[];
16
16
  plugins: string[];
17
- bots: Array<Record<string, any>>;
17
+ endpoints: Array<Record<string, any>>;
18
18
  envVars: Record<string, string>;
19
19
  /** 所选适配器需要 database 插件(如 GitHub 订阅/OAuth 表) */
20
20
  requiresDatabase?: boolean;
@@ -33,7 +33,7 @@ interface AdapterDefinition {
33
33
  /** 文档:zhin.js.org/adapters/<slug>;索引见 ADAPTERS_DOCS_URL */
34
34
  docUrl?: string;
35
35
  requiresDatabase?: boolean;
36
- configure?: (ctx: import('./adapter-configurers.js').BotConfigureContext) => Promise<Record<string, unknown>>;
36
+ configure?: (ctx: import('./adapter-configurers.js').EndpointConfigureContext) => Promise<Record<string, unknown>>;
37
37
  fields: AdapterField[];
38
38
  }
39
39
 
@@ -89,10 +89,10 @@ const ADAPTERS: AdapterDefinition[] = [
89
89
  package: '@zhin.js/adapter-kook',
90
90
  plugin: '@zhin.js/adapter-kook',
91
91
  needsHttp: true,
92
- description: 'KOOK 语音平台 Bot',
93
- setupHint: '在 KOOK 开发者中心创建 Bot 并复制 Token;需配置 Webhook 回调地址(公网或内网穿透)。',
92
+ description: 'KOOK 语音平台 Endpoint',
93
+ setupHint: '在 KOOK 开发者中心创建 Endpoint 并复制 Token;需配置 Webhook 回调地址(公网或内网穿透)。',
94
94
  fields: [
95
- { key: 'token', message: 'KOOK Bot Token:', required: true, type: 'password', envKey: 'KOOK_TOKEN' },
95
+ { key: 'token', message: 'KOOK Endpoint Token:', required: true, type: 'password', envKey: 'KOOK_TOKEN' },
96
96
  ],
97
97
  },
98
98
  {
@@ -104,7 +104,7 @@ const ADAPTERS: AdapterDefinition[] = [
104
104
  description: 'Discord(Gateway 或 Interactions)',
105
105
  setupHint: 'Developer Portal 创建 Bot;Gateway 用 Token,Interactions 需 Application ID + Public Key。',
106
106
  docUrl: adapterDocsUrl('discord'),
107
- configure: configureDiscordBot,
107
+ configure: configureDiscordEndpoint,
108
108
  fields: [],
109
109
  },
110
110
  {
@@ -116,7 +116,7 @@ const ADAPTERS: AdapterDefinition[] = [
116
116
  description: 'Telegram Bot(polling / webhook 可选)',
117
117
  setupHint: '@BotFather 获取 Token;polling 适合本地,webhook 需 HTTPS 公网域名。',
118
118
  docUrl: adapterDocsUrl('telegram'),
119
- configure: configureTelegramBot,
119
+ configure: configureTelegramEndpoint,
120
120
  fields: [],
121
121
  },
122
122
  {
@@ -128,7 +128,7 @@ const ADAPTERS: AdapterDefinition[] = [
128
128
  description: 'Slack(Socket Mode 或 HTTP Events)',
129
129
  setupHint: 'Socket Mode 无需公网;HTTP 模式需配置 Event Subscriptions URL。',
130
130
  docUrl: adapterDocsUrl('slack'),
131
- configure: configureSlackBot,
131
+ configure: configureSlackEndpoint,
132
132
  fields: [],
133
133
  },
134
134
  {
@@ -213,19 +213,19 @@ const ADAPTERS: AdapterDefinition[] = [
213
213
  setupHint: 'GitHub App 或 gh CLI;Webhook 用 /pub/github/webhook;需 database 存订阅与 OAuth。',
214
214
  docUrl: adapterDocsUrl('github'),
215
215
  requiresDatabase: true,
216
- configure: configureGitHubBot,
216
+ configure: configureGitHubEndpoint,
217
217
  fields: [],
218
218
  },
219
219
  ];
220
220
 
221
221
  /**
222
- * 适配器选择与 Bot 配置引导向导
222
+ * 适配器选择与 Endpoint 配置引导向导
223
223
  */
224
224
  export async function configureAdapters(): Promise<AdapterSetupResult> {
225
225
  console.log('');
226
226
  console.log(chalk.blue('🔌 配置聊天适配器'));
227
227
  console.log(chalk.gray(' Sandbox 适合本地调试;生产环境请额外勾选目标 IM 平台。'));
228
- console.log(chalk.gray(' 带 Webhook 的适配器需要 Bot 可被公网访问(或使用内网穿透)。'));
228
+ console.log(chalk.gray(' 带 Webhook 的适配器需要 Endpoint 可被公网访问(或使用内网穿透)。'));
229
229
 
230
230
  const { selectedAdapters } = await inquirer.prompt([
231
231
  {
@@ -266,7 +266,7 @@ export async function configureAdapters(): Promise<AdapterSetupResult> {
266
266
  const result: AdapterSetupResult = {
267
267
  packages: [],
268
268
  plugins: [],
269
- bots: [],
269
+ endpoints: [],
270
270
  envVars: {},
271
271
  requiresDatabase: false,
272
272
  };
@@ -303,7 +303,7 @@ export async function configureAdapters(): Promise<AdapterSetupResult> {
303
303
 
304
304
  if (adapterDef.configure) {
305
305
  let markedDb = false;
306
- const botConfig = await adapterDef.configure({
306
+ const endpointConfig = await adapterDef.configure({
307
307
  envVars: result.envVars,
308
308
  markRequiresDatabase: () => {
309
309
  markedDb = true;
@@ -311,11 +311,11 @@ export async function configureAdapters(): Promise<AdapterSetupResult> {
311
311
  },
312
312
  });
313
313
  if (markedDb) result.requiresDatabase = true;
314
- result.bots.push(botConfig);
314
+ result.endpoints.push(endpointConfig);
315
315
  continue;
316
316
  }
317
317
 
318
- const botConfig: Record<string, any> = {
318
+ const endpointConfig: Record<string, any> = {
319
319
  context: adapterDef.value,
320
320
  };
321
321
 
@@ -343,13 +343,13 @@ export async function configureAdapters(): Promise<AdapterSetupResult> {
343
343
  if (field.envKey) {
344
344
  // 敏感信息存 .env,配置引用环境变量
345
345
  result.envVars[field.envKey] = value || '';
346
- botConfig[field.key] = `\${${field.envKey}}`;
346
+ endpointConfig[field.key] = `\${${field.envKey}}`;
347
347
  } else {
348
- botConfig[field.key] = value;
348
+ endpointConfig[field.key] = value;
349
349
  }
350
350
  }
351
351
 
352
- result.bots.push(botConfig);
352
+ result.endpoints.push(endpointConfig);
353
353
  }
354
354
 
355
355
  return result;
@@ -370,7 +370,7 @@ export function generateAdapterEnvVars(result: AdapterSetupResult): string {
370
370
  }
371
371
 
372
372
  /**
373
- * 将 bot 配置项格式化为 YAML 行
373
+ * 将 Endpoint 配置项格式化为 YAML 行
374
374
  */
375
375
  function appendYamlValue(lines: string[], key: string, value: unknown, indent: number): void {
376
376
  const pad = ' '.repeat(indent);
@@ -405,20 +405,20 @@ function appendYamlValue(lines: string[], key: string, value: unknown, indent: n
405
405
  }
406
406
 
407
407
  /**
408
- * 生成 YAML 格式的 bots 配置段
408
+ * 生成 YAML 格式的 endpoints 配置段
409
409
  */
410
- export function generateBotsConfigYaml(result: AdapterSetupResult): string {
411
- if (result.bots.length === 0) {
410
+ export function generateEndpointsConfigYaml(result: AdapterSetupResult): string {
411
+ if (result.endpoints.length === 0) {
412
412
  return `
413
413
 
414
- # Sandbox:Remote Console 打开「沙盒」页并通过 WebSocket 连接时自动创建 bot
415
- bots: []
414
+ # Sandbox:Remote Console 打开「沙盒」页并通过 WebSocket 连接时自动创建 Endpoint
415
+ endpoints: []
416
416
  `;
417
417
  }
418
418
 
419
- const lines: string[] = ['', 'bots:'];
420
- for (const bot of result.bots) {
421
- const { context, ...config } = bot;
419
+ const lines: string[] = ['', 'endpoints:'];
420
+ for (const entry of result.endpoints) {
421
+ const { context, ...config } = entry;
422
422
  lines.push(` - context: ${context}`);
423
423
  for (const [key, value] of Object.entries(config)) {
424
424
  appendYamlValue(lines, key, value, 2);
@@ -429,15 +429,15 @@ bots: []
429
429
  }
430
430
 
431
431
  /**
432
- * 生成 TOML 格式的 bots 配置段
432
+ * 生成 TOML 格式的 endpoints 配置段
433
433
  */
434
- export function generateBotsConfigToml(result: AdapterSetupResult): string {
435
- if (result.bots.length === 0) return '';
434
+ export function generateEndpointsConfigToml(result: AdapterSetupResult): string {
435
+ if (result.endpoints.length === 0) return '';
436
436
 
437
437
  const lines: string[] = [''];
438
- for (const bot of result.bots) {
439
- lines.push('[[bots]]');
440
- for (const [key, value] of Object.entries(bot)) {
438
+ for (const entry of result.endpoints) {
439
+ lines.push('[[endpoints]]');
440
+ for (const [key, value] of Object.entries(entry)) {
441
441
  if (typeof value === 'string') {
442
442
  lines.push(`${key} = "${value.replace(/"/g, '\\"')}"`);
443
443
  } else {
@@ -450,11 +450,11 @@ export function generateBotsConfigToml(result: AdapterSetupResult): string {
450
450
  }
451
451
 
452
452
  /**
453
- * 生成 JSON 格式的 bots 配置段
453
+ * 生成 JSON 格式的 endpoints 配置段
454
454
  */
455
- export function generateBotsConfigJSON(result: AdapterSetupResult): string {
456
- if (result.bots.length === 0) return '';
457
- return ` "bots": ${JSON.stringify(result.bots, null, 4).replace(/^/gm, ' ').trimStart()},`;
455
+ export function generateEndpointsConfigJSON(result: AdapterSetupResult): string {
456
+ if (result.endpoints.length === 0) return '';
457
+ return ` "endpoints": ${JSON.stringify(result.endpoints, null, 4).replace(/^/gm, ' ').trimStart()},`;
458
458
  }
459
459
 
460
460
  /**
@@ -462,21 +462,21 @@ export function generateBotsConfigJSON(result: AdapterSetupResult): string {
462
462
  */
463
463
  export function getAdapterSetupNotes(result: AdapterSetupResult): string[] {
464
464
  const notes: string[] = [];
465
- for (const bot of result.bots) {
466
- const context = bot.context as string;
467
- if (context === 'telegram' && bot.polling === false) {
465
+ for (const entry of result.endpoints) {
466
+ const context = entry.context as string;
467
+ if (context === 'telegram' && entry.polling === false) {
468
468
  notes.push('Telegram Webhook: 确保 HTTPS 域名可从公网访问,且 webhook.port 已放行');
469
469
  }
470
- if (context === 'telegram' && bot.polling !== false) {
470
+ if (context === 'telegram' && entry.polling !== false) {
471
471
  notes.push('Telegram polling: 本地开发可直接 pnpm dev,无需公网');
472
472
  }
473
473
  if (context === 'github') {
474
- if (bot.webhook_secret) {
474
+ if (entry.webhook_secret) {
475
475
  notes.push('GitHub Webhook: App 设置 URL 为 https://<域名>/pub/github/webhook');
476
476
  } else {
477
477
  notes.push('GitHub polling: 无 Webhook 时将轮询 Events API(默认 60s)');
478
478
  }
479
- if (bot.app_id) {
479
+ if (entry.app_id) {
480
480
  notes.push('GitHub App: 私钥路径需存在,且 App 已安装到目标仓库');
481
481
  } else {
482
482
  notes.push('GitHub gh CLI: 运行 gh auth login 完成认证');
package/src/ai.ts CHANGED
@@ -76,19 +76,22 @@ const PROVIDERS = [
76
76
  },
77
77
  ] as const;
78
78
 
79
- function providerApiFor(name: string): string {
79
+ function providerSdkFor(name: string): string {
80
80
  switch (name) {
81
81
  case 'anthropic':
82
- return 'anthropic-messages';
82
+ return 'anthropic';
83
83
  case 'google':
84
84
  case 'gemini':
85
- return 'google-generative-ai';
85
+ return 'google';
86
86
  case 'ollama':
87
- return 'ollama-chat';
88
- case 'cloudflare':
89
- return 'cloudflare-workers-ai';
87
+ return 'ollama';
88
+ case 'deepseek':
89
+ return 'deepseek';
90
+ case 'moonshot':
91
+ case 'zhipu':
92
+ return 'openai-compatible';
90
93
  default:
91
- return 'openai-completions';
94
+ return 'openai';
92
95
  }
93
96
  }
94
97
 
@@ -127,7 +130,7 @@ export const RECOMMENDED_AI_DEFAULTS = {
127
130
  export async function configureAI(): Promise<AISetupConfig> {
128
131
  console.log('');
129
132
  console.log(chalk.blue('🤖 配置 AI 智能体'));
130
- console.log(chalk.gray(' 启用后将预装 @modelcontextprotocol/sdk,支持 MCP 与 memory 扩展。'));
133
+ console.log(chalk.gray(' 启用后将自动写入 package.json:@zhin.js/agent、zod、ai、所选 @ai-sdk/*、@modelcontextprotocol/sdk。'));
131
134
  console.log(chalk.gray(' 会话持久化默认开启,将自动配置 SQLite 数据库(若尚未选择)。'));
132
135
 
133
136
  const { enableAI } = await inquirer.prompt([
@@ -223,7 +226,7 @@ export async function configureAI(): Promise<AISetupConfig> {
223
226
 
224
227
  console.log('');
225
228
  console.log(chalk.blue('⚡ 配置 AI 触发方式'));
226
- console.log(chalk.gray(' 决定 Bot 何时自动调用大模型;命令(/ 开头)不会被 AI 拦截。'));
229
+ console.log(chalk.gray(' 决定 Endpoint 何时自动调用大模型;命令(/ 开头)不会被 AI 拦截。'));
227
230
 
228
231
  const triggerConfig = await inquirer.prompt([
229
232
  {
@@ -348,7 +351,7 @@ export function generateAIConfigYaml(config: AISetupConfig): string {
348
351
  if (config.providers) {
349
352
  for (const [name, providerConfig] of Object.entries(config.providers)) {
350
353
  lines.push(` ${name}:`);
351
- lines.push(` api: ${providerApiFor(name)}`);
354
+ lines.push(` sdk: ${providerSdkFor(name)}`);
352
355
  if (providerConfig.apiKey) {
353
356
  lines.push(` apiKey: ${providerConfig.apiKey}`);
354
357
  }
package/src/apply.ts CHANGED
@@ -30,9 +30,9 @@ export function applyAdaptersToConfig(config: Record<string, unknown>, result: A
30
30
  }
31
31
  config.plugins = plugins;
32
32
 
33
- const bots = Array.isArray(config.bots) ? [...config.bots as Record<string, unknown>[]] : [];
34
- bots.push(...result.bots);
35
- config.bots = bots;
33
+ const endpoints = Array.isArray(config.endpoints) ? [...config.endpoints as Record<string, unknown>[]] : [];
34
+ endpoints.push(...result.endpoints);
35
+ config.endpoints = endpoints;
36
36
  }
37
37
 
38
38
  export function applyAIToConfig(config: Record<string, unknown>, ai: AISetupConfig): void {
package/src/index.ts CHANGED
@@ -5,9 +5,9 @@ export type { AdapterSetupResult } from './adapter.js';
5
5
  export {
6
6
  configureAdapters,
7
7
  generateAdapterEnvVars,
8
- generateBotsConfigYaml,
9
- generateBotsConfigJSON,
10
- generateBotsConfigToml,
8
+ generateEndpointsConfigYaml,
9
+ generateEndpointsConfigJSON,
10
+ generateEndpointsConfigToml,
11
11
  getAdapterDependencies,
12
12
  getAdapterSetupNotes,
13
13
  } from './adapter.js';
@@ -34,6 +34,13 @@ export { configureDatabaseOptions } from './database.js';
34
34
  export {
35
35
  MCP_SDK_VERSION,
36
36
  getAIDependencies,
37
+ listAIDependencyNames,
38
+ formatAIDependencyHint,
39
+ isAiEnabledInConfig,
40
+ resolveDefaultProviderFromConfig,
41
+ getRequiredAIDependenciesForConfig,
42
+ diagnoseAIDependencies,
43
+ formatAIDependencyFixCommand,
37
44
  ensureDatabaseForAI,
38
45
  ensureDatabaseForAdapters,
39
46
  } from './project-deps.js';
@@ -1,17 +1,194 @@
1
+ import { createRequire } from 'node:module';
2
+ import fs from 'fs-extra';
3
+ import path from 'node:path';
1
4
  import type { AISetupConfig } from './ai.js';
2
5
  import type { InitOptions } from './types.js';
3
6
 
4
7
  /** 与 monorepo 中 agent / test-bot 对齐的 MCP SDK 版本 */
5
8
  export const MCP_SDK_VERSION = '^1.29.0';
6
9
 
10
+ const AI_STACK_VERSION = '^4.0.0';
11
+ const ZOD_VERSION = '^4.0.0';
12
+ const VERCEL_AI_VERSION = '^6.0.0';
13
+
14
+ const PROVIDER_SDK_PACKAGES: Record<string, { pkg: string; version: string }> = {
15
+ openai: { pkg: '@ai-sdk/openai', version: '^3.0.0' },
16
+ anthropic: { pkg: '@ai-sdk/anthropic', version: '^3.0.0' },
17
+ google: { pkg: '@ai-sdk/google', version: '^3.0.0' },
18
+ gemini: { pkg: '@ai-sdk/google', version: '^3.0.0' },
19
+ deepseek: { pkg: '@ai-sdk/deepseek', version: '^1.0.0' },
20
+ ollama: { pkg: '@ai-sdk/openai-compatible', version: '^1.0.0' },
21
+ moonshot: { pkg: '@ai-sdk/openai-compatible', version: '^1.0.0' },
22
+ zhipu: { pkg: '@ai-sdk/openai-compatible', version: '^1.0.0' },
23
+ };
24
+
25
+ function providerSdkPackage(provider?: string): { pkg: string; version: string } | undefined {
26
+ if (!provider) return undefined;
27
+ return PROVIDER_SDK_PACKAGES[provider];
28
+ }
29
+
30
+ /** 启用 AI 时将写入 package.json 的依赖名(用于向导提示与 CLI 输出) */
31
+ export function listAIDependencyNames(provider?: string, options?: { includeMcp?: boolean }): string[] {
32
+ const names = ['@zhin.js/agent', 'zod', 'ai'];
33
+ if (options?.includeMcp !== false) {
34
+ names.push('@modelcontextprotocol/sdk');
35
+ }
36
+ const sdk = providerSdkPackage(provider);
37
+ if (sdk && !names.includes(sdk.pkg)) {
38
+ names.push(sdk.pkg);
39
+ }
40
+ return names;
41
+ }
42
+
43
+ export function formatAIDependencyHint(provider?: string, options?: { includeMcp?: boolean }): string {
44
+ return listAIDependencyNames(provider, options).join(', ');
45
+ }
46
+
47
+ export interface PackageJsonLike {
48
+ dependencies?: Record<string, string>;
49
+ devDependencies?: Record<string, string>;
50
+ }
51
+
52
+ /** zhin.config 中是否启用了 AI(enabled 未显式 false 且存在 agents/providers) */
53
+ export function isAiEnabledInConfig(config: { ai?: unknown }): boolean {
54
+ const ai = config.ai;
55
+ if (!ai || typeof ai !== 'object' || Array.isArray(ai)) return false;
56
+ const rec = ai as Record<string, unknown>;
57
+ if (rec.enabled === false) return false;
58
+ if (rec.enabled === true) return true;
59
+ const agents = rec.agents;
60
+ if (agents && typeof agents === 'object' && !Array.isArray(agents) && Object.keys(agents).length > 0) {
61
+ return true;
62
+ }
63
+ const providers = rec.providers;
64
+ if (providers && typeof providers === 'object' && !Array.isArray(providers) && Object.keys(providers).length > 0) {
65
+ return true;
66
+ }
67
+ return false;
68
+ }
69
+
70
+ export function resolveDefaultProviderFromConfig(config: { ai?: unknown }): string | undefined {
71
+ const ai = config.ai;
72
+ if (!ai || typeof ai !== 'object' || Array.isArray(ai)) return undefined;
73
+ const rec = ai as Record<string, unknown>;
74
+ const agents = rec.agents;
75
+ if (agents && typeof agents === 'object' && !Array.isArray(agents)) {
76
+ const zhin = (agents as Record<string, { provider?: string }>).zhin;
77
+ if (zhin?.provider) return String(zhin.provider);
78
+ const firstAgent = Object.values(agents as Record<string, { provider?: string }>)[0];
79
+ if (firstAgent?.provider) return String(firstAgent.provider);
80
+ }
81
+ const providers = rec.providers;
82
+ if (providers && typeof providers === 'object' && !Array.isArray(providers)) {
83
+ const first = Object.keys(providers as Record<string, unknown>)[0];
84
+ if (first) return first;
85
+ }
86
+ if (rec.defaultProvider != null) return String(rec.defaultProvider);
87
+ return undefined;
88
+ }
89
+
90
+ function aiConfigNeedsMcp(ai: Record<string, unknown>): boolean {
91
+ if (ai.memoryMcp === true) return true;
92
+ const servers = ai.mcpServers;
93
+ return Array.isArray(servers) && servers.length > 0;
94
+ }
95
+
96
+ /** 根据 zhin.config 的 ai 段推导应安装的 production 依赖 */
97
+ export function getRequiredAIDependenciesForConfig(config: { ai?: unknown }): Record<string, string> {
98
+ if (!isAiEnabledInConfig(config)) return {};
99
+ const ai = config.ai as Record<string, unknown>;
100
+ const setup: AISetupConfig = {
101
+ enabled: true,
102
+ defaultProvider: resolveDefaultProviderFromConfig(config),
103
+ memoryMcp: aiConfigNeedsMcp(ai),
104
+ };
105
+ const deps = getAIDependencies(setup);
106
+ if (!aiConfigNeedsMcp(ai)) {
107
+ delete deps['@modelcontextprotocol/sdk'];
108
+ }
109
+ return deps;
110
+ }
111
+
112
+ export function findMissingPackageDependencies(
113
+ pkg: PackageJsonLike,
114
+ required: Record<string, string>,
115
+ ): string[] {
116
+ const declared = new Set([
117
+ ...Object.keys(pkg.dependencies ?? {}),
118
+ ...Object.keys(pkg.devDependencies ?? {}),
119
+ ]);
120
+ return Object.keys(required).filter((name) => !declared.has(name));
121
+ }
122
+
123
+ export function findUnresolvedPackageInstalls(cwd: string, packageNames: string[]): string[] {
124
+ const pkgJson = path.join(cwd, 'package.json');
125
+ if (!fs.existsSync(pkgJson)) return [...packageNames];
126
+ const req = createRequire(pkgJson);
127
+ const missing: string[] = [];
128
+ for (const name of packageNames) {
129
+ try {
130
+ req.resolve(`${name}/package.json`);
131
+ } catch {
132
+ missing.push(name);
133
+ }
134
+ }
135
+ return missing;
136
+ }
137
+
138
+ export interface AIDependencyDiagnosis {
139
+ enabled: true;
140
+ provider?: string;
141
+ required: Record<string, string>;
142
+ missingFromPackageJson: string[];
143
+ notInstalled: string[];
144
+ }
145
+
146
+ export function diagnoseAIDependencies(
147
+ cwd: string,
148
+ config: { ai?: unknown },
149
+ pkg?: PackageJsonLike,
150
+ ): AIDependencyDiagnosis | null {
151
+ if (!isAiEnabledInConfig(config)) return null;
152
+ const required = getRequiredAIDependenciesForConfig(config);
153
+ const packageJsonPath = path.join(cwd, 'package.json');
154
+ const packageJson = pkg ?? (fs.existsSync(packageJsonPath) ? fs.readJsonSync(packageJsonPath) as PackageJsonLike : {});
155
+ const missingFromPackageJson = findMissingPackageDependencies(packageJson, required);
156
+ const declared = Object.keys(required).filter((name) => !missingFromPackageJson.includes(name));
157
+ const notInstalled = findUnresolvedPackageInstalls(cwd, declared);
158
+ return {
159
+ enabled: true,
160
+ provider: resolveDefaultProviderFromConfig(config),
161
+ required,
162
+ missingFromPackageJson,
163
+ notInstalled,
164
+ };
165
+ }
166
+
167
+ export function formatAIDependencyFixCommand(missing: string[], required: Record<string, string>): string {
168
+ if (missing.length === 0) return 'pnpm install';
169
+ const specs = missing.map((name) => `${name}@${required[name] ?? 'latest'}`);
170
+ return `pnpm add ${specs.join(' ')}`;
171
+ }
172
+
7
173
  /**
8
- * AI 启用时预装 MCP SDK,避免 memoryMcp / mcpServers 运行时报 optional peer 缺失。
174
+ * AI 启用时安装 agent 栈 + 所选 provider 的 @ai-sdk/* + 可选 MCP SDK。
9
175
  */
10
176
  export function getAIDependencies(ai?: AISetupConfig): Record<string, string> {
11
177
  if (!ai?.enabled) return {};
12
- return {
178
+
179
+ const deps: Record<string, string> = {
180
+ '@zhin.js/agent': AI_STACK_VERSION,
181
+ zod: ZOD_VERSION,
182
+ ai: VERCEL_AI_VERSION,
13
183
  '@modelcontextprotocol/sdk': MCP_SDK_VERSION,
14
184
  };
185
+
186
+ const sdk = providerSdkPackage(ai.defaultProvider);
187
+ if (sdk) {
188
+ deps[sdk.pkg] = sdk.version;
189
+ }
190
+
191
+ return deps;
15
192
  }
16
193
 
17
194
  /**
package/src/types.ts CHANGED
@@ -6,6 +6,7 @@ export type { AdapterSetupResult } from './adapter.js';
6
6
 
7
7
  export interface InitOptions {
8
8
  name?: string;
9
+ template?: 'default' | 'life-assistant';
9
10
  config?: 'json' | 'yaml' | 'toml';
10
11
  runtime?: 'node' | 'bun';
11
12
  yes?: boolean;