ai-agent-router 0.2.1 → 0.2.2

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.
Files changed (154) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/build-manifest.json +2 -2
  3. package/.next/fallback-build-manifest.json +2 -2
  4. package/.next/next-server.js.nft.json +1 -1
  5. package/.next/prerender-manifest.json +3 -3
  6. package/.next/required-server-files.js +4 -4
  7. package/.next/required-server-files.json +4 -4
  8. package/.next/server/app/_global-error.html +2 -2
  9. package/.next/server/app/_global-error.rsc +1 -1
  10. package/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  11. package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  12. package/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  13. package/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  14. package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  15. package/.next/server/app/_not-found/page.js +1 -1
  16. package/.next/server/app/_not-found/page.js.nft.json +1 -1
  17. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  18. package/.next/server/app/_not-found.html +1 -1
  19. package/.next/server/app/_not-found.rsc +2 -2
  20. package/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
  21. package/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  22. package/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
  23. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  24. package/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  25. package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  26. package/.next/server/app/api/config/route.js.nft.json +1 -1
  27. package/.next/server/app/api/gateway/[...path]/route.js.nft.json +1 -1
  28. package/.next/server/app/api/gateway/models/route.js.nft.json +1 -1
  29. package/.next/server/app/api/gateway/route.js.nft.json +1 -1
  30. package/.next/server/app/api/ide/claude/apply/route.js.nft.json +1 -1
  31. package/.next/server/app/api/ide/claude/available-models/route.js.nft.json +1 -1
  32. package/.next/server/app/api/ide/claude/restore/route.js.nft.json +1 -1
  33. package/.next/server/app/api/ide/claude/save/route.js.nft.json +1 -1
  34. package/.next/server/app/api/ide/claude/status/route.js.nft.json +1 -1
  35. package/.next/server/app/api/ide/claude/test/route.js.nft.json +1 -1
  36. package/.next/server/app/api/ide/openclaw/apply/route.js.nft.json +1 -1
  37. package/.next/server/app/api/ide/openclaw/available-models/route.js.nft.json +1 -1
  38. package/.next/server/app/api/ide/openclaw/preview/route.js.nft.json +1 -1
  39. package/.next/server/app/api/ide/openclaw/save/route.js.nft.json +1 -1
  40. package/.next/server/app/api/ide/openclaw/status/route.js.nft.json +1 -1
  41. package/.next/server/app/api/ide/opencode/apply/route.js.nft.json +1 -1
  42. package/.next/server/app/api/ide/opencode/available-models/route.js.nft.json +1 -1
  43. package/.next/server/app/api/ide/opencode/preview/route.js.nft.json +1 -1
  44. package/.next/server/app/api/ide/opencode/save/route.js.nft.json +1 -1
  45. package/.next/server/app/api/ide/opencode/status/route.js.nft.json +1 -1
  46. package/.next/server/app/api/logs/route.js.nft.json +1 -1
  47. package/.next/server/app/api/models/route.js.nft.json +1 -1
  48. package/.next/server/app/api/providers/route.js.nft.json +1 -1
  49. package/.next/server/app/api/providers/test/route.js.nft.json +1 -1
  50. package/.next/server/app/api/service/force-stop/route.js.nft.json +1 -1
  51. package/.next/server/app/api/service/start/route.js.nft.json +1 -1
  52. package/.next/server/app/api/service/status/route.js.nft.json +1 -1
  53. package/.next/server/app/api/service/stop/route.js.nft.json +1 -1
  54. package/.next/server/app/ide/page.js +1 -1
  55. package/.next/server/app/ide/page.js.nft.json +1 -1
  56. package/.next/server/app/ide/page_client-reference-manifest.js +1 -1
  57. package/.next/server/app/ide.html +1 -1
  58. package/.next/server/app/ide.rsc +3 -3
  59. package/.next/server/app/ide.segments/_full.segment.rsc +3 -3
  60. package/.next/server/app/ide.segments/_head.segment.rsc +1 -1
  61. package/.next/server/app/ide.segments/_index.segment.rsc +2 -2
  62. package/.next/server/app/ide.segments/_tree.segment.rsc +2 -2
  63. package/.next/server/app/ide.segments/ide/__PAGE__.segment.rsc +2 -2
  64. package/.next/server/app/ide.segments/ide.segment.rsc +1 -1
  65. package/.next/server/app/index.html +1 -1
  66. package/.next/server/app/index.rsc +2 -2
  67. package/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  68. package/.next/server/app/index.segments/_full.segment.rsc +2 -2
  69. package/.next/server/app/index.segments/_head.segment.rsc +1 -1
  70. package/.next/server/app/index.segments/_index.segment.rsc +2 -2
  71. package/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  72. package/.next/server/app/logs/page.js +1 -1
  73. package/.next/server/app/logs/page.js.nft.json +1 -1
  74. package/.next/server/app/logs/page_client-reference-manifest.js +1 -1
  75. package/.next/server/app/logs.html +1 -1
  76. package/.next/server/app/logs.rsc +2 -2
  77. package/.next/server/app/logs.segments/_full.segment.rsc +2 -2
  78. package/.next/server/app/logs.segments/_head.segment.rsc +1 -1
  79. package/.next/server/app/logs.segments/_index.segment.rsc +2 -2
  80. package/.next/server/app/logs.segments/_tree.segment.rsc +2 -2
  81. package/.next/server/app/logs.segments/logs/__PAGE__.segment.rsc +1 -1
  82. package/.next/server/app/logs.segments/logs.segment.rsc +1 -1
  83. package/.next/server/app/models/page.js +1 -1
  84. package/.next/server/app/models/page.js.nft.json +1 -1
  85. package/.next/server/app/models/page_client-reference-manifest.js +1 -1
  86. package/.next/server/app/models.html +1 -1
  87. package/.next/server/app/models.rsc +2 -2
  88. package/.next/server/app/models.segments/_full.segment.rsc +2 -2
  89. package/.next/server/app/models.segments/_head.segment.rsc +1 -1
  90. package/.next/server/app/models.segments/_index.segment.rsc +2 -2
  91. package/.next/server/app/models.segments/_tree.segment.rsc +2 -2
  92. package/.next/server/app/models.segments/models/__PAGE__.segment.rsc +1 -1
  93. package/.next/server/app/models.segments/models.segment.rsc +1 -1
  94. package/.next/server/app/page.js +1 -1
  95. package/.next/server/app/page.js.nft.json +1 -1
  96. package/.next/server/app/page_client-reference-manifest.js +1 -1
  97. package/.next/server/app/providers/page.js +1 -1
  98. package/.next/server/app/providers/page.js.nft.json +1 -1
  99. package/.next/server/app/providers/page_client-reference-manifest.js +1 -1
  100. package/.next/server/app/providers.html +1 -1
  101. package/.next/server/app/providers.rsc +2 -2
  102. package/.next/server/app/providers.segments/_full.segment.rsc +2 -2
  103. package/.next/server/app/providers.segments/_head.segment.rsc +1 -1
  104. package/.next/server/app/providers.segments/_index.segment.rsc +2 -2
  105. package/.next/server/app/providers.segments/_tree.segment.rsc +2 -2
  106. package/.next/server/app/providers.segments/providers/__PAGE__.segment.rsc +1 -1
  107. package/.next/server/app/providers.segments/providers.segment.rsc +1 -1
  108. package/.next/server/chunks/[root-of-the-server]__3a204d25._.js +1 -1
  109. package/.next/server/chunks/[root-of-the-server]__3a204d25._.js.map +1 -1
  110. package/.next/server/chunks/[root-of-the-server]__4a8f9bc7._.js +1 -1
  111. package/.next/server/chunks/[root-of-the-server]__4a8f9bc7._.js.map +1 -1
  112. package/.next/server/chunks/[root-of-the-server]__5e8276bc._.js +1 -1
  113. package/.next/server/chunks/[root-of-the-server]__5e8276bc._.js.map +1 -1
  114. package/.next/server/chunks/[root-of-the-server]__c1b4b601._.js +16 -16
  115. package/.next/server/chunks/[root-of-the-server]__c1b4b601._.js.map +1 -1
  116. package/.next/server/chunks/[root-of-the-server]__f0461b8d._.js +1 -1
  117. package/.next/server/chunks/[root-of-the-server]__f0461b8d._.js.map +1 -1
  118. package/.next/server/chunks/ssr/_69468f4c._.js +3 -0
  119. package/.next/server/chunks/ssr/_69468f4c._.js.map +1 -0
  120. package/.next/server/chunks/ssr/src_app_ide_page_tsx_8962793b._.js +1 -1
  121. package/.next/server/chunks/ssr/src_app_ide_page_tsx_8962793b._.js.map +1 -1
  122. package/.next/server/pages/404.html +1 -1
  123. package/.next/server/pages/500.html +2 -2
  124. package/.next/server/server-reference-manifest.js +1 -1
  125. package/.next/server/server-reference-manifest.json +1 -1
  126. package/.next/static/chunks/6992685fe009e8fd.css +1 -0
  127. package/.next/static/chunks/8ccc14e022ff6de3.js +1 -0
  128. package/README.md +31 -4
  129. package/dist/package.json +55 -0
  130. package/dist/src/app/api/config/route.js +17 -0
  131. package/dist/src/app/api/ide/claude/apply/route.js +84 -0
  132. package/dist/src/app/api/ide/claude/restore/route.js +30 -0
  133. package/dist/src/app/api/ide/claude/save/route.js +79 -0
  134. package/dist/src/app/components/Footer.js +11 -0
  135. package/dist/src/app/ide/page.js +62 -2
  136. package/dist/src/app/layout.js +5 -1
  137. package/dist/src/cli/index.js +0 -0
  138. package/dist/src/db/database.js +21 -2
  139. package/package.json +1 -1
  140. package/src/app/api/config/route.ts +17 -0
  141. package/src/app/api/ide/claude/apply/route.ts +94 -0
  142. package/src/app/api/ide/claude/restore/route.ts +36 -1
  143. package/src/app/api/ide/claude/save/route.ts +91 -1
  144. package/src/app/components/Footer.tsx +9 -0
  145. package/src/app/ide/page.tsx +71 -5
  146. package/src/app/layout.tsx +3 -1
  147. package/src/db/database.ts +27 -2
  148. package/.next/server/chunks/ssr/src_app_b2b1d928._.js +0 -3
  149. package/.next/server/chunks/ssr/src_app_b2b1d928._.js.map +0 -1
  150. package/.next/static/chunks/0f120c117962200b.css +0 -1
  151. package/.next/static/chunks/8ed839b2e4948968.js +0 -1
  152. /package/.next/static/{dYin74gcpdlg8TGoGv-_d → cf2SWIkI5HVbnDjLExI42}/_buildManifest.js +0 -0
  153. /package/.next/static/{dYin74gcpdlg8TGoGv-_d → cf2SWIkI5HVbnDjLExI42}/_clientMiddlewareManifest.json +0 -0
  154. /package/.next/static/{dYin74gcpdlg8TGoGv-_d → cf2SWIkI5HVbnDjLExI42}/_ssgManifest.js +0 -0
