agentstudio 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.
Files changed (115) hide show
  1. package/.env +15 -0
  2. package/README.md +85 -0
  3. package/dist/bin/agentstudio.d.ts +3 -0
  4. package/dist/bin/agentstudio.d.ts.map +1 -0
  5. package/dist/bin/agentstudio.js +141 -0
  6. package/dist/bin/agentstudio.js.map +1 -0
  7. package/dist/index.d.ts +2 -0
  8. package/dist/index.d.ts.map +1 -0
  9. package/dist/index.js +87 -0
  10. package/dist/index.js.map +1 -0
  11. package/dist/middleware/auth.d.ts +7 -0
  12. package/dist/middleware/auth.d.ts.map +1 -0
  13. package/dist/middleware/auth.js +21 -0
  14. package/dist/middleware/auth.js.map +1 -0
  15. package/dist/routes/agents.d.ts +4 -0
  16. package/dist/routes/agents.d.ts.map +1 -0
  17. package/dist/routes/agents.js +804 -0
  18. package/dist/routes/agents.js.map +1 -0
  19. package/dist/routes/auth.d.ts +4 -0
  20. package/dist/routes/auth.d.ts.map +1 -0
  21. package/dist/routes/auth.js +60 -0
  22. package/dist/routes/auth.js.map +1 -0
  23. package/dist/routes/files.d.ts +4 -0
  24. package/dist/routes/files.d.ts.map +1 -0
  25. package/dist/routes/files.js +301 -0
  26. package/dist/routes/files.js.map +1 -0
  27. package/dist/routes/mcp.d.ts +4 -0
  28. package/dist/routes/mcp.d.ts.map +1 -0
  29. package/dist/routes/mcp.js +652 -0
  30. package/dist/routes/mcp.js.map +1 -0
  31. package/dist/routes/media.d.ts +5 -0
  32. package/dist/routes/media.d.ts.map +1 -0
  33. package/dist/routes/media.js +117 -0
  34. package/dist/routes/media.js.map +1 -0
  35. package/dist/routes/slides.d.ts +4 -0
  36. package/dist/routes/slides.d.ts.map +1 -0
  37. package/dist/routes/slides.js +146 -0
  38. package/dist/routes/slides.js.map +1 -0
  39. package/dist/services/claudeSession.d.ts +83 -0
  40. package/dist/services/claudeSession.d.ts.map +1 -0
  41. package/dist/services/claudeSession.js +255 -0
  42. package/dist/services/claudeSession.js.map +1 -0
  43. package/dist/services/messageQueue.d.ts +31 -0
  44. package/dist/services/messageQueue.d.ts.map +1 -0
  45. package/dist/services/messageQueue.js +67 -0
  46. package/dist/services/messageQueue.js.map +1 -0
  47. package/dist/services/sessionManager.d.ts +132 -0
  48. package/dist/services/sessionManager.d.ts.map +1 -0
  49. package/dist/services/sessionManager.js +439 -0
  50. package/dist/services/sessionManager.js.map +1 -0
  51. package/dist/types/claude-history.d.ts +48 -0
  52. package/dist/types/claude-history.d.ts.map +1 -0
  53. package/dist/types/claude-history.js +2 -0
  54. package/dist/types/claude-history.js.map +1 -0
  55. package/dist/types/claude-versions.d.ts +31 -0
  56. package/dist/types/claude-versions.d.ts.map +1 -0
  57. package/dist/types/claude-versions.js +2 -0
  58. package/dist/types/claude-versions.js.map +1 -0
  59. package/dist/types/commands.d.ts +32 -0
  60. package/dist/types/commands.d.ts.map +1 -0
  61. package/dist/types/commands.js +2 -0
  62. package/dist/types/commands.js.map +1 -0
  63. package/dist/types/index.d.ts +81 -0
  64. package/dist/types/index.d.ts.map +1 -0
  65. package/dist/types/index.js +150 -0
  66. package/dist/types/index.js.map +1 -0
  67. package/dist/types/subagents.d.ts +88 -0
  68. package/dist/types/subagents.d.ts.map +1 -0
  69. package/dist/types/subagents.js +2 -0
  70. package/dist/types/subagents.js.map +1 -0
  71. package/dist/utils/agentStorage.d.ts +19 -0
  72. package/dist/utils/agentStorage.d.ts.map +1 -0
  73. package/dist/utils/agentStorage.js +110 -0
  74. package/dist/utils/agentStorage.js.map +1 -0
  75. package/dist/utils/claudeVersionStorage.d.ts +33 -0
  76. package/dist/utils/claudeVersionStorage.d.ts.map +1 -0
  77. package/dist/utils/claudeVersionStorage.js +168 -0
  78. package/dist/utils/claudeVersionStorage.js.map +1 -0
  79. package/dist/utils/jwt.d.ts +15 -0
  80. package/dist/utils/jwt.d.ts.map +1 -0
  81. package/dist/utils/jwt.js +28 -0
  82. package/dist/utils/jwt.js.map +1 -0
  83. package/dist/utils/projectMetadataStorage.d.ts +21 -0
  84. package/dist/utils/projectMetadataStorage.d.ts.map +1 -0
  85. package/dist/utils/projectMetadataStorage.js +68 -0
  86. package/dist/utils/projectMetadataStorage.js.map +1 -0
  87. package/frontend/dist/index.html +86 -0
  88. package/package.json +66 -0
  89. package/src/bin/agentstudio.ts +161 -0
  90. package/src/index.ts +100 -0
  91. package/src/middleware/auth.ts +26 -0
  92. package/src/routes/agents.ts +885 -0
  93. package/src/routes/auth.ts +73 -0
  94. package/src/routes/commands.ts.bak +441 -0
  95. package/src/routes/files.ts +352 -0
  96. package/src/routes/mcp.ts +751 -0
  97. package/src/routes/media.ts +140 -0
  98. package/src/routes/projects.ts.bak +601 -0
  99. package/src/routes/sessions.ts.bak +809 -0
  100. package/src/routes/settings.ts.bak +718 -0
  101. package/src/routes/slides.ts +170 -0
  102. package/src/routes/subagents.ts.bak +364 -0
  103. package/src/services/claudeSession.ts +293 -0
  104. package/src/services/messageQueue.ts +71 -0
  105. package/src/services/sessionManager.ts +532 -0
  106. package/src/types/claude-history.ts +50 -0
  107. package/src/types/claude-versions.ts +33 -0
  108. package/src/types/commands.ts +35 -0
  109. package/src/types/index.ts +248 -0
  110. package/src/types/subagents.ts +106 -0
  111. package/src/utils/agentStorage.ts +126 -0
  112. package/src/utils/claudeVersionStorage.ts +199 -0
  113. package/src/utils/jwt.ts +36 -0
  114. package/src/utils/projectMetadataStorage.ts +86 -0
  115. package/tsconfig.json +26 -0
