stigmergy 1.2.13 → 1.3.2-beta.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 (48) hide show
  1. package/README.md +39 -3
  2. package/STIGMERGY.md +3 -0
  3. package/config/enhanced-cli-config.json +438 -0
  4. package/docs/CLI_TOOLS_AGENT_SKILL_ANALYSIS.md +463 -0
  5. package/docs/ENHANCED_CLI_AGENT_SKILL_CONFIG.md +285 -0
  6. package/docs/INSTALLER_ARCHITECTURE.md +257 -0
  7. package/docs/SUDO_PROBLEM_AND_SOLUTION.md +529 -0
  8. package/package.json +14 -5
  9. package/scripts/analyze-router.js +168 -0
  10. package/scripts/test-runner.js +344 -0
  11. package/src/cli/commands/autoinstall.js +158 -0
  12. package/src/cli/commands/errors.js +190 -0
  13. package/src/cli/commands/install.js +142 -0
  14. package/src/cli/commands/permissions.js +108 -0
  15. package/src/cli/commands/project.js +449 -0
  16. package/src/cli/commands/resume.js +136 -0
  17. package/src/cli/commands/scan.js +97 -0
  18. package/src/cli/commands/skills.js +158 -0
  19. package/src/cli/commands/status.js +106 -0
  20. package/src/cli/commands/system.js +301 -0
  21. package/src/cli/router-beta.js +477 -0
  22. package/src/cli/utils/environment.js +75 -0
  23. package/src/cli/utils/formatters.js +47 -0
  24. package/src/cli/utils/skills_cache.js +92 -0
  25. package/src/core/cache_cleaner.js +1 -0
  26. package/src/core/cli_adapters.js +345 -0
  27. package/src/core/cli_help_analyzer.js +473 -1
  28. package/src/core/cli_path_detector.js +2 -1
  29. package/src/core/cli_tools.js +107 -0
  30. package/src/core/coordination/nodejs/HookDeploymentManager.js +185 -422
  31. package/src/core/coordination/nodejs/HookDeploymentManager.refactored.js +323 -0
  32. package/src/core/coordination/nodejs/generators/CLIAdapterGenerator.js +363 -0
  33. package/src/core/coordination/nodejs/generators/ResumeSessionGenerator.js +701 -0
  34. package/src/core/coordination/nodejs/generators/SkillsIntegrationGenerator.js +1210 -0
  35. package/src/core/coordination/nodejs/generators/index.js +12 -0
  36. package/src/core/enhanced_cli_installer.js +220 -30
  37. package/src/core/enhanced_cli_parameter_handler.js +395 -0
  38. package/src/core/execution_mode_detector.js +222 -0
  39. package/src/core/installer.js +51 -70
  40. package/src/core/local_skill_scanner.js +732 -0
  41. package/src/core/multilingual/language-pattern-manager.js +1 -1
  42. package/src/core/skills/StigmergySkillManager.js +26 -8
  43. package/src/core/smart_router.js +279 -2
  44. package/src/index.js +10 -4
  45. package/test/cli-integration.test.js +304 -0
  46. package/test/enhanced-cli-agent-skill-test.js +485 -0
  47. package/test/specific-cli-agent-skill-analysis.js +385 -0
  48. package/src/cli/router.js +0 -1783