@@ -11,6 +11,10 @@ const queries_1 = require("@/db/queries");
11
11
  const CLAUDE_DIR = (0, path_1.join)((0, os_1.homedir)(), '.claude');
12
12
  const SETTINGS_FILE = (0, path_1.join)(CLAUDE_DIR, 'settings.json');
13
13
  const BACKUP_FILE = (0, path_1.join)(CLAUDE_DIR, 'settings.json.aar.bak');
14
+ // Claude 登录配置文件(用于 hasCompletedOnboarding)
15
+ const CLAUDE_JSON_FILE = (0, path_1.join)((0, os_1.homedir)(), '.claude.json');
16
+ // AAR 备份目录
17
+ const AAR_BACKUP_DIR = (0, path_1.join)((0, os_1.homedir)(), '.aar', 'backups');
14
18
  // 默认模型映射
15
19
  const DEFAULT_MODEL_MAPPING = {
16
20
  haiku: 'GLM-4.5-air',
@@ -34,6 +38,15 @@ async function POST(request) {
34
38
  const gatewayApiKey = await getGatewayApiKey();
35
39
  const modelMapping = { haiku, sonnet, opus, default: defaultModel, reasoning: reasoningModel };
36
40
  const aarEnv = buildAarEnv(gatewayAddress, gatewayApiKey, modelMapping);
41
+ // 在修改配置前,先备份到 ~/.aar/backups 目录
42
+ const settingsBackup = backupToAar(SETTINGS_FILE, 'settings');
43
+ const claudeJsonBackup = backupToAar(CLAUDE_JSON_FILE, 'claude');
44
+ if (!settingsBackup.success) {
45
+ console.warn('[Claude Apply] Settings backup warning:', settingsBackup.error);
46
+ }
47
+ if (!claudeJsonBackup.success) {
48
+ console.warn('[Claude Apply] Claude.json backup warning:', claudeJsonBackup.error);
49
+ }
37
50
  const backupResult = backupOriginalConfig();
38
51
  if (!backupResult.success) {
39
52
  return server_1.NextResponse.json({ error: backupResult.error }, { status: 500 });
@@ -62,12 +75,22 @@ async function POST(request) {
62
75
  if (!validationResult.success) {
63
76
  return server_1.NextResponse.json({ error: validationResult.error }, { status: 500 });
64
77
  }
78
+ // 处理 ~/.claude.json,确保包含 hasCompletedOnboarding: true
79
+ const onboardingResult = ensureClaudeOnboarding();
80
+ if (!onboardingResult.success) {
81
+ console.warn('[Claude Apply] Onboarding update warning:', onboardingResult.error);
82
+ }
65
83
  return server_1.NextResponse.json({
66
84
  success: true,
67
85
  message: 'Configuration applied successfully',
68
86
  config: configToWrite,
69
87
  backup: backupResult.existed ? 'Created backup' : 'No existing config to backup',
70
88
  previousWasFromAar: backupResult.existed && backupResult.isFromAar,
89
+ onboarding: onboardingResult.created ? 'Created ~/.claude.json' : 'Updated ~/.claude.json',
90
+ aarBackups: {
91
+ settings: settingsBackup.backupPath,
92
+ claudeJson: claudeJsonBackup.backupPath,
93
+ },
71
94
  });
72
95
  }
73
96
  catch (error) {
@@ -168,3 +191,64 @@ function validateAndWriteConfig(config) {
168
191
  };
169
192
  }
170
193
  }
194
+ /**
195
+ * 备份文件到 ~/.aar/backups 目录
196
+ */
197
+ function backupToAar(filePath, backupName) {
198
+ try {
199
+ if (!(0, fs_1.existsSync)(filePath)) {
200
+ return { success: true }; // 文件不存在,无需备份
201
+ }
202
+ if (!(0, fs_1.existsSync)(AAR_BACKUP_DIR)) {
203
+ (0, fs_1.mkdirSync)(AAR_BACKUP_DIR, { recursive: true });
204
+ }
205
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
206
+ const backupPath = (0, path_1.join)(AAR_BACKUP_DIR, `${backupName}-${timestamp}.json`);
207
+ (0, fs_1.copyFileSync)(filePath, backupPath);
208
+ return { success: true, backupPath };
209
+ }
210
+ catch (error) {
211
+ return {
212
+ success: false,
213
+ error: `Backup to AAR failed: ${error.message}`,
214
+ };
215
+ }
216
+ }
217
+ /**
218
+ * 确保 ~/.claude.json 文件中包含 hasCompletedOnboarding: true
219
+ * 这允许用户跳过 Claude 登录使用自定义模型
220
+ */
221
+ function ensureClaudeOnboarding() {
222
+ try {
223
+ let config = {};
224
+ let created = false;
225
+ if ((0, fs_1.existsSync)(CLAUDE_JSON_FILE)) {
226
+ try {
227
+ const content = (0, fs_1.readFileSync)(CLAUDE_JSON_FILE, 'utf-8');
228
+ config = JSON.parse(content);
229
+ }
230
+ catch {
231
+ // 文件存在但解析失败,使用空对象
232
+ config = {};
233
+ }
234
+ }
235
+ else {
236
+ created = true;
237
+ }
238
+ // 检查是否已经有 hasCompletedOnboarding
239
+ if (config.hasCompletedOnboarding === true) {
240
+ return { success: true, created: false };
241
+ }
242
+ // 添加 hasCompletedOnboarding
243
+ config.hasCompletedOnboarding = true;
244
+ (0, fs_1.writeFileSync)(CLAUDE_JSON_FILE, JSON.stringify(config, null, 2), 'utf-8');
245
+ return { success: true, created };
246
+ }
247
+ catch (error) {
248
+ return {
249
+ success: false,
250
+ created: false,
251
+ error: `Failed to update claude.json: ${error.message}`,
252
+ };
253
+ }
254
+ }
@@ -10,6 +10,8 @@ const os_1 = require("os");
10
10
  const CLAUDE_DIR = (0, path_1.join)((0, os_1.homedir)(), '.claude');
11
11
  const SETTINGS_FILE = (0, path_1.join)(CLAUDE_DIR, 'settings.json');
12
12
  const BACKUP_FILE = (0, path_1.join)(CLAUDE_DIR, 'settings.json.aar.bak');
13
+ // AAR 备份目录
14
+ const AAR_BACKUP_DIR = (0, path_1.join)((0, os_1.homedir)(), '.aar', 'backups');
13
15
  /**
14
16
  * 还原 Claude IDE 配置
15
17
  */
@@ -26,6 +28,11 @@ async function POST(request) {
26
28
  if (!(0, fs_1.existsSync)(CLAUDE_DIR)) {
27
29
  return server_1.NextResponse.json({ error: 'Claude config directory does not exist' }, { status: 500 });
28
30
  }
31
+ // 在还原前,先备份当前配置到 ~/.aar/backups 目录
32
+ const currentBackup = backupToAar(SETTINGS_FILE, 'settings-before-restore');
33
+ if (!currentBackup.success) {
34
+ console.warn('[IDE Restore] Current config backup warning:', currentBackup.error);
35
+ }
29
36
  // 将备份内容复制到原配置文件
30
37
  (0, fs_1.copyFileSync)(BACKUP_FILE, SETTINGS_FILE);
31
38
  // 尝试解析备份内容以验证格式
@@ -71,3 +78,26 @@ async function DELETE(request) {
71
78
  return server_1.NextResponse.json({ error: error.message || 'Internal server error', stack: process.env.NODE_ENV === 'development' ? error.stack : undefined }, { status: 500 });
72
79
  }
73
80
  }
81
+ /**
82
+ * 备份文件到 ~/.aar/backups 目录
83
+ */
84
+ function backupToAar(filePath, backupName) {
85
+ try {
86
+ if (!(0, fs_1.existsSync)(filePath)) {
87
+ return { success: true }; // 文件不存在,无需备份
88
+ }
89
+ if (!(0, fs_1.existsSync)(AAR_BACKUP_DIR)) {
90
+ (0, fs_1.mkdirSync)(AAR_BACKUP_DIR, { recursive: true });
91
+ }
92
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
93
+ const backupPath = (0, path_1.join)(AAR_BACKUP_DIR, `${backupName}-${timestamp}.json`);
94
+ (0, fs_1.copyFileSync)(filePath, backupPath);
95
+ return { success: true, backupPath };
96
+ }
97
+ catch (error) {
98
+ return {
99
+ success: false,
100
+ error: `Backup to AAR failed: ${error.message}`,
101
+ };
102
+ }
103
+ }
@@ -10,9 +10,13 @@ const queries_1 = require("@/db/queries");
10
10
  // Claude settings.json 文件路径
11
11
  const CLAUDE_DIR = (0, path_1.join)((0, os_1.homedir)(), '.claude');
12
12
  const SETTINGS_FILE = (0, path_1.join)(CLAUDE_DIR, 'settings.json');
13
+ // Claude 登录配置文件(用于 hasCompletedOnboarding)
14
+ const CLAUDE_JSON_FILE = (0, path_1.join)((0, os_1.homedir)(), '.claude.json');
13
15
  // AAR 临时配置目录和文件
14
16
  const AAR_DIR = (0, path_1.join)((0, os_1.homedir)(), '.aar');
15
17
  const TEMP_SETTINGS_FILE = (0, path_1.join)(AAR_DIR, 'settings.tmp.json');
18
+ // AAR 备份目录
19
+ const AAR_BACKUP_DIR = (0, path_1.join)(AAR_DIR, 'backups');
16
20
  /**
17
21
  * 保存 IDE 配置
18
22
  * - 如果当前已应用配置( ~/.claude/settings.json 存在且匹配当前网关),直接更新 settings.json
@@ -47,6 +51,15 @@ async function POST(request) {
47
51
  // 已应用当前网关配置,更新 settings.json
48
52
  savePath = SETTINGS_FILE;
49
53
  saveType = 'applied';
54
+ // 备份到 ~/.aar/backups 目录
55
+ const settingsBackup = backupToAar(SETTINGS_FILE, 'settings');
56
+ const claudeJsonBackup = backupToAar(CLAUDE_JSON_FILE, 'claude');
57
+ if (!settingsBackup.success) {
58
+ console.warn('[IDE Save] Settings backup warning:', settingsBackup.error);
59
+ }
60
+ if (!claudeJsonBackup.success) {
61
+ console.warn('[IDE Save] Claude.json backup warning:', claudeJsonBackup.error);
62
+ }
50
63
  mergedConfig = generateClaudeConfig(gatewayAddress, gatewayApiKey, {
51
64
  haiku: haiku,
52
65
  sonnet: sonnet,
@@ -54,6 +67,11 @@ async function POST(request) {
54
67
  default: defaultModel,
55
68
  reasoning: reasoningModel
56
69
  });
70
+ // 确保 ~/.claude.json 包含 hasCompletedOnboarding: true
71
+ const onboardingResult = ensureClaudeOnboarding();
72
+ if (!onboardingResult.success) {
73
+ console.warn('[IDE Save] Onboarding update warning:', onboardingResult.error);
74
+ }
57
75
  }
58
76
  else {
59
77
  // 未应用网络关配置,保存到临时文件 ~/.aar/settings.tmp.json
@@ -149,3 +167,64 @@ function generateClaudeConfig(gatewayAddress, gatewayApiKey, modelMapping) {
149
167
  },
150
168
  };
151
169
  }
170
+ /**
171
+ * 备份文件到 ~/.aar/backups 目录
172
+ */
173
+ function backupToAar(filePath, backupName) {
174
+ try {
175
+ if (!(0, fs_1.existsSync)(filePath)) {
176
+ return { success: true }; // 文件不存在,无需备份
177
+ }
178
+ if (!(0, fs_1.existsSync)(AAR_BACKUP_DIR)) {
179
+ (0, fs_1.mkdirSync)(AAR_BACKUP_DIR, { recursive: true });
180
+ }
181
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
182
+ const backupPath = (0, path_1.join)(AAR_BACKUP_DIR, `${backupName}-${timestamp}.json`);
183
+ (0, fs_1.copyFileSync)(filePath, backupPath);
184
+ return { success: true, backupPath };
185
+ }
186
+ catch (error) {
187
+ return {
188
+ success: false,
189
+ error: `Backup to AAR failed: ${error.message}`,
190
+ };
191
+ }
192
+ }
193
+ /**
194
+ * 确保 ~/.claude.json 文件中包含 hasCompletedOnboarding: true
195
+ * 这允许用户跳过 Claude 登录使用自定义模型
196
+ */
197
+ function ensureClaudeOnboarding() {
198
+ try {
199
+ let config = {};
200
+ let created = false;
201
+ if ((0, fs_1.existsSync)(CLAUDE_JSON_FILE)) {
202
+ try {
203
+ const content = (0, fs_1.readFileSync)(CLAUDE_JSON_FILE, 'utf-8');
204
+ config = JSON.parse(content);
205
+ }
206
+ catch {
207
+ // 文件存在但解析失败,使用空对象
208
+ config = {};
209
+ }
210
+ }
211
+ else {
212
+ created = true;
213
+ }
214
+ // 检查是否已经有 hasCompletedOnboarding
215
+ if (config.hasCompletedOnboarding === true) {
216
+ return { success: true, created: false };
217
+ }
218
+ // 添加 hasCompletedOnboarding
219
+ config.hasCompletedOnboarding = true;
220
+ (0, fs_1.writeFileSync)(CLAUDE_JSON_FILE, JSON.stringify(config, null, 2), 'utf-8');
221
+ return { success: true, created };
222
+ }
223
+ catch (error) {
224
+ return {
225
+ success: false,
226
+ created: false,
227
+ error: `Failed to update claude.json: ${error.message}`,
228
+ };
229
+ }
230
+ }
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.default = Footer;
7
+ const jsx_runtime_1 = require("react/jsx-runtime");
8
+ const package_json_1 = __importDefault(require("../../../package.json"));
9
+ function Footer() {
10
+ return ((0, jsx_runtime_1.jsx)("footer", { className: "fixed bottom-0 left-0 right-0 py-2 text-center text-xs text-gray-400 bg-gray-50 border-t border-gray-200", children: (0, jsx_runtime_1.jsxs)("span", { children: ["AAR v", package_json_1.default.version] }) }));
11
+ }
@@ -263,6 +263,66 @@ function IDEConfigPage() {
263
263
  document.addEventListener('mousedown', onDocClick);
264
264
  return () => document.removeEventListener('mousedown', onDocClick);
265
265
  }, [openClawDropdown]);
266
+ // 同步 Claude 下拉框默认值:确保状态值在可用模型列表中
267
+ (0, react_1.useEffect)(() => {
268
+ const allModels = Object.values(models).flat();
269
+ if (allModels.length === 0)
270
+ return;
271
+ const modelIds = allModels.map(m => m.model_id);
272
+ const firstModelId = allModels[0].model_id;
273
+ // 如果当前状态值不在可用模型列表中,更新为第一个可用模型
274
+ if (!modelIds.includes(haikuModel)) {
275
+ setHaikuModel(firstModelId);
276
+ }
277
+ if (!modelIds.includes(sonnetModel)) {
278
+ setSonnetModel(firstModelId);
279
+ }
280
+ if (!modelIds.includes(opusModel)) {
281
+ setOpusModel(firstModelId);
282
+ }
283
+ if (!modelIds.includes(defaultModel)) {
284
+ setDefaultModel(firstModelId);
285
+ }
286
+ if (!modelIds.includes(reasoningModel)) {
287
+ setReasoningModel(firstModelId);
288
+ }
289
+ }, [models]);
290
+ // 同步 OpenCode 下拉框默认值
291
+ (0, react_1.useEffect)(() => {
292
+ const allModels = Object.values(opencodeModels).flat();
293
+ if (allModels.length === 0)
294
+ return;
295
+ const modelIds = allModels.map(m => m.model_id);
296
+ const firstModelId = allModels[0].model_id;
297
+ if (!modelIds.includes(opencodeDefaultModel)) {
298
+ setOpencodeDefaultModel(firstModelId);
299
+ }
300
+ }, [opencodeModels]);
301
+ // 同步 OpenClaw 下拉框默认值
302
+ (0, react_1.useEffect)(() => {
303
+ const allModels = Object.values(openclawModels).flat();
304
+ if (allModels.length === 0)
305
+ return;
306
+ const modelIds = allModels.map(m => m.model_id);
307
+ const firstModelId = allModels[0].model_id;
308
+ if (!modelIds.includes(openclawDefaultModel)) {
309
+ setOpenclawDefaultModel(firstModelId);
310
+ }
311
+ // 过滤无效的 fallback 模型
312
+ const validFallbacks = openclawDefaultFallbacks.filter(id => modelIds.includes(id));
313
+ if (validFallbacks.length !== openclawDefaultFallbacks.length) {
314
+ setOpenclawDefaultFallbacks(validFallbacks);
315
+ }
316
+ // 检查 image model
317
+ if (openclawImageModel && !modelIds.includes(openclawImageModel)) {
318
+ setOpenclawImageModel('');
319
+ }
320
+ // 过滤无效的 image fallback 模型
321
+ const validImageFallbacks = openclawImageFallbacks.filter(id => modelIds.includes(id));
322
+ if (validImageFallbacks.length !== openclawImageFallbacks.length) {
323
+ setOpenclawImageFallbacks(validImageFallbacks);
324
+ }
325
+ }, [openclawModels]);
266
326
  // Claude Handlers
267
327
  const handleApply = async () => {
268
328
  setApplying(true);
@@ -645,7 +705,7 @@ function IDEConfigPage() {
645
705
  ? 'text-slate-900'
646
706
  : 'text-slate-400 hover:text-slate-600'}`, children: ["OpenCode", activeTab === 'opencode' && ((0, jsx_runtime_1.jsx)("span", { className: "absolute bottom-0 left-4 right-4 h-[2px] bg-slate-900 rounded-full" }))] }), (0, jsx_runtime_1.jsxs)("button", { onClick: () => setActiveTab('openclaw'), className: `relative px-4 py-3.5 text-sm font-medium transition-all duration-200 ${activeTab === 'openclaw'
647
707
  ? 'text-slate-900'
648
- : 'text-slate-400 hover:text-slate-600'}`, children: ["OpenClaw", activeTab === 'openclaw' && ((0, jsx_runtime_1.jsx)("span", { className: "absolute bottom-0 left-4 right-4 h-[2px] bg-slate-900 rounded-full" }))] }), (0, jsx_runtime_1.jsx)("button", { onClick: () => setActiveTab('cursor'), disabled: true, className: "relative px-4 py-3.5 text-sm font-medium text-slate-300 disabled:cursor-not-allowed", children: (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-2", children: ["Cursor", (0, jsx_runtime_1.jsx)("span", { className: "px-2 py-0.5 text-[10px] font-medium text-slate-400 bg-slate-100/60 rounded-full border border-slate-200/50", children: "\u5373\u5C06\u63A8\u51FA" })] }) })] }) }), (0, jsx_runtime_1.jsxs)("div", { className: "p-5 sm:p-6", children: [activeTab === 'claude' && ((0, jsx_runtime_1.jsxs)("div", { className: "space-y-6", children: [status && ((0, jsx_runtime_1.jsxs)("div", { className: "flex items-center justify-between py-3 px-4 bg-white/60 rounded-lg border border-slate-200/50", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-3", children: [(0, jsx_runtime_1.jsx)("div", { className: `w-6 h-6 rounded-full flex items-center justify-center ${status.applied
708
+ : 'text-slate-400 hover:text-slate-600'}`, children: ["OpenClaw", activeTab === 'openclaw' && ((0, jsx_runtime_1.jsx)("span", { className: "absolute bottom-0 left-4 right-4 h-[2px] bg-slate-900 rounded-full" }))] }), (0, jsx_runtime_1.jsx)("button", { onClick: () => setActiveTab('picoclaw'), disabled: true, className: "relative px-4 py-3.5 text-sm font-medium text-slate-300 disabled:cursor-not-allowed", children: (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-2", children: ["PicoClaw", (0, jsx_runtime_1.jsx)("span", { className: "px-2 py-0.5 text-[10px] font-medium text-slate-400 bg-slate-100/60 rounded-full border border-slate-200/50", children: "\u5373\u5C06\u63A8\u51FA" })] }) })] }) }), (0, jsx_runtime_1.jsxs)("div", { className: "p-5 sm:p-6", children: [activeTab === 'claude' && ((0, jsx_runtime_1.jsxs)("div", { className: "space-y-6", children: [status && ((0, jsx_runtime_1.jsxs)("div", { className: "flex items-center justify-between py-3 px-4 bg-white/60 rounded-lg border border-slate-200/50", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-3", children: [(0, jsx_runtime_1.jsx)("div", { className: `w-6 h-6 rounded-full flex items-center justify-center ${status.applied
649
709
  ? 'bg-emerald-100 text-emerald-600'
650
710
  : 'bg-slate-100 text-slate-400'}`, children: status.applied ? ((0, jsx_runtime_1.jsx)("svg", { className: "w-3.5 h-3.5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: (0, jsx_runtime_1.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2.5, d: "M5 13l4 4L19 7" }) })) : ((0, jsx_runtime_1.jsx)("svg", { className: "w-3.5 h-3.5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: (0, jsx_runtime_1.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M12 9v2m0 4h.01" }) })) }), (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-2.5", children: [(0, jsx_runtime_1.jsx)("span", { className: "text-sm font-medium text-slate-700", children: status.applied ? '已配置' : '未配置' }), status.applied && status.routerProvider === 'aar' && status.matchCurrentGateway !== undefined && ((0, jsx_runtime_1.jsx)("span", { className: `inline-flex items-center px-2 py-0.5 text-[10px] font-medium rounded-full border ${status.matchCurrentGateway
651
711
  ? 'bg-emerald-50/80 text-emerald-700 border-emerald-200/60'
@@ -794,5 +854,5 @@ function IDEConfigPage() {
794
854
  ? 'bg-white text-slate-800 font-semibold shadow-sm'
795
855
  : 'text-slate-500 font-medium hover:bg-slate-200/80 hover:text-slate-600'}`, children: "\u9884\u89C8" }), (0, jsx_runtime_1.jsx)("button", { type: "button", onClick: () => setOpenclawConfigSubTab('current'), className: `flex-1 px-4 py-2.5 text-[11px] transition-all ${openclawConfigSubTab === 'current'
796
856
  ? 'bg-white text-slate-800 font-semibold shadow-sm'
797
- : 'text-slate-500 font-medium hover:bg-slate-200/80 hover:text-slate-600'}`, children: "\u751F\u6548\u4E2D" })] }), (0, jsx_runtime_1.jsx)("div", { className: "relative overflow-hidden rounded-b-lg bg-slate-950 border border-slate-200 border-t-0", children: openclawConfigSubTab === 'preview' ? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-1.5 px-3 py-2 bg-slate-900 border-b border-slate-800", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex gap-1.5", children: [(0, jsx_runtime_1.jsx)("div", { className: "w-2.5 h-2.5 rounded-full bg-slate-600" }), (0, jsx_runtime_1.jsx)("div", { className: "w-2.5 h-2.5 rounded-full bg-slate-600" }), (0, jsx_runtime_1.jsx)("div", { className: "w-2.5 h-2.5 rounded-full bg-slate-600" })] }), (0, jsx_runtime_1.jsx)("span", { className: "text-[10px] font-medium text-slate-400 ml-2", children: "openclaw.json\uFF08\u5E94\u7528\u540E\u5C06\u5199\u5165\u7684\u5408\u5E76\u7ED3\u679C\uFF09" })] }), openclawPreviewLoading ? ((0, jsx_runtime_1.jsx)("div", { className: "p-3.5 text-[10px] text-slate-400 font-mono", children: "\u52A0\u8F7D\u4E2D..." })) : ((0, jsx_runtime_1.jsx)("textarea", { className: "openclaw-preview-textarea w-full min-h-[12rem] p-3.5 text-[10px] text-slate-300 font-mono bg-slate-950 border-0 focus:ring-0 focus:ring-offset-0 focus:outline-none resize-y max-h-96 overflow-y-auto placeholder:text-slate-500\n [&::-webkit-scrollbar]:w-2 [&::-webkit-scrollbar-track]:bg-slate-900 [&::-webkit-scrollbar-thumb]:bg-slate-600 [&::-webkit-scrollbar-thumb]:rounded-full", placeholder: "\u6682\u65E0\u9884\u89C8", spellCheck: false, value: openclawPreviewText, onChange: (e) => setOpenclawPreviewText(e.target.value) }))] })) : ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-1.5 px-3 py-2 bg-slate-900 border-b border-slate-800", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex gap-1.5", children: [(0, jsx_runtime_1.jsx)("div", { className: "w-2.5 h-2.5 rounded-full bg-slate-600" }), (0, jsx_runtime_1.jsx)("div", { className: "w-2.5 h-2.5 rounded-full bg-slate-600" }), (0, jsx_runtime_1.jsx)("div", { className: "w-2.5 h-2.5 rounded-full bg-slate-600" })] }), (0, jsx_runtime_1.jsx)("span", { className: "text-[10px] font-medium text-slate-400 ml-2", children: "openclaw.json\uFF08\u5F53\u524D\u78C1\u76D8\u5185\u5BB9\uFF0C\u8FD8\u539F\u540E\u5373\u66F4\u65B0\uFF09" })] }), openclawStatus?.config != null ? ((0, jsx_runtime_1.jsx)("pre", { className: "w-full min-h-[12rem] p-3.5 text-[10px] text-slate-300 font-mono overflow-auto max-h-96 [&::-webkit-scrollbar]:w-2 [&::-webkit-scrollbar-track]:bg-slate-900 [&::-webkit-scrollbar-thumb]:bg-slate-600 [&::-webkit-scrollbar-thumb]:rounded-full", children: JSON.stringify(openclawStatus.config, null, 2) })) : ((0, jsx_runtime_1.jsx)("div", { className: "p-3.5 text-[10px] text-slate-500 font-mono", children: "\u6682\u65E0\u914D\u7F6E\u6587\u4EF6" }))] })) })] })] }), (0, jsx_runtime_1.jsxs)("div", { className: "rounded-lg border border-slate-200/50 bg-slate-50/60 px-4 py-3", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-2 mb-2.5", children: [(0, jsx_runtime_1.jsx)("svg", { className: "w-4 h-4 text-slate-600", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: (0, jsx_runtime_1.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" }) }), (0, jsx_runtime_1.jsx)("h3", { className: "text-xs font-semibold text-slate-700", children: "\u4F7F\u7528\u8BF4\u660E" })] }), (0, jsx_runtime_1.jsxs)("ul", { className: "space-y-1.5 text-[10px] text-slate-600", children: [(0, jsx_runtime_1.jsxs)("li", { className: "flex gap-2", children: [(0, jsx_runtime_1.jsx)("span", { className: "text-slate-400", children: "1." }), (0, jsx_runtime_1.jsx)("span", { children: "\u300C\u751F\u6548\u4E2D\u300D\u4E3A\u5F53\u524D\u78C1\u76D8\u4E0A\u7684 openclaw.json\uFF1B\u300C\u9884\u89C8\u300D\u4E3A\u5E94\u7528\u540E\u5C06\u5199\u5165\u7684\u5408\u5E76\u7ED3\u679C\uFF08\u542B AAR \u7F51\u5173\uFF09" })] }), (0, jsx_runtime_1.jsxs)("li", { className: "flex gap-2", children: [(0, jsx_runtime_1.jsx)("span", { className: "text-slate-400", children: "2." }), (0, jsx_runtime_1.jsx)("span", { children: "\u9009\u62E9\u9ED8\u8BA4\u6A21\u578B\u7B49\u540E\uFF0C\u5728\u300C\u9884\u89C8\u300D\u4E2D\u67E5\u770B\u5408\u5E76\u7ED3\u679C\uFF0C\u70B9\u51FB\u300C\u5E94\u7528\u914D\u7F6E\u300D\u5199\u5165" })] }), (0, jsx_runtime_1.jsxs)("li", { className: "flex gap-2", children: [(0, jsx_runtime_1.jsx)("span", { className: "text-slate-400", children: "3." }), (0, jsx_runtime_1.jsx)("span", { children: "\u914D\u7F6E\u6587\u4EF6\u4F4D\u4E8E ~/.openclaw/openclaw.json" })] }), (0, jsx_runtime_1.jsxs)("li", { className: "flex gap-2", children: [(0, jsx_runtime_1.jsx)("span", { className: "text-slate-400", children: "4." }), (0, jsx_runtime_1.jsx)("span", { children: "\u300C\u8FD8\u539F\u914D\u7F6E\u300D\u6062\u590D\u4E3A\u5E94\u7528\u524D\u7684\u5907\u4EFD\uFF1B\u8FD8\u539F\u540E\u8BF7\u5728\u300C\u751F\u6548\u4E2D\u300D\u67E5\u770B\u5F53\u524D\u5185\u5BB9" })] })] })] })] })), activeTab === 'cursor' && ((0, jsx_runtime_1.jsxs)("div", { className: "flex flex-col items-center justify-center py-12 text-center", children: [(0, jsx_runtime_1.jsx)("div", { className: "w-16 h-16 bg-slate-100 rounded-full flex items-center justify-center mb-4", children: (0, jsx_runtime_1.jsx)("svg", { className: "w-8 h-8 text-slate-400", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: (0, jsx_runtime_1.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 1.5, d: "M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4" }) }) }), (0, jsx_runtime_1.jsx)("h3", { className: "text-sm font-medium text-slate-900 mb-1", children: "Cursor \u652F\u6301\u5373\u5C06\u63A8\u51FA" }), (0, jsx_runtime_1.jsx)("p", { className: "text-xs text-slate-500 max-w-xs", children: "\u6211\u4EEC\u5C06\u5F88\u5FEB\u6DFB\u52A0\u5BF9 Cursor \u7F16\u8F91\u5668\u7684\u652F\u6301\uFF0C\u656C\u8BF7\u671F\u5F85\u3002" })] }))] })] })] })] }));
857
+ : 'text-slate-500 font-medium hover:bg-slate-200/80 hover:text-slate-600'}`, children: "\u751F\u6548\u4E2D" })] }), (0, jsx_runtime_1.jsx)("div", { className: "relative overflow-hidden rounded-b-lg bg-slate-950 border border-slate-200 border-t-0", children: openclawConfigSubTab === 'preview' ? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-1.5 px-3 py-2 bg-slate-900 border-b border-slate-800", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex gap-1.5", children: [(0, jsx_runtime_1.jsx)("div", { className: "w-2.5 h-2.5 rounded-full bg-slate-600" }), (0, jsx_runtime_1.jsx)("div", { className: "w-2.5 h-2.5 rounded-full bg-slate-600" }), (0, jsx_runtime_1.jsx)("div", { className: "w-2.5 h-2.5 rounded-full bg-slate-600" })] }), (0, jsx_runtime_1.jsx)("span", { className: "text-[10px] font-medium text-slate-400 ml-2", children: "openclaw.json\uFF08\u5E94\u7528\u540E\u5C06\u5199\u5165\u7684\u5408\u5E76\u7ED3\u679C\uFF09" })] }), openclawPreviewLoading ? ((0, jsx_runtime_1.jsx)("div", { className: "p-3.5 text-[10px] text-slate-400 font-mono", children: "\u52A0\u8F7D\u4E2D..." })) : ((0, jsx_runtime_1.jsx)("textarea", { className: "openclaw-preview-textarea w-full min-h-[12rem] p-3.5 text-[10px] text-slate-300 font-mono bg-slate-950 border-0 focus:ring-0 focus:ring-offset-0 focus:outline-none resize-y max-h-96 overflow-y-auto placeholder:text-slate-500\n [&::-webkit-scrollbar]:w-2 [&::-webkit-scrollbar-track]:bg-slate-900 [&::-webkit-scrollbar-thumb]:bg-slate-600 [&::-webkit-scrollbar-thumb]:rounded-full", placeholder: "\u6682\u65E0\u9884\u89C8", spellCheck: false, value: openclawPreviewText, onChange: (e) => setOpenclawPreviewText(e.target.value) }))] })) : ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-1.5 px-3 py-2 bg-slate-900 border-b border-slate-800", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex gap-1.5", children: [(0, jsx_runtime_1.jsx)("div", { className: "w-2.5 h-2.5 rounded-full bg-slate-600" }), (0, jsx_runtime_1.jsx)("div", { className: "w-2.5 h-2.5 rounded-full bg-slate-600" }), (0, jsx_runtime_1.jsx)("div", { className: "w-2.5 h-2.5 rounded-full bg-slate-600" })] }), (0, jsx_runtime_1.jsx)("span", { className: "text-[10px] font-medium text-slate-400 ml-2", children: "openclaw.json\uFF08\u5F53\u524D\u78C1\u76D8\u5185\u5BB9\uFF0C\u8FD8\u539F\u540E\u5373\u66F4\u65B0\uFF09" })] }), openclawStatus?.config != null ? ((0, jsx_runtime_1.jsx)("pre", { className: "w-full min-h-[12rem] p-3.5 text-[10px] text-slate-300 font-mono overflow-auto max-h-96 [&::-webkit-scrollbar]:w-2 [&::-webkit-scrollbar-track]:bg-slate-900 [&::-webkit-scrollbar-thumb]:bg-slate-600 [&::-webkit-scrollbar-thumb]:rounded-full", children: JSON.stringify(openclawStatus.config, null, 2) })) : ((0, jsx_runtime_1.jsx)("div", { className: "p-3.5 text-[10px] text-slate-500 font-mono", children: "\u6682\u65E0\u914D\u7F6E\u6587\u4EF6" }))] })) })] })] }), (0, jsx_runtime_1.jsxs)("div", { className: "rounded-lg border border-slate-200/50 bg-slate-50/60 px-4 py-3", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-2 mb-2.5", children: [(0, jsx_runtime_1.jsx)("svg", { className: "w-4 h-4 text-slate-600", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: (0, jsx_runtime_1.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" }) }), (0, jsx_runtime_1.jsx)("h3", { className: "text-xs font-semibold text-slate-700", children: "\u4F7F\u7528\u8BF4\u660E" })] }), (0, jsx_runtime_1.jsxs)("ul", { className: "space-y-1.5 text-[10px] text-slate-600", children: [(0, jsx_runtime_1.jsxs)("li", { className: "flex gap-2", children: [(0, jsx_runtime_1.jsx)("span", { className: "text-slate-400", children: "1." }), (0, jsx_runtime_1.jsx)("span", { children: "\u300C\u751F\u6548\u4E2D\u300D\u4E3A\u5F53\u524D\u78C1\u76D8\u4E0A\u7684 openclaw.json\uFF1B\u300C\u9884\u89C8\u300D\u4E3A\u5E94\u7528\u540E\u5C06\u5199\u5165\u7684\u5408\u5E76\u7ED3\u679C\uFF08\u542B AAR \u7F51\u5173\uFF09" })] }), (0, jsx_runtime_1.jsxs)("li", { className: "flex gap-2", children: [(0, jsx_runtime_1.jsx)("span", { className: "text-slate-400", children: "2." }), (0, jsx_runtime_1.jsx)("span", { children: "\u9009\u62E9\u9ED8\u8BA4\u6A21\u578B\u7B49\u540E\uFF0C\u5728\u300C\u9884\u89C8\u300D\u4E2D\u67E5\u770B\u5408\u5E76\u7ED3\u679C\uFF0C\u70B9\u51FB\u300C\u5E94\u7528\u914D\u7F6E\u300D\u5199\u5165" })] }), (0, jsx_runtime_1.jsxs)("li", { className: "flex gap-2", children: [(0, jsx_runtime_1.jsx)("span", { className: "text-slate-400", children: "3." }), (0, jsx_runtime_1.jsx)("span", { children: "\u914D\u7F6E\u6587\u4EF6\u4F4D\u4E8E ~/.openclaw/openclaw.json" })] }), (0, jsx_runtime_1.jsxs)("li", { className: "flex gap-2", children: [(0, jsx_runtime_1.jsx)("span", { className: "text-slate-400", children: "4." }), (0, jsx_runtime_1.jsx)("span", { children: "\u300C\u8FD8\u539F\u914D\u7F6E\u300D\u6062\u590D\u4E3A\u5E94\u7528\u524D\u7684\u5907\u4EFD\uFF1B\u8FD8\u539F\u540E\u8BF7\u5728\u300C\u751F\u6548\u4E2D\u300D\u67E5\u770B\u5F53\u524D\u5185\u5BB9" })] })] })] })] })), activeTab === 'picoclaw' && ((0, jsx_runtime_1.jsxs)("div", { className: "flex flex-col items-center justify-center py-12 text-center", children: [(0, jsx_runtime_1.jsx)("div", { className: "w-16 h-16 bg-slate-100 rounded-full flex items-center justify-center mb-4", children: (0, jsx_runtime_1.jsx)("svg", { className: "w-8 h-8 text-slate-400", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: (0, jsx_runtime_1.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 1.5, d: "M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4" }) }) }), (0, jsx_runtime_1.jsx)("h3", { className: "text-sm font-medium text-slate-900 mb-1", children: "PicoClaw \u652F\u6301\u5373\u5C06\u63A8\u51FA" }), (0, jsx_runtime_1.jsx)("p", { className: "text-xs text-slate-500 max-w-xs", children: "\u6211\u4EEC\u5C06\u5F88\u5FEB\u6DFB\u52A0\u5BF9 PicoClaw \u7684\u652F\u6301\uFF0C\u656C\u8BF7\u671F\u5F85\u3002" })] }))] })] })] })] }));
798
858
  }