@@ -0,0 +1,718 @@
1
+ import express, { Router } from 'express';
2
+ import { readFile, writeFile } from 'fs/promises';
3
+ import { join } from 'path';
4
+ import { homedir } from 'os';
5
+ import { exec } from 'child_process';
6
+ import { promisify } from 'util';
7
+ import {
8
+ getAllVersions,
9
+ getDefaultVersionId,
10
+ setDefaultVersion,
11
+ createVersion,
12
+ updateVersion,
13
+ deleteVersion,
14
+ initializeSystemVersion
15
+ } from '../utils/claudeVersionStorage.js';
16
+ import { ClaudeVersionCreate, ClaudeVersionUpdate } from '../types/claude-versions.js';
17
+
18
+ const router: Router = express.Router();
19
+ const execAsync = promisify(exec);
20
+
21
+ // Package manager detection and utilities
22
+ const detectPackageManagers = async () => {
23
+ const managers = {
24
+ npm: false,
25
+ pnpm: false,
26
+ yarn: false
27
+ };
28
+
29
+ // Check npm
30
+ try {
31
+ await execAsync('npm --version');
32
+ managers.npm = true;
33
+ } catch (error) {
34
+ // npm not available
35
+ }
36
+
37
+ // Check pnpm
38
+ try {
39
+ await execAsync('pnpm --version');
40
+ managers.pnpm = true;
41
+ } catch (error) {
42
+ // pnpm not available
43
+ }
44
+
45
+ // Check yarn
46
+ try {
47
+ await execAsync('yarn --version');
48
+ managers.yarn = true;
49
+ } catch (error) {
50
+ // yarn not available
51
+ }
52
+
53
+ return managers;
54
+ };
55
+
56
+ // Detect which package manager installed Claude Code
57
+ const detectClaudeCodeInstallationSource = async () => {
58
+ try {
59
+ // Get Claude Code executable path
60
+ const { stdout: claudePath } = await execAsync('which claude');
61
+ if (!claudePath) return null;
62
+
63
+ const cleanPath = claudePath.trim();
64
+ console.log('Claude Code executable path:', cleanPath);
65
+
66
+ // Skip local node_modules paths - we want global installation
67
+ if (cleanPath.includes('node_modules/.bin')) {
68
+ console.log('Skipping local node_modules path, looking for global installation');
69
+
70
+ // Try to find global installation by checking PATH without local node_modules
71
+ try {
72
+ const { stdout: allClaudes } = await execAsync('which -a claude');
73
+ const claudes = allClaudes.trim().split('\n');
74
+
75
+ // Find the first non-local installation
76
+ for (const claudePathOption of claudes) {
77
+ if (!claudePathOption.includes('node_modules/.bin')) {
78
+ console.log('Found global Claude installation:', claudePathOption);
79
+ const globalPath = claudePathOption.trim();
80
+
81
+ // Check for pnpm patterns
82
+ if (globalPath.includes('/pnpm/') || globalPath.includes('pnpm')) {
83
+ console.log('Detected pnpm installation from global path');
84
+ return 'pnpm';
85
+ }
86
+
87
+ // Continue with other checks using this global path
88
+ const cleanGlobalPath = globalPath;
89
+
90
+ // Check for yarn patterns
91
+ if (cleanGlobalPath.includes('/yarn/') || cleanGlobalPath.includes('yarn')) {
92
+ console.log('Detected yarn installation from global path');
93
+ return 'yarn';
94
+ }
95
+
96
+ // Check npm patterns
97
+ try {
98
+ const { stdout: npmPrefix } = await execAsync('npm config get prefix');
99
+ if (cleanGlobalPath.startsWith(npmPrefix.trim())) {
100
+ console.log('Detected npm installation from global path');
101
+ return 'npm';
102
+ }
103
+ } catch (error) {
104
+ // Ignore npm prefix error
105
+ }
106
+
107
+ break; // Use the first non-local path found
108
+ }
109
+ }
110
+ } catch (error) {
111
+ console.log('Could not find alternative Claude installations');
112
+ }
113
+ }
114
+
115
+ // Check for pnpm patterns
116
+ if (cleanPath.includes('/pnpm/') || cleanPath.includes('pnpm')) {
117
+ console.log('Detected pnpm installation from path');
118
+ return 'pnpm';
119
+ }
120
+
121
+ // Check for yarn patterns
122
+ if (cleanPath.includes('/yarn/') || cleanPath.includes('yarn')) {
123
+ return 'yarn';
124
+ }
125
+
126
+ // Check npm patterns - could be in node_modules or npm prefix
127
+ try {
128
+ const { stdout: npmPrefix } = await execAsync('npm config get prefix');
129
+ if (cleanPath.startsWith(npmPrefix.trim())) {
130
+ return 'npm';
131
+ }
132
+ } catch (error) {
133
+ // Ignore npm prefix error
134
+ }
135
+
136
+ // Additional checks for npm global installation patterns
137
+ if (cleanPath.includes('/node_modules/.bin/') ||
138
+ cleanPath.includes('/npm/') ||
139
+ cleanPath.includes('/.npm/')) {
140
+ return 'npm';
141
+ }
142
+
143
+ // Fallback: check for the presence of Claude Code in different package managers' global directories
144
+ const managers = await detectPackageManagers();
145
+
146
+ // Check pnpm global directory
147
+ if (managers.pnpm) {
148
+ try {
149
+ const { stdout: pnpmRoot } = await execAsync('pnpm root -g');
150
+ const pnpmClaudePath = `${pnpmRoot.trim()}/@anthropic-ai/claude-code`;
151
+ await execAsync(`test -d "${pnpmClaudePath}"`);
152
+ return 'pnpm';
153
+ } catch (error) {
154
+ // Claude Code not found in pnpm global
155
+ }
156
+ }
157
+
158
+ // Check yarn global directory
159
+ if (managers.yarn) {
160
+ try {
161
+ const { stdout: yarnGlobalDir } = await execAsync('yarn global dir');
162
+ const yarnClaudePath = `${yarnGlobalDir.trim()}/node_modules/@anthropic-ai/claude-code`;
163
+ await execAsync(`test -d "${yarnClaudePath}"`);
164
+ return 'yarn';
165
+ } catch (error) {
166
+ // Claude Code not found in yarn global
167
+ }
168
+ }
169
+
170
+ // Check npm global directory
171
+ if (managers.npm) {
172
+ try {
173
+ const { stdout: npmPrefix } = await execAsync('npm config get prefix');
174
+ const npmClaudePath = `${npmPrefix.trim()}/lib/node_modules/@anthropic-ai/claude-code`;
175
+ await execAsync(`test -d "${npmClaudePath}"`);
176
+ return 'npm';
177
+ } catch (error) {
178
+ // Claude Code not found in npm global
179
+ }
180
+ }
181
+
182
+ return null;
183
+ } catch (error) {
184
+ console.error('Error detecting Claude Code installation source:', error);
185
+ return null;
186
+ }
187
+ };
188
+
189
+ // Get preferred package manager for global installs
190
+ const getPreferredPackageManager = async () => {
191
+ // First try to detect which package manager installed Claude Code
192
+ const claudeInstallSource = await detectClaudeCodeInstallationSource();
193
+ if (claudeInstallSource) {
194
+ return claudeInstallSource;
195
+ }
196
+
197
+ // Fallback to priority order if detection fails
198
+ const managers = await detectPackageManagers();
199
+ if (managers.pnpm) return 'pnpm';
200
+ if (managers.yarn) return 'yarn';
201
+ if (managers.npm) return 'npm';
202
+
203
+ return null;
204
+ };
205
+
206
+ // Get package manager version
207
+ const getPackageManagerVersion = async (manager: string) => {
208
+ try {
209
+ const { stdout } = await execAsync(`${manager} --version`);
210
+ return stdout.trim();
211
+ } catch (error) {
212
+ return 'Not found';
213
+ }
214
+ };
215
+
216
+ // Get user's home directory
217
+ const getUserHomeDir = () => homedir();
218
+ const getGlobalMemoryPath = () => join(getUserHomeDir(), '.claude', 'CLAUDE.md');
219
+
220
+ // GET /api/settings/global-memory - Read global memory file
221
+ router.get('/global-memory', async (req, res) => {
222
+ try {
223
+ const filePath = getGlobalMemoryPath();
224
+
225
+ try {
226
+ const content = await readFile(filePath, 'utf-8');
227
+ res.type('text/plain').send(content);
228
+ } catch (error: any) {
229
+ if (error.code === 'ENOENT') {
230
+ // File doesn't exist, return empty content
231
+ res.type('text/plain').send('');
232
+ } else {
233
+ throw error;
234
+ }
235
+ }
236
+ } catch (error) {
237
+ console.error('Error reading global memory:', error);
238
+ res.status(500).json({
239
+ error: 'Failed to read global memory',
240
+ message: error instanceof Error ? error.message : 'Unknown error'
241
+ });
242
+ }
243
+ });
244
+
245
+ // POST /api/settings/global-memory - Write global memory file
246
+ router.post('/global-memory', express.text({ type: 'text/plain' }), async (req, res) => {
247
+ try {
248
+ const content = req.body;
249
+
250
+ if (typeof content !== 'string') {
251
+ return res.status(400).json({
252
+ error: 'Invalid content type',
253
+ message: 'Content must be a string'
254
+ });
255
+ }
256
+
257
+ const filePath = getGlobalMemoryPath();
258
+
259
+ await writeFile(filePath, content, 'utf-8');
260
+
261
+ res.json({
262
+ success: true,
263
+ message: 'Global memory saved successfully',
264
+ filePath
265
+ });
266
+ } catch (error) {
267
+ console.error('Error writing global memory:', error);
268
+ res.status(500).json({
269
+ error: 'Failed to save global memory',
270
+ message: error instanceof Error ? error.message : 'Unknown error'
271
+ });
272
+ }
273
+ });
274
+
275
+ // GET /api/settings/global-memory/path - Get the path to the global memory file
276
+ router.get('/global-memory/path', (req, res) => {
277
+ try {
278
+ const filePath = getGlobalMemoryPath();
279
+ res.json({
280
+ path: filePath,
281
+ homeDir: getUserHomeDir()
282
+ });
283
+ } catch (error) {
284
+ console.error('Error getting global memory path:', error);
285
+ res.status(500).json({
286
+ error: 'Failed to get file path',
287
+ message: error instanceof Error ? error.message : 'Unknown error'
288
+ });
289
+ }
290
+ });
291
+
292
+ // GET /api/settings/versions - Get version information for Claude Code, Node.js, and package managers
293
+ router.get('/versions', async (req, res) => {
294
+ try {
295
+ const availableManagers = await detectPackageManagers();
296
+ const preferredManager = await getPreferredPackageManager();
297
+
298
+ const claudeInstallSource = await detectClaudeCodeInstallationSource();
299
+
300
+ const versions: any = {
301
+ nodejs: null,
302
+ packageManagers: {},
303
+ preferredManager: preferredManager,
304
+ claudeInstallSource: claudeInstallSource,
305
+ claudeCode: null,
306
+ lastChecked: new Date().toISOString()
307
+ };
308
+
309
+ // Get Node.js version
310
+ try {
311
+ const { stdout: nodeVersion } = await execAsync('node --version');
312
+ versions.nodejs = nodeVersion.trim();
313
+ } catch (error) {
314
+ console.error('Failed to get Node.js version:', error);
315
+ versions.nodejs = 'Not found';
316
+ }
317
+
318
+ // Get all available package manager versions
319
+ for (const [manager, available] of Object.entries(availableManagers)) {
320
+ if (available) {
321
+ versions.packageManagers[manager] = await getPackageManagerVersion(manager);
322
+ } else {
323
+ versions.packageManagers[manager] = 'Not installed';
324
+ }
325
+ }
326
+
327
+ // Get Claude Code version using the correct global path
328
+ try {
329
+ let claudeCommand = 'claude --version';
330
+
331
+ // If we detected a specific installation source, try to use the correct path
332
+ if (claudeInstallSource) {
333
+ try {
334
+ const { stdout: claudePath } = await execAsync('which claude');
335
+ if (claudePath && claudePath.includes('node_modules/.bin')) {
336
+ // We're getting the local version, try to find the global one
337
+ const { stdout: allClaudes } = await execAsync('which -a claude');
338
+ const claudes = allClaudes.trim().split('\n');
339
+
340
+ for (const claudePathOption of claudes) {
341
+ if (!claudePathOption.includes('node_modules/.bin')) {
342
+ claudeCommand = `"${claudePathOption.trim()}" --version`;
343
+ console.log('Using global Claude path for version:', claudePathOption.trim());
344
+ break;
345
+ }
346
+ }
347
+ }
348
+ } catch (error) {
349
+ // Fallback to default command
350
+ }
351
+ }
352
+
353
+ const { stdout: claudeVersion } = await execAsync(claudeCommand);
354
+ versions.claudeCode = claudeVersion.trim();
355
+ console.log('Claude Code version detected:', claudeVersion.trim());
356
+ } catch (error) {
357
+ console.error('Failed to get Claude Code version:', error);
358
+ versions.claudeCode = 'Not installed';
359
+ }
360
+
361
+ res.json(versions);
362
+ } catch (error) {
363
+ console.error('Error getting version information:', error);
364
+ res.status(500).json({
365
+ error: 'Failed to get version information',
366
+ message: error instanceof Error ? error.message : 'Unknown error'
367
+ });
368
+ }
369
+ });
370
+
371
+ // POST /api/settings/update-claude - Update Claude Code using preferred package manager
372
+ router.post('/update-claude', async (req, res) => {
373
+ try {
374
+ const preferredManager = await getPreferredPackageManager();
375
+
376
+ if (!preferredManager) {
377
+ return res.status(400).json({
378
+ success: false,
379
+ error: 'No package manager available',
380
+ message: 'Please install npm, pnpm, or yarn to update Claude Code'
381
+ });
382
+ }
383
+
384
+ // Different commands for different package managers
385
+ let updateCommand: string;
386
+ switch (preferredManager) {
387
+ case 'pnpm':
388
+ updateCommand = 'pnpm add -g @anthropic-ai/claude-code@latest';
389
+ break;
390
+ case 'yarn':
391
+ updateCommand = 'yarn global add @anthropic-ai/claude-code@latest';
392
+ break;
393
+ case 'npm':
394
+ default:
395
+ updateCommand = 'npm update -g @anthropic-ai/claude-code';
396
+ break;
397
+ }
398
+
399
+ console.log(`Updating Claude Code using ${preferredManager}: ${updateCommand}`);
400
+ const { stdout, stderr } = await execAsync(updateCommand);
401
+
402
+ res.json({
403
+ success: true,
404
+ message: `Claude Code update completed using ${preferredManager}`,
405
+ packageManager: preferredManager,
406
+ command: updateCommand,
407
+ output: stdout,
408
+ error: stderr || null
409
+ });
410
+ } catch (error: any) {
411
+ console.error('Failed to update Claude Code:', error);
412
+ res.status(500).json({
413
+ success: false,
414
+ error: 'Failed to update Claude Code',
415
+ message: error.message,
416
+ output: error.stdout || null,
417
+ stderr: error.stderr || null
418
+ });
419
+ }
420
+ });
421
+
422
+ // Claude 版本管理 API
423
+
424
+ // GET /api/settings/claude-versions - 获取所有 Claude 版本
425
+ router.get('/claude-versions', async (req, res) => {
426
+ try {
427
+ // 首先确保系统版本存在
428
+ try {
429
+ const { stdout: claudePath } = await execAsync('which claude');
430
+ if (claudePath) {
431
+ await initializeSystemVersion(claudePath.trim());
432
+ }
433
+ } catch (error) {
434
+ console.warn('No system claude found:', error);
435
+ }
436
+
437
+ const versions = await getAllVersions();
438
+ const defaultVersionId = await getDefaultVersionId();
439
+
440
+ res.json({
441
+ versions,
442
+ defaultVersionId
443
+ });
444
+ } catch (error) {
445
+ console.error('Error getting Claude versions:', error);
446
+ res.status(500).json({
447
+ error: 'Failed to get Claude versions',
448
+ message: error instanceof Error ? error.message : 'Unknown error'
449
+ });
450
+ }
451
+ });
452
+
453
+ // POST /api/settings/claude-versions - 创建新的 Claude 版本
454
+ router.post('/claude-versions', async (req, res) => {
455
+ try {
456
+ const data: ClaudeVersionCreate = req.body;
457
+
458
+ // 验证必填字段
459
+ if (!data.name || !data.alias) {
460
+ return res.status(400).json({
461
+ error: 'Missing required fields',
462
+ message: 'name and alias are required'
463
+ });
464
+ }
465
+
466
+ const version = await createVersion(data);
467
+ res.json(version);
468
+ } catch (error) {
469
+ console.error('Error creating Claude version:', error);
470
+ const status = error instanceof Error && error.message.includes('已存在') ? 409 : 500;
471
+ res.status(status).json({
472
+ error: 'Failed to create Claude version',
473
+ message: error instanceof Error ? error.message : 'Unknown error'
474
+ });
475
+ }
476
+ });
477
+
478
+ // PUT /api/settings/claude-versions/:id - 更新 Claude 版本
479
+ router.put('/claude-versions/:id', async (req, res) => {
480
+ try {
481
+ const { id } = req.params;
482
+ const data: ClaudeVersionUpdate = req.body;
483
+
484
+ const version = await updateVersion(id, data);
485
+ res.json(version);
486
+ } catch (error) {
487
+ console.error('Error updating Claude version:', error);
488
+ const status = error instanceof Error && (
489
+ error.message.includes('不存在') ||
490
+ error.message.includes('已存在') ||
491
+ error.message.includes('不允许')
492
+ ) ? 400 : 500;
493
+ res.status(status).json({
494
+ error: 'Failed to update Claude version',
495
+ message: error instanceof Error ? error.message : 'Unknown error'
496
+ });
497
+ }
498
+ });
499
+
500
+ // DELETE /api/settings/claude-versions/:id - 删除 Claude 版本
501
+ router.delete('/claude-versions/:id', async (req, res) => {
502
+ try {
503
+ const { id } = req.params;
504
+
505
+ await deleteVersion(id);
506
+ res.json({ success: true });
507
+ } catch (error) {
508
+ console.error('Error deleting Claude version:', error);
509
+ const status = error instanceof Error && (
510
+ error.message.includes('不存在') ||
511
+ error.message.includes('不允许')
512
+ ) ? 400 : 500;
513
+ res.status(status).json({
514
+ error: 'Failed to delete Claude version',
515
+ message: error instanceof Error ? error.message : 'Unknown error'
516
+ });
517
+ }
518
+ });
519
+
520
+ // PUT /api/settings/claude-versions/:id/set-default - 设置默认版本
521
+ router.put('/claude-versions/:id/set-default', async (req, res) => {
522
+ try {
523
+ const { id } = req.params;
524
+
525
+ await setDefaultVersion(id);
526
+ res.json({ success: true });
527
+ } catch (error) {
528
+ console.error('Error setting default Claude version:', error);
529
+ const status = error instanceof Error && error.message.includes('不存在') ? 400 : 500;
530
+ res.status(status).json({
531
+ error: 'Failed to set default Claude version',
532
+ message: error instanceof Error ? error.message : 'Unknown error'
533
+ });
534
+ }
535
+ });
536
+
537
+ // POST /api/settings/claude-versions/detect - 检测 Claude CLI 安装
538
+ router.post('/claude-versions/detect', async (req, res) => {
539
+ try {
540
+ const result = {
541
+ userInstalled: false,
542
+ systemInstalled: false,
543
+ userPath: null as string | null,
544
+ systemPath: null as string | null,
545
+ version: null as string | null,
546
+ packageManager: null as string | null
547
+ };
548
+
549
+ // 检测用户自己安装的 Claude CLI (全局安装)
550
+ try {
551
+ const { stdout: claudePath } = await execAsync('which claude');
552
+ if (claudePath) {
553
+ const cleanPath = claudePath.trim();
554
+
555
+ // 检测是否是系统 npm 包自带的 (在项目 node_modules 中)
556
+ const projectRoot = process.cwd();
557
+ const isSystemPackage = cleanPath.includes(`${projectRoot}/node_modules`);
558
+
559
+ if (isSystemPackage) {
560
+ result.systemInstalled = true;
561
+ result.systemPath = cleanPath;
562
+ } else {
563
+ result.userInstalled = true;
564
+ result.userPath = cleanPath;
565
+ }
566
+
567
+ // 获取版本
568
+ try {
569
+ const { stdout: version } = await execAsync(`"${cleanPath}" --version`);
570
+ result.version = version.trim();
571
+ } catch (error) {
572
+ console.error('Failed to get Claude version:', error);
573
+ }
574
+
575
+ // 检测包管理器
576
+ if (result.userInstalled) {
577
+ result.packageManager = await detectClaudeCodeInstallationSource();
578
+ }
579
+ }
580
+ } catch (error) {
581
+ console.log('No Claude CLI found in PATH');
582
+ }
583
+
584
+ // 如果没有找到全局安装的,检查系统 npm 包(在 backend/node_modules 中)
585
+ if (!result.systemInstalled) {
586
+ try {
587
+ const projectRoot = process.cwd();
588
+ // 在 workspace 项目中,backend 的依赖在 backend/node_modules
589
+ const systemClaudePath = join(projectRoot, 'backend', 'node_modules', '.bin', 'claude');
590
+
591
+ // 检查文件是否存在(.bin/claude 是符号链接,用 -e 而不是 -f)
592
+ const { stdout } = await execAsync(`test -e "${systemClaudePath}" && echo "exists"`);
593
+ if (stdout.trim() === 'exists') {
594
+ result.systemInstalled = true;
595
+ result.systemPath = systemClaudePath;
596
+
597
+ // 尝试获取版本
598
+ if (!result.version) {
599
+ try {
600
+ const { stdout: version } = await execAsync(`"${systemClaudePath}" --version`);
601
+ result.version = version.trim();
602
+ } catch (error) {
603
+ console.error('Failed to get system package version:', error);
604
+ }
605
+ }
606
+ }
607
+ } catch (error) {
608
+ console.log('System package not found:', error);
609
+ }
610
+ }
611
+
612
+ res.json(result);
613
+ } catch (error) {
614
+ console.error('Error detecting Claude CLI:', error);
615
+ res.status(500).json({
616
+ error: 'Failed to detect Claude CLI',
617
+ message: error instanceof Error ? error.message : 'Unknown error'
618
+ });
619
+ }
620
+ });
621
+
622
+ // POST /api/settings/claude-versions/init-system - 初始化系统版本
623
+ router.post('/claude-versions/init-system', async (req, res) => {
624
+ try {
625
+ const {
626
+ useUserInstalled,
627
+ authToken,
628
+ skipAuthToken
629
+ } = req.body;
630
+
631
+ let claudePath: string | null = null;
632
+
633
+ // 根据用户选择获取 Claude 路径
634
+ if (useUserInstalled) {
635
+ // 使用用户自己安装的
636
+ try {
637
+ const { stdout } = await execAsync('which claude');
638
+ const cleanPath = stdout.trim();
639
+ const projectRoot = process.cwd();
640
+
641
+ // 确保不是项目内的
642
+ if (!cleanPath.includes(`${projectRoot}/node_modules`)) {
643
+ claudePath = cleanPath;
644
+ } else {
645
+ return res.status(400).json({
646
+ error: 'Invalid selection',
647
+ message: '未找到用户全局安装的 Claude CLI'
648
+ });
649
+ }
650
+ } catch (error) {
651
+ return res.status(400).json({
652
+ error: 'Claude CLI not found',
653
+ message: '未找到用户安装的 Claude CLI'
654
+ });
655
+ }
656
+ } else {
657
+ // 使用系统 npm 包自带的(在 backend/node_modules 中)
658
+ try {
659
+ const projectRoot = process.cwd();
660
+ const systemClaudePath = join(projectRoot, 'backend', 'node_modules', '.bin', 'claude');
661
+ const { stdout } = await execAsync(`test -e "${systemClaudePath}" && echo "exists"`);
662
+
663
+ if (stdout.trim() === 'exists') {
664
+ claudePath = systemClaudePath;
665
+ } else {
666
+ return res.status(400).json({
667
+ error: 'System Claude not found',
668
+ message: '系统 npm 包中未找到 Claude CLI'
669
+ });
670
+ }
671
+ } catch (error) {
672
+ return res.status(400).json({
673
+ error: 'System Claude not found',
674
+ message: '系统 npm 包中未找到 Claude CLI'
675
+ });
676
+ }
677
+ }
678
+
679
+ if (!claudePath) {
680
+ return res.status(400).json({
681
+ error: 'No Claude CLI found',
682
+ message: '未找到可用的 Claude CLI'
683
+ });
684
+ }
685
+
686
+ // 准备环境变量
687
+ const environmentVariables: Record<string, string> = {};
688
+
689
+ if (authToken && !skipAuthToken) {
690
+ environmentVariables.ANTHROPIC_AUTH_TOKEN = authToken;
691
+ }
692
+
693
+ // 创建或更新系统版本
694
+ const storage = await import('../utils/claudeVersionStorage.js');
695
+ let systemVersion = await storage.initializeSystemVersion(claudePath);
696
+
697
+ // 更新环境变量
698
+ if (Object.keys(environmentVariables).length > 0) {
699
+ systemVersion = await storage.updateVersion(systemVersion.id, {
700
+ environmentVariables
701
+ });
702
+ }
703
+
704
+ res.json({
705
+ success: true,
706
+ version: systemVersion,
707
+ message: '系统版本初始化成功'
708
+ });
709
+ } catch (error) {
710
+ console.error('Error initializing system version:', error);
711
+ res.status(500).json({
712
+ error: 'Failed to initialize system version',
713
+ message: error instanceof Error ? error.message : 'Unknown error'
714
+ });
715
+ }
716
+ });
717
+
718
+ export default router;