@@ -0,0 +1,701 @@
1
+ // ResumeSession Extension Generator
2
+ // 专门负责生成各CLI的ResumeSession扩展文件
3
+
4
+ const path = require('path');
5
+
6
+ class ResumeSessionGenerator {
7
+ constructor() {
8
+ this.supportedCLIs = [
9
+ 'claude', 'gemini', 'qwen', 'codebuddy', 'codex',
10
+ 'iflow', 'qodercli', 'copilot', 'kode'
11
+ ];
12
+ }
13
+
14
+ generateForCLI(cliName) {
15
+ const needsSlashPrefix = ['claude', 'codebuddy'].includes(cliName.toLowerCase());
16
+ const commandName = needsSlashPrefix ? '/stigmergy-resume' : 'stigmergy-resume';
17
+ const currentProjectPath = process.cwd();
18
+
19
+ return this.generateExtensionContent(cliName, commandName, currentProjectPath);
20
+ }
21
+
22
+ generateExtensionContent(cliName, commandName, projectPath) {
23
+ return `// ${cliName.charAt(0).toUpperCase() + cliName.slice(1)} CLI ResumeSession Integration
24
+ // Auto-generated by ResumeSession v1.0.4
25
+ // Project: ${projectPath}
26
+
27
+ const fs = require('fs');
28
+ const path = require('path');
29
+ const os = require('os');
30
+
31
+ // Load shared path configuration
32
+ const pathConfigLoader = require(path.join(__dirname, 'path-config-loader.js'));
33
+
34
+ // Embedded ResumeSession Core Functionality
35
+
36
+ class SessionScanner {
37
+ scanSessions(cliType, sessionsPath, projectPath) {
38
+ const sessions = [];
39
+ if (!sessionsPath || !projectPath) return sessions;
40
+
41
+ try {
42
+ if (!fs.existsSync(sessionsPath)) return sessions;
43
+
44
+ // For IFlow, Claude, QoderCLI, Kode: scan projects subdirectories (one level)
45
+ if ((cliType === 'iflow' || cliType === 'claude' || cliType === 'qodercli' || cliType === 'kode') && sessionsPath.includes('projects')) {
46
+ const subdirs = fs.readdirSync(sessionsPath);
47
+ for (const subdir of subdirs) {
48
+ const subdirPath = path.join(sessionsPath, subdir);
49
+ try {
50
+ const stat = fs.statSync(subdirPath);
51
+ if (stat.isDirectory()) {
52
+ sessions.push(...this.scanSessionFiles(cliType, subdirPath, projectPath));
53
+ }
54
+ } catch (error) {
55
+ continue;
56
+ }
57
+ }
58
+ return sessions;
59
+ }
60
+
61
+ // For Gemini: scan tmp/<hash>/chats subdirectories (multiple levels)
62
+ if (cliType === 'gemini' && sessionsPath.includes('tmp')) {
63
+ const hashDirs = fs.readdirSync(sessionsPath);
64
+ for (const hashDir of hashDirs) {
65
+ const hashDirPath = path.join(sessionsPath, hashDir);
66
+ try {
67
+ const stat = fs.statSync(hashDirPath);
68
+ if (stat.isDirectory()) {
69
+ const chatsPath = path.join(hashDirPath, 'chats');
70
+ if (fs.existsSync(chatsPath)) {
71
+ sessions.push(...this.scanSessionFiles(cliType, chatsPath, projectPath));
72
+ }
73
+ }
74
+ } catch (error) {
75
+ continue;
76
+ }
77
+ }
78
+ return sessions;
79
+ }
80
+
81
+ // For Qwen: scan projects/<projectName>/chats subdirectories (two levels)
82
+ if (cliType === 'qwen' && sessionsPath.includes('projects')) {
83
+ const projectDirs = fs.readdirSync(sessionsPath);
84
+ for (const projectDir of projectDirs) {
85
+ const projectDirPath = path.join(sessionsPath, projectDir);
86
+ try {
87
+ const stat = fs.statSync(projectDirPath);
88
+ if (stat.isDirectory()) {
89
+ const chatsPath = path.join(projectDirPath, 'chats');
90
+ if (fs.existsSync(chatsPath)) {
91
+ sessions.push(...this.scanSessionFiles(cliType, chatsPath, projectPath));
92
+ }
93
+ }
94
+ } catch (error) {
95
+ continue;
96
+ }
97
+ }
98
+ return sessions;
99
+ }
100
+
101
+ // For CodeBuddy: scan both projects subdirectories and root history.jsonl
102
+ if (cliType === 'codebuddy') {
103
+ const projectsPath = path.join(sessionsPath, 'projects');
104
+ if (fs.existsSync(projectsPath)) {
105
+ const projectDirs = fs.readdirSync(projectsPath);
106
+ for (const projectDir of projectDirs) {
107
+ const projectDirPath = path.join(projectsPath, projectDir);
108
+ try {
109
+ const stat = fs.statSync(projectDirPath);
110
+ if (stat.isDirectory()) {
111
+ sessions.push(...this.scanSessionFiles(cliType, projectDirPath, projectPath));
112
+ }
113
+ } catch (error) {
114
+ continue;
115
+ }
116
+ }
117
+ }
118
+ sessions.push(...this.scanSessionFiles(cliType, sessionsPath, projectPath));
119
+ return sessions;
120
+ }
121
+
122
+ return this.scanSessionFiles(cliType, sessionsPath, projectPath);
123
+ } catch (error) {
124
+ console.warn(\`Warning: Could not scan \${cliType} sessions:\`, error.message);
125
+ }
126
+
127
+ return sessions;
128
+ }
129
+
130
+ scanSessionFiles(cliType, sessionsPath, projectPath) {
131
+ const sessions = [];
132
+ try {
133
+ const files = fs.readdirSync(sessionsPath);
134
+ for (const file of files) {
135
+ if (file.endsWith('.json') || file.endsWith('.session') || file.endsWith('.jsonl')) {
136
+ try {
137
+ const filePath = path.join(sessionsPath, file);
138
+ let sessionData;
139
+
140
+ if (file.endsWith('.jsonl')) {
141
+ const content = fs.readFileSync(filePath, 'utf8');
142
+ const lines = content.trim().split('\\n').filter(line => line.trim());
143
+ const messages = lines.map(line => JSON.parse(line));
144
+
145
+ if (messages.length === 0) continue;
146
+ sessionData = this.parseJSONLSession(messages, file);
147
+ } else {
148
+ const content = fs.readFileSync(filePath, 'utf8');
149
+ sessionData = JSON.parse(content);
150
+ }
151
+
152
+ if (this.isProjectSession(sessionData, projectPath)) {
153
+ sessions.push({
154
+ cliType,
155
+ sessionId: sessionData.id || sessionData.sessionId || file.replace(/\\.(json|session|jsonl)$/, ''),
156
+ title: sessionData.title || sessionData.topic || 'Untitled',
157
+ content: this.extractContent(sessionData),
158
+ updatedAt: new Date(sessionData.updatedAt || sessionData.timestamp || fs.statSync(filePath).mtime),
159
+ messageCount: sessionData.messageCount || this.countMessages(sessionData),
160
+ projectPath
161
+ });
162
+ }
163
+ } catch (error) {
164
+ console.warn(\`Warning: Could not parse \${file}:\`, error.message);
165
+ }
166
+ }
167
+ }
168
+ } catch (error) {
169
+ console.warn('Warning: Could not scan files:', error.message);
170
+ }
171
+
172
+ return sessions;
173
+ }
174
+
175
+ parseJSONLSession(messages, filename) {
176
+ const firstMsg = messages[0];
177
+ const lastMsg = messages[messages.length - 1];
178
+ const userMessages = messages.filter(m => m.type === 'user' || m.role === 'user');
179
+
180
+ let title = 'Untitled Session';
181
+ if (userMessages.length > 0) {
182
+ const firstUserMsg = userMessages[0];
183
+ let content = firstUserMsg.message?.content || firstUserMsg.content || '';
184
+ if (typeof content === 'object') {
185
+ content = JSON.stringify(content);
186
+ }
187
+ if (typeof content === 'string' && content.trim()) {
188
+ title = content.substring(0, 100) || title;
189
+ }
190
+ }
191
+
192
+ const contentParts = messages
193
+ .map(m => {
194
+ if (m.message && typeof m.message === 'object') {
195
+ return m.message.content || m.message.text || '';
196
+ }
197
+ return m.content || m.text || '';
198
+ })
199
+ .filter(text => text && typeof text === 'string' && text.trim());
200
+
201
+ return {
202
+ sessionId: firstMsg.sessionId || filename.replace('.jsonl', ''),
203
+ title: title,
204
+ content: contentParts.join(' '),
205
+ timestamp: lastMsg.timestamp || new Date().toISOString(),
206
+ projectPath: firstMsg.cwd || firstMsg.workingDirectory,
207
+ messageCount: messages.filter(m => m.type === 'user' || m.type === 'assistant' || m.role === 'user' || m.role === 'assistant').length,
208
+ messages: messages
209
+ };
210
+ }
211
+
212
+ scanAllCLISessions(projectPath) {
213
+ const allSessions = [];
214
+ const cliPathsMap = pathConfigLoader.getAllCLISessionPaths();
215
+
216
+ for (const [cliType, sessionsPaths] of Object.entries(cliPathsMap)) {
217
+ for (const sessionsPath of sessionsPaths) {
218
+ const sessions = this.scanSessions(cliType, sessionsPath, projectPath);
219
+ allSessions.push(...sessions);
220
+ }
221
+ }
222
+
223
+ return allSessions;
224
+ }
225
+
226
+ isProjectSession(session, projectPath) {
227
+ const sessionProject = session.projectPath || session.workingDirectory;
228
+ if (!sessionProject) return true;
229
+
230
+ return sessionProject === projectPath ||
231
+ sessionProject.startsWith(projectPath) ||
232
+ projectPath.startsWith(sessionProject);
233
+ }
234
+
235
+ extractContent(sessionData) {
236
+ if (sessionData.content && typeof sessionData.content === 'string') {
237
+ return sessionData.content;
238
+ }
239
+
240
+ if (sessionData.messages && Array.isArray(sessionData.messages)) {
241
+ return sessionData.messages
242
+ .map(msg => {
243
+ if (msg.message && typeof msg.message === 'object') {
244
+ const content = msg.message.content || msg.message.text || '';
245
+ return this.extractTextFromContent(content);
246
+ }
247
+ const content = msg.content || msg.text || '';
248
+ return this.extractTextFromContent(content);
249
+ })
250
+ .filter(text => text && typeof text === 'string' && text.trim())
251
+ .join(' ');
252
+ }
253
+
254
+ if (Array.isArray(sessionData)) {
255
+ return sessionData
256
+ .map(item => {
257
+ if (item.message && typeof item.message === 'object') {
258
+ const content = item.message.content || item.message.text || '';
259
+ return this.extractTextFromContent(content);
260
+ }
261
+ const content = item.content || item.text || '';
262
+ return this.extractTextFromContent(content);
263
+ })
264
+ .filter(text => text && typeof text === 'string' && text.trim())
265
+ .join(' ');
266
+ }
267
+
268
+ return '';
269
+ }
270
+
271
+ extractTextFromContent(content) {
272
+ if (typeof content === 'string') {
273
+ return content;
274
+ }
275
+
276
+ if (Array.isArray(content)) {
277
+ return content
278
+ .map(item => {
279
+ if (typeof item === 'string') return item;
280
+ if (item && typeof item === 'object') {
281
+ return item.text || item.content || '';
282
+ }
283
+ return '';
284
+ })
285
+ .filter(text => text && typeof text === 'string')
286
+ .join(' ');
287
+ }
288
+
289
+ if (content && typeof content === 'object') {
290
+ return content.text || content.content || JSON.stringify(content);
291
+ }
292
+
293
+ return '';
294
+ }
295
+
296
+ countMessages(sessionData) {
297
+ if (sessionData.messages) {
298
+ return Array.isArray(sessionData.messages) ? sessionData.messages.length : 0;
299
+ }
300
+
301
+ if (Array.isArray(sessionData)) {
302
+ return sessionData.length;
303
+ }
304
+
305
+ return 0;
306
+ }
307
+ }
308
+
309
+ class SessionFilter {
310
+ filterByCLI(sessions, cliType) {
311
+ if (!cliType) return sessions;
312
+ return sessions.filter(session => session.cliType === cliType);
313
+ }
314
+
315
+ filterBySearch(sessions, searchTerm) {
316
+ if (!searchTerm) return sessions;
317
+
318
+ const lowerSearch = searchTerm.toLowerCase();
319
+ return sessions.filter(session =>
320
+ session.title.toLowerCase().includes(lowerSearch) ||
321
+ session.content.toLowerCase().includes(lowerSearch)
322
+ );
323
+ }
324
+
325
+ filterByDateRange(sessions, timeRange = 'all') {
326
+ if (timeRange === 'all') return sessions;
327
+
328
+ const now = new Date();
329
+ return sessions.filter(session => {
330
+ const sessionDate = new Date(session.updatedAt);
331
+
332
+ switch (timeRange) {
333
+ case 'today':
334
+ return sessionDate.toDateString() === now.toDateString();
335
+ case 'week':
336
+ const weekAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);
337
+ return sessionDate >= weekAgo;
338
+ case 'month':
339
+ const monthAgo = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000);
340
+ return sessionDate >= monthAgo;
341
+ default:
342
+ return true;
343
+ }
344
+ });
345
+ }
346
+
347
+ sortByDate(sessions) {
348
+ return [...sessions].sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime());
349
+ }
350
+
351
+ filterByProject(sessions, projectPath) {
352
+ return sessions.filter(session => session.projectPath === projectPath);
353
+ }
354
+
355
+ applyFilters(sessions, options, projectPath) {
356
+ let filteredSessions = [...sessions];
357
+
358
+ filteredSessions = this.filterByProject(filteredSessions, projectPath);
359
+
360
+ if (options.cli) {
361
+ filteredSessions = this.filterByCLI(filteredSessions, options.cli);
362
+ }
363
+
364
+ if (options.search) {
365
+ filteredSessions = this.filterBySearch(filteredSessions, options.search);
366
+ }
367
+
368
+ if (options.timeRange) {
369
+ filteredSessions = this.filterByDateRange(filteredSessions, options.timeRange);
370
+ }
371
+
372
+ filteredSessions = this.sortByDate(filteredSessions);
373
+
374
+ if (options.limit && options.limit > 0) {
375
+ filteredSessions = filteredSessions.slice(0, options.limit);
376
+ }
377
+
378
+ return filteredSessions;
379
+ }
380
+ }
381
+
382
+ class HistoryFormatter {
383
+ formatSummary(sessions, context) {
384
+ if (sessions.length === 0) {
385
+ return \`📭 当前项目暂无历史会话\\n\\n💡 **提示:** 尝试: ${commandName} --search <关键词>\`;
386
+ }
387
+
388
+ let response = \`📁 **项目历史会话**\\n\\n📊 共找到 \${sessions.length} 个会话\\n\\n\`;
389
+
390
+ const byCLI = {};
391
+ sessions.forEach(session => {
392
+ if (!byCLI[session.cliType]) byCLI[session.cliType] = [];
393
+ byCLI[session.cliType].push(session);
394
+ });
395
+
396
+ Object.entries(byCLI).forEach(([cli, cliSessions]) => {
397
+ const icon = this.getCLIIcon(cli);
398
+ response += \`\${icon} **\${cli.toUpperCase()}** (\${cliSessions.length}个)\\n\`;
399
+
400
+ cliSessions.slice(0, 3).forEach((session, i) => {
401
+ const date = this.formatDate(session.updatedAt);
402
+ const title = session.title.substring(0, 50);
403
+ response += \` \${i + 1}. \${title}...\\n\`;
404
+ response += \` 📅 \${date} • 💬 \${session.messageCount}条消息\\n\`;
405
+ });
406
+
407
+ if (cliSessions.length > 3) {
408
+ response += \` ... 还有 \${cliSessions.length - 3} 个会话\\n\`;
409
+ }
410
+ response += '\\n';
411
+ });
412
+
413
+ response += \`💡 **使用方法:**\\n\`;
414
+ response += \`• '${commandName} --cli <工具>' - 查看特定CLI\\n\`;
415
+ response += \`• '${commandName} --search <关键词>' - 搜索内容\\n\`;
416
+ response += \`• '${commandName} --format timeline' - 时间线视图\`;
417
+
418
+ return response;
419
+ }
420
+
421
+ getCLIIcon(cliType) {
422
+ const icons = {
423
+ 'claude': '🟢',
424
+ 'gemini': '🔵',
425
+ 'qwen': '🟡',
426
+ 'iflow': '🔴',
427
+ 'codebuddy': '🟣',
428
+ 'codex': '🟪',
429
+ 'qodercli': '🟠',
430
+ 'kode': '⚡'
431
+ };
432
+ return icons[cliType] || '🔹';
433
+ }
434
+
435
+ formatDate(date) {
436
+ const now = new Date();
437
+ const diff = now.getTime() - date.getTime();
438
+ const days = Math.floor(diff / (24 * 60 * 60 * 1000));
439
+
440
+ if (days === 0) {
441
+ return date.toLocaleTimeString();
442
+ } else if (days === 1) {
443
+ return '昨天';
444
+ } else if (days < 7) {
445
+ return \`\${days}天前\`;
446
+ } else if (days < 30) {
447
+ return \`\${Math.floor(days / 7)}周前\`;
448
+ } else {
449
+ return \`\${Math.floor(days / 30)}个月前\`;
450
+ }
451
+ }
452
+ }
453
+
454
+ class HistoryQuery {
455
+ constructor() {
456
+ this.scanner = new SessionScanner();
457
+ this.filter = new SessionFilter();
458
+ this.formatter = new HistoryFormatter();
459
+ }
460
+
461
+ queryHistory(options, projectPath) {
462
+ try {
463
+ const allSessions = this.scanner.scanAllCLISessions(projectPath);
464
+ const filteredSessions = this.filter.applyFilters(allSessions, options, projectPath);
465
+
466
+ const response = this.formatter.formatSummary(filteredSessions);
467
+ const suggestions = this.generateSuggestions(filteredSessions, options);
468
+
469
+ return {
470
+ response,
471
+ suggestions
472
+ };
473
+ } catch (error) {
474
+ return {
475
+ response: \`❌ 历史查询失败: \${error.message}\`,
476
+ suggestions: ['${commandName} --help']
477
+ };
478
+ }
479
+ }
480
+
481
+ generateSuggestions(sessions, query) {
482
+ const suggestions = [];
483
+
484
+ if (sessions.length > 0) {
485
+ suggestions.push(\`${commandName} --format timeline\`);
486
+
487
+ if (sessions[0]?.cliType) {
488
+ suggestions.push(\`${commandName} --cli \${sessions[0].cliType}\`);
489
+ }
490
+ }
491
+
492
+ suggestions.push(\`${commandName} --search "react"\`);
493
+ suggestions.push(\`${commandName} --today\`);
494
+
495
+ return suggestions.slice(0, 5);
496
+ }
497
+ }
498
+
499
+ /**
500
+ * 处理 ${commandName} 命令
501
+ */
502
+ async function handleHistoryCommand(input, context) {
503
+ try {
504
+ console.log('🔍 Searching cross-CLI history...');
505
+
506
+ const query = buildQuery(input);
507
+ const historyQuery = new HistoryQuery();
508
+ const result = historyQuery.queryHistory(query, '${projectPath}');
509
+
510
+ return {
511
+ response: result.response,
512
+ suggestions: result.suggestions
513
+ };
514
+ } catch (error) {
515
+ console.error('History command error:', error);
516
+ return {
517
+ response: \`❌ 历史查询失败: \${error.message}\`,
518
+ suggestions: ['${commandName} --help']
519
+ };
520
+ }
521
+ }
522
+
523
+ /**
524
+ * 构建查询参数
525
+ */
526
+ function buildQuery(input) {
527
+ const options = {
528
+ limit: 10,
529
+ format: 'summary',
530
+ timeRange: 'all',
531
+ cli: null,
532
+ search: null
533
+ };
534
+
535
+ const cleanInput = input.replace(/^\\\\/?${commandName.replace(/\//g, '')}\\s*/i, '').trim();
536
+ const parts = cleanInput.split(/\s+/).filter(p => p.length > 0);
537
+
538
+ for (let i = 0; i < parts.length; i++) {
539
+ const part = parts[i].toLowerCase();
540
+
541
+ if (part === '--cli' && i + 1 < parts.length) {
542
+ options.cli = parts[++i];
543
+ } else if (part === '--search' && i + 1 < parts.length) {
544
+ options.search = parts[++i];
545
+ } else if (part === '--limit' && i + 1 < parts.length) {
546
+ options.limit = parseInt(parts[++i]);
547
+ } else if (part === '--format' && i + 1 < parts.length) {
548
+ const format = parts[++i]?.toLowerCase();
549
+ if (['summary', 'timeline', 'detailed', 'context'].includes(format)) {
550
+ options.format = format;
551
+ }
552
+ } else if (part === '--today') {
553
+ options.timeRange = 'today';
554
+ } else if (part === '--week') {
555
+ options.timeRange = 'week';
556
+ } else if (part === '--month') {
557
+ options.timeRange = 'month';
558
+ } else if (!part.startsWith('--') && !options.search) {
559
+ options.search = part;
560
+ }
561
+ }
562
+
563
+ return options;
564
+ }
565
+
566
+ // CLI-specific registration logic
567
+
568
+ ${this.generateCLIRegistrationCode(cliName, commandName)}
569
+ `;
570
+ }
571
+
572
+ generateCLIRegistrationCode(cliName, commandName) {
573
+ switch (cliName.toLowerCase()) {
574
+ case 'claude':
575
+ case 'codebuddy':
576
+ return `
577
+ // ${cliName.charAt(0).toUpperCase() + cliName.slice(1)} CLI integration
578
+ if (typeof ${cliName} !== 'undefined') {
579
+ ${cliName}.addCommand('${commandName}', handleHistoryCommand);
580
+ }
581
+
582
+ // 导出处理器
583
+ module.exports = {
584
+ handleHistoryCommand
585
+ };`;
586
+
587
+ case 'gemini':
588
+ return `
589
+ class GeminiHistoryHandler {
590
+ constructor() {
591
+ this.commandName = '${commandName}';
592
+ }
593
+
594
+ async handleCommand(input, session) {
595
+ if (!input.startsWith('${commandName}')) return null;
596
+
597
+ try {
598
+ const options = this.buildQuery(input);
599
+ const historyQuery = new HistoryQuery();
600
+ const result = historyQuery.queryHistory(options, '${projectPath}');
601
+
602
+ return {
603
+ text: result.response,
604
+ continue: true,
605
+ suggestions: result.suggestions
606
+ };
607
+ } catch (error) {
608
+ return {
609
+ text: \`History command failed: \${error.message}\`,
610
+ continue: true,
611
+ suggestions: []
612
+ };
613
+ }
614
+ }
615
+
616
+ buildQuery(input) {
617
+ const options = {
618
+ limit: 10,
619
+ format: 'summary',
620
+ timeRange: 'all',
621
+ cli: null,
622
+ search: null
623
+ };
624
+
625
+ const cleanInput = input.replace(/^\\\\/?\${this.commandName.replace(/\\//g, '')}\\s*/i, '').trim();
626
+ const parts = cleanInput.split(/\\s+/).filter(p => p.length > 0);
627
+
628
+ for (let i = 0; i < parts.length; i++) {
629
+ const part = parts[i].toLowerCase();
630
+ if (part === '--cli' && i + 1 < parts.length) {
631
+ options.cli = parts[++i];
632
+ } else if (part === '--search' && i + 1 < parts.length) {
633
+ options.search = parts[++i];
634
+ } else if (part === '--limit' && i + 1 < parts.length) {
635
+ options.limit = parseInt(parts[++i]);
636
+ } else if (!part.startsWith('--') && !options.search) {
637
+ options.search = part;
638
+ }
639
+ }
640
+
641
+ return options;
642
+ }
643
+ }
644
+
645
+ // 注册处理器
646
+ const handler = new GeminiHistoryHandler();
647
+
648
+ if (typeof module !== 'undefined' && module.exports) {
649
+ module.exports = { GeminiHistoryHandler, handler };
650
+ }
651
+
652
+ if (typeof geminiCLI !== 'undefined') {
653
+ const cmdName = '${commandName}'.replace(/^\\//, '');
654
+ geminiCLI.addCommandHandler(cmdName, handler.handleCommand.bind(handler));
655
+ }`;
656
+
657
+ case 'qwen':
658
+ case 'qodercli':
659
+ case 'kode':
660
+ return `
661
+ // ${cliName.charAt(0).toUpperCase() + cliName.slice(1)} CLI integration
662
+ if (typeof ${cliName} !== 'undefined' && ${cliName}.addExtension) {
663
+ ${cliName}.addExtension('history', handleHistoryCommand);
664
+ }
665
+
666
+ // 导出处理器
667
+ module.exports = {
668
+ handleHistoryCommand
669
+ };`;
670
+
671
+ default:
672
+ return `
673
+ // ${cliName.charAt(0).toUpperCase() + cliName.slice(1)} CLI integration
674
+ // Generic registration - may need customization for specific CLI
675
+
676
+ if (typeof module !== 'undefined' && module.exports) {
677
+ module.exports = {
678
+ handleHistoryCommand
679
+ };
680
+ }`;
681
+ }
682
+ }
683
+
684
+ getFileName(cliName) {
685
+ switch (cliName.toLowerCase()) {
686
+ case 'claude':
687
+ case 'gemini':
688
+ case 'qwen':
689
+ case 'codebuddy':
690
+ case 'codex':
691
+ case 'kode':
692
+ return 'resumesession-history.js';
693
+ case 'qodercli':
694
+ return 'history.js';
695
+ default:
696
+ return 'resumesession-history.js';
697
+ }
698
+ }
699
+ }
700
+
701
+ module.exports = ResumeSessionGenerator;