@@ -1,14 +1,18 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  exports.metadata = void 0;
4
7
  exports.default = RootLayout;
5
8
  const jsx_runtime_1 = require("react/jsx-runtime");
6
9
  require("./globals.css");
7
10
  const ToastProvider_1 = require("./components/ToastProvider");
11
+ const Footer_1 = __importDefault(require("./components/Footer"));
8
12
  exports.metadata = {
9
13
  title: 'AAR - AI Agent Router',
10
14
  description: 'Unified API gateway for managing multiple AI model providers',
11
15
  };
12
16
  function RootLayout({ children, }) {
13
- return ((0, jsx_runtime_1.jsx)("html", { lang: "zh-CN", suppressHydrationWarning: true, children: (0, jsx_runtime_1.jsx)("body", { children: (0, jsx_runtime_1.jsx)(ToastProvider_1.ToastProvider, { children: children }) }) }));
17
+ return ((0, jsx_runtime_1.jsx)("html", { lang: "zh-CN", suppressHydrationWarning: true, children: (0, jsx_runtime_1.jsxs)("body", { className: "pb-10", children: [(0, jsx_runtime_1.jsx)(ToastProvider_1.ToastProvider, { children: children }), (0, jsx_runtime_1.jsx)(Footer_1.default, {})] }) }));
14
18
  }
File without changes
@@ -14,7 +14,14 @@ const schema_1 = require("./schema");
14
14
  const path_1 = __importDefault(require("path"));
15
15
  const fs_1 = __importDefault(require("fs"));
16
16
  const os_1 = __importDefault(require("os"));
17
+ const crypto_1 = __importDefault(require("crypto"));
17
18
  const DB_PATH = process.env.DB_PATH || path_1.default.join(os_1.default.homedir(), '.aar', 'gateway.db');
19
+ /**
20
+ * 生成随机的 API Key(用于网关密码默认值)
21
+ */
22
+ function generateRandomApiKey() {
23
+ return `aar-${crypto_1.default.randomBytes(16).toString('hex')}`;
24
+ }
18
25
  // Use hardcoded path for the built/distributed WASM file
19
26
  // Next.js copies sql-wasm.wasm to its chunks directory
20
27
  function findSqlJsWasmPath() {
@@ -104,6 +111,7 @@ async function getDatabase() {
104
111
  if (!dbInstance) {
105
112
  const engine = await initSqlJsEngine();
106
113
  const loaded = loadDatabase();
114
+ let isNewDatabase = false;
107
115
  if (loaded) {
108
116
  dbInstance = loaded;
109
117
  if (fs_1.default.existsSync(DB_PATH))
@@ -112,7 +120,9 @@ async function getDatabase() {
112
120
  else {
113
121
  dbInstance = new engine.Database();
114
122
  lastLoadMtimeMs = 0;
123
+ isNewDatabase = true; // 标记为新数据库
115
124
  }
125
+ let migrationPerformed = false;
116
126
  try {
117
127
  dbInstance.run(schema_1.CREATE_TABLES_SQL);
118
128
  // Migration: Allow NULL model_id in request_logs table
@@ -144,7 +154,7 @@ async function getDatabase() {
144
154
  dbInstance.run(`INSERT INTO request_logs_new SELECT * FROM request_logs;`);
145
155
  dbInstance.run(`DROP TABLE request_logs;`);
146
156
  dbInstance.run(`ALTER TABLE request_logs_new RENAME TO request_logs;`);
147
- saveDatabase(dbInstance);
157
+ migrationPerformed = true;
148
158
  console.log('[Database] Migration completed successfully');
149
159
  }
150
160
  }
@@ -157,7 +167,16 @@ async function getDatabase() {
157
167
  console.warn('[Database] Migration warning (non-critical):', migrationError.message);
158
168
  }
159
169
  }
160
- saveDatabase(dbInstance);
170
+ // 只在新数据库创建或执行了 migration 时才保存,避免覆盖现有数据
171
+ if (isNewDatabase || migrationPerformed) {
172
+ // 为新数据库初始化默认的 api_key
173
+ if (isNewDatabase) {
174
+ const defaultApiKey = generateRandomApiKey();
175
+ dbInstance.run(`INSERT OR IGNORE INTO config (key, value, updated_at) VALUES ('api_key', ?, datetime('now'))`, [defaultApiKey]);
176
+ console.log('[Database] Generated default API key for new database');
177
+ }
178
+ saveDatabase(dbInstance);
179
+ }
161
180
  }
162
181
  catch (schemaError) {
163
182
  if (!schemaError.message.includes('already exists') &&
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-agent-router",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "description": "A unified API gateway for managing multiple AI model providers (Anthropic, OpenAI, Gemini, etc.)",
5
5
  "main": "dist/src/cli/index.js",
6
6
  "bin": {
@@ -1,10 +1,18 @@
1
1
  import { NextRequest, NextResponse } from 'next/server';
2
2
  import { getDatabase } from '@/db/database';
3
3
  import { getConfig, setConfig, getAllConfig } from '@/db/queries';
4
+ import crypto from 'crypto';
4
5
 
5
6
  // Ensure Node.js runtime (required for SQLite)
6
7
  export const runtime = 'nodejs';
7
8
 
9
+ /**
10
+ * 生成随机的 API Key(用于网关密码默认值)
11
+ */
12
+ function generateRandomApiKey(): string {
13
+ return `aar-${crypto.randomBytes(16).toString('hex')}`;
14
+ }
15
+
8
16
  export async function GET(request: NextRequest) {
9
17
  try {
10
18
  // Initialize database on request (safer than module load)
@@ -25,6 +33,15 @@ export async function GET(request: NextRequest) {
25
33
  }
26
34
 
27
35
  const configs = await getAllConfig();
36
+
37
+ // 如果 api_key 为空,生成一个随机默认值
38
+ if (!configs.api_key) {
39
+ const defaultApiKey = generateRandomApiKey();
40
+ await setConfig('api_key', defaultApiKey);
41
+ configs.api_key = defaultApiKey;
42
+ console.log('[Config API] Generated default API key');
43
+ }
44
+
28
45
  return NextResponse.json(configs);
29
46
  } catch (error: any) {
30
47
  console.error('Config API error:', error);
@@ -10,6 +10,12 @@ const CLAUDE_DIR = join(homedir(), '.claude');
10
10
  const SETTINGS_FILE = join(CLAUDE_DIR, 'settings.json');
11
11
  const BACKUP_FILE = join(CLAUDE_DIR, 'settings.json.aar.bak');
12
12
 
13
+ // Claude 登录配置文件(用于 hasCompletedOnboarding)
14
+ const CLAUDE_JSON_FILE = join(homedir(), '.claude.json');
15
+
16
+ // AAR 备份目录
17
+ const AAR_BACKUP_DIR = join(homedir(), '.aar', 'backups');
18
+
13
19
  // 默认模型映射
14
20
  const DEFAULT_MODEL_MAPPING = {
15
21
  haiku: 'GLM-4.5-air',
@@ -41,6 +47,17 @@ export async function POST(request: NextRequest) {
41
47
  const modelMapping = { haiku, sonnet, opus, default: defaultModel, reasoning: reasoningModel };
42
48
  const aarEnv = buildAarEnv(gatewayAddress, gatewayApiKey, modelMapping);
43
49
 
50
+ // 在修改配置前,先备份到 ~/.aar/backups 目录
51
+ const settingsBackup = backupToAar(SETTINGS_FILE, 'settings');
52
+ const claudeJsonBackup = backupToAar(CLAUDE_JSON_FILE, 'claude');
53
+
54
+ if (!settingsBackup.success) {
55
+ console.warn('[Claude Apply] Settings backup warning:', settingsBackup.error);
56
+ }
57
+ if (!claudeJsonBackup.success) {
58
+ console.warn('[Claude Apply] Claude.json backup warning:', claudeJsonBackup.error);
59
+ }
60
+
44
61
  const backupResult = backupOriginalConfig();
45
62
  if (!backupResult.success) {
46
63
  return NextResponse.json(
@@ -77,12 +94,23 @@ export async function POST(request: NextRequest) {
77
94
  );
78
95
  }
79
96
 
97
+ // 处理 ~/.claude.json,确保包含 hasCompletedOnboarding: true
98
+ const onboardingResult = ensureClaudeOnboarding();
99
+ if (!onboardingResult.success) {
100
+ console.warn('[Claude Apply] Onboarding update warning:', onboardingResult.error);
101
+ }
102
+
80
103
  return NextResponse.json({
81
104
  success: true,
82
105
  message: 'Configuration applied successfully',
83
106
  config: configToWrite,
84
107
  backup: backupResult.existed ? 'Created backup' : 'No existing config to backup',
85
108
  previousWasFromAar: backupResult.existed && backupResult.isFromAar,
109
+ onboarding: onboardingResult.created ? 'Created ~/.claude.json' : 'Updated ~/.claude.json',
110
+ aarBackups: {
111
+ settings: settingsBackup.backupPath,
112
+ claudeJson: claudeJsonBackup.backupPath,
113
+ },
86
114
  });
87
115
  } catch (error: any) {
88
116
  console.error('IDE Apply API error:', error);
@@ -204,3 +232,69 @@ function validateAndWriteConfig(config: any): { success: boolean; error?: string
204
232
  };
205
233
  }
206
234
  }
235
+
236
+ /**
237
+ * 备份文件到 ~/.aar/backups 目录
238
+ */
239
+ function backupToAar(filePath: string, backupName: string): { success: boolean; backupPath?: string; error?: string } {
240
+ try {
241
+ if (!existsSync(filePath)) {
242
+ return { success: true }; // 文件不存在,无需备份
243
+ }
244
+
245
+ if (!existsSync(AAR_BACKUP_DIR)) {
246
+ mkdirSync(AAR_BACKUP_DIR, { recursive: true });
247
+ }
248
+
249
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
250
+ const backupPath = join(AAR_BACKUP_DIR, `${backupName}-${timestamp}.json`);
251
+ copyFileSync(filePath, backupPath);
252
+
253
+ return { success: true, backupPath };
254
+ } catch (error: any) {
255
+ return {
256
+ success: false,
257
+ error: `Backup to AAR failed: ${error.message}`,
258
+ };
259
+ }
260
+ }
261
+
262
+ /**
263
+ * 确保 ~/.claude.json 文件中包含 hasCompletedOnboarding: true
264
+ * 这允许用户跳过 Claude 登录使用自定义模型
265
+ */
266
+ function ensureClaudeOnboarding(): { success: boolean; created: boolean; error?: string } {
267
+ try {
268
+ let config: any = {};
269
+ let created = false;
270
+
271
+ if (existsSync(CLAUDE_JSON_FILE)) {
272
+ try {
273
+ const content = readFileSync(CLAUDE_JSON_FILE, 'utf-8');
274
+ config = JSON.parse(content);
275
+ } catch {
276
+ // 文件存在但解析失败,使用空对象
277
+ config = {};
278
+ }
279
+ } else {
280
+ created = true;
281
+ }
282
+
283
+ // 检查是否已经有 hasCompletedOnboarding
284
+ if (config.hasCompletedOnboarding === true) {
285
+ return { success: true, created: false };
286
+ }
287
+
288
+ // 添加 hasCompletedOnboarding
289
+ config.hasCompletedOnboarding = true;
290
+ writeFileSync(CLAUDE_JSON_FILE, JSON.stringify(config, null, 2), 'utf-8');
291
+
292
+ return { success: true, created };
293
+ } catch (error: any) {
294
+ return {
295
+ success: false,
296
+ created: false,
297
+ error: `Failed to update claude.json: ${error.message}`,
298
+ };
299
+ }
300
+ }