stigmergy 1.0.94 → 1.0.97

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 (67) hide show
  1. package/README.md +20 -0
  2. package/bin/stigmergy +37 -12
  3. package/docs/HASH_TABLE.md +83 -0
  4. package/docs/WEATHER_PROCESSOR_API.md +230 -0
  5. package/docs/best_practices.md +135 -0
  6. package/docs/development_guidelines.md +392 -0
  7. package/docs/http-request-handler.md +289 -0
  8. package/docs/json-parser.md +102 -0
  9. package/docs/requirements_specification.md +148 -0
  10. package/docs/rest_client.md +144 -0
  11. package/docs/system_design.md +314 -0
  12. package/docs/tdd_implementation_plan.md +384 -0
  13. package/docs/test_report.md +49 -0
  14. package/examples/calculator-example.js +72 -0
  15. package/examples/encryption-example.js +67 -0
  16. package/examples/json-parser-example.js +120 -0
  17. package/examples/json-validation-example.js +64 -0
  18. package/examples/rest-client-example.js +52 -0
  19. package/examples/rest_client_example.js +54 -0
  20. package/package.json +26 -21
  21. package/scripts/post-deployment-config.js +9 -2
  22. package/src/auth.js +173 -0
  23. package/src/auth_command.js +208 -0
  24. package/src/calculator.js +313 -0
  25. package/src/core/cli_help_analyzer.js +674 -563
  26. package/src/core/cli_parameter_handler.js +127 -0
  27. package/src/core/cli_tools.js +89 -0
  28. package/src/core/error_handler.js +406 -0
  29. package/src/core/memory_manager.js +83 -0
  30. package/src/core/rest_client.js +160 -0
  31. package/src/core/smart_router.js +146 -0
  32. package/src/data_encryption.js +143 -0
  33. package/src/data_structures.js +440 -0
  34. package/src/deploy.js +56 -0
  35. package/src/index.js +9 -9
  36. package/src/main.js +889 -752
  37. package/src/main_english.js +1305 -977
  38. package/src/main_fixed.js +1172 -0
  39. package/src/utils.js +916 -0
  40. package/src/weatherProcessor.js +228 -0
  41. package/test/calculator.test.js +215 -0
  42. package/test/collision-test.js +26 -0
  43. package/test/csv-processing-test.js +36 -0
  44. package/test/e2e/claude-cli-test.js +128 -0
  45. package/test/e2e/collaboration-test.js +75 -0
  46. package/test/e2e/comprehensive-test.js +431 -0
  47. package/test/e2e/error-handling-test.js +90 -0
  48. package/test/e2e/individual-tool-test.js +143 -0
  49. package/test/e2e/other-cli-test.js +130 -0
  50. package/test/e2e/qoder-cli-test.js +128 -0
  51. package/test/e2e/run-e2e-tests.js +73 -0
  52. package/test/e2e/test-data.js +88 -0
  53. package/test/e2e/test-utils.js +222 -0
  54. package/test/encryption-simple-test.js +110 -0
  55. package/test/encryption.test.js +129 -0
  56. package/test/hash-table-demo.js +33 -0
  57. package/test/hash-table-test.js +26 -0
  58. package/test/hash_table_test.js +114 -0
  59. package/test/json-parser-test.js +161 -0
  60. package/test/json-validation-test.js +164 -0
  61. package/test/rest-client-test.js +56 -0
  62. package/test/rest_client.test.js +85 -0
  63. package/test/unit/calculator-full.test.js +191 -0
  64. package/test/unit/calculator-simple.test.js +96 -0
  65. package/test/unit/calculator.test.js +97 -0
  66. package/test/unit/cli_parameter_handler.test.js +116 -0
  67. package/test/weather-processor.test.js +104 -0
@@ -1,563 +1,674 @@
1
- /**
2
- * Advanced CLI Help Analyzer - International Version
3
- * Analyzes CLI help information to extract command patterns and calling methods
4
- */
5
-
6
- const { spawnSync } = require('child_process');
7
- const fs = require('fs/promises');
8
- const path = require('path');
9
- const os = require('os');
10
-
11
- class CLIHelpAnalyzer {
12
- constructor() {
13
- this.configDir = path.join(os.homedir(), '.stigmergy', 'cli-patterns');
14
- this.persistentConfig = path.join(this.configDir, 'cli-patterns.json');
15
- this.lastAnalysisFile = path.join(this.configDir, 'last-analysis.json');
16
- this.cliTools = require('../main').CLI_TOOLS || {};
17
-
18
- // Pattern recognition rules for different CLI types
19
- this.patternRules = {
20
- // OpenAI style CLI (codex, chatgpt)
21
- openai: {
22
- commandPattern: /^(\w+)(?:\s+--?[\w-]+(?:\s+[\w-]+)?)?\s*(.*)$/,
23
- optionPattern: /--?[\w-]+(?:\s+[\w-]+)?/g,
24
- examplePattern: /(?:example|usage|Usage)[::]\s*\n([\s\S]*?)(?=\n\n|\n[A-Z]|\n$)/gi,
25
- subcommandPattern: /^\s{2,4}(\w+)\s+.+$/gm
26
- },
27
- // Anthropic style CLI (claude)
28
- anthropic: {
29
- commandPattern: /^(\w+)(?:\s+(?:--?\w+|\[\w+\]))*\s*(.*)$/,
30
- optionPattern: /--?\w+(?:\s+<\w+>)?/g,
31
- examplePattern: /(?:Examples?|例子)[::]\s*\n([\s\S]*?)(?=\n\n|\n[A-Z]|\n$)/gi,
32
- subcommandPattern: /^\s{2}(\w+)\s{2,}.+$/gm
33
- },
34
- // Google style CLI (gemini)
35
- google: {
36
- commandPattern: /^(\w+)(?:\s+(?:--?\w+(?:=\w+)?|<\w+>))*\s*(.*)$/,
37
- optionPattern: /--?\w+(?:=\w+)?/g,
38
- examplePattern: /(?:Examples?|SAMPLE)[::]\s*\n([\s\S]*?)(?=\n\n|\n[A-Z]|\n$)/gi,
39
- subcommandPattern: /^\s{2,4}(\w+)\s{2,}.+$/gm
40
- },
41
- // Generic CLI pattern
42
- generic: {
43
- commandPattern: /^(\w+)(?:\s+.*)?$/,
44
- optionPattern: /--?[a-zA-Z][\w-]*/g,
45
- examplePattern: /(?:example|usage|用法|使用)[::]\s*\n([\s\S]*?)(?=\n\n|\n[A-Z]|\n$)/gi,
46
- subcommandPattern: /^\s{2,6}([a-z][a-z0-9_-]+)\s+.+$/gm
47
- }
48
- };
49
- }
50
-
51
- /**
52
- * Initialize the analyzer and ensure config directory exists
53
- */
54
- async initialize() {
55
- try {
56
- await fs.mkdir(this.configDir, { recursive: true });
57
-
58
- // Initialize persistent config if not exists
59
- const configExists = await this.fileExists(this.persistentConfig);
60
- if (!configExists) {
61
- await this.savePersistentConfig({
62
- version: '1.0.0',
63
- lastUpdated: new Date().toISOString(),
64
- cliPatterns: {},
65
- failedAttempts: {}
66
- });
67
- }
68
-
69
- return true;
70
- } catch (error) {
71
- console.error('Failed to initialize CLI Help Analyzer:', error.message);
72
- return false;
73
- }
74
- }
75
-
76
- /**
77
- * Analyze all configured CLI tools
78
- */
79
- async analyzeAllCLI() {
80
- const results = {};
81
-
82
- for (const [cliName, cliConfig] of Object.entries(this.cliTools)) {
83
- try {
84
- console.log(`Analyzing ${cliName}...`);
85
- results[cliName] = await this.analyzeCLI(cliName);
86
- } catch (error) {
87
- console.error(`Failed to analyze ${cliName}:`, error.message);
88
- results[cliName] = {
89
- success: false,
90
- error: error.message,
91
- timestamp: new Date().toISOString()
92
- };
93
- }
94
- }
95
-
96
- return results;
97
- }
98
-
99
- /**
100
- * Analyze a specific CLI tool's help information
101
- */
102
- async analyzeCLI(cliName) {
103
- const cliConfig = this.cliTools[cliName];
104
- if (!cliConfig) {
105
- throw new Error(`CLI configuration not found for ${cliName}`);
106
- }
107
-
108
- // Check if we have recent cached analysis
109
- const cached = await this.getCachedAnalysis(cliName);
110
- if (cached && !this.isCacheExpired(cached.timestamp)) {
111
- return cached;
112
- }
113
-
114
- try {
115
- // Get help information using multiple methods
116
- const helpInfo = await this.getHelpInfo(cliName, cliConfig);
117
-
118
- // Detect CLI type for pattern recognition
119
- const cliType = this.detectCLIType(helpInfo.rawHelp, cliName);
120
-
121
- // Extract patterns based on CLI type
122
- const patterns = this.extractPatterns(helpInfo.rawHelp, cliType);
123
-
124
- // Analyze command structure
125
- const commandStructure = this.analyzeCommandStructure(patterns);
126
-
127
- // Extract usage examples
128
- const examples = this.extractUsageExamples(helpInfo.rawHelp, cliType);
129
-
130
- // Determine CLI interaction mode
131
- const interactionMode = this.determineInteractionMode(helpInfo, patterns);
132
-
133
- const analysis = {
134
- success: true,
135
- cliName,
136
- cliType,
137
- version: helpInfo.version,
138
- helpMethod: helpInfo.method,
139
- patterns,
140
- commandStructure,
141
- examples,
142
- interactionMode,
143
- timestamp: new Date().toISOString()
144
- };
145
-
146
- // Cache the analysis
147
- await this.cacheAnalysis(cliName, analysis);
148
-
149
- return analysis;
150
-
151
- } catch (error) {
152
- // Record failed attempt
153
- await this.recordFailedAttempt(cliName, error);
154
- throw error;
155
- }
156
- }
157
-
158
- /**
159
- * Get help information using multiple methods
160
- */
161
- async getHelpInfo(cliName, cliConfig) {
162
- const helpMethods = [
163
- ['--help'],
164
- ['-h'],
165
- ['help'],
166
- ['--usage'],
167
- [''],
168
- ['version'],
169
- ['--version'],
170
- ['-v']
171
- ];
172
-
173
- let rawHelp = '';
174
- let version = 'unknown';
175
- let method = 'unknown';
176
-
177
- // Try different help commands
178
- for (const helpArgs of helpMethods) {
179
- try {
180
- const result = spawnSync(cliName, helpArgs, {
181
- encoding: 'utf8',
182
- timeout: 15000,
183
- shell: true
184
- });
185
-
186
- if (result.status === 0 && result.stdout) {
187
- rawHelp = result.stdout;
188
- method = `${cliName} ${helpArgs.join(' ')}`;
189
- break;
190
- } else if (result.stderr) {
191
- rawHelp = result.stderr;
192
- method = `${cliName} ${helpArgs.join(' ')} (stderr)`;
193
- break;
194
- }
195
- } catch (error) {
196
- // Try next method
197
- continue;
198
- }
199
- }
200
-
201
- // Try to get version separately
202
- if (cliConfig.version) {
203
- try {
204
- const versionCmd = cliConfig.version.split(' ');
205
- const versionResult = spawnSync(versionCmd[0], versionCmd.slice(1), {
206
- encoding: 'utf8',
207
- timeout: 10000,
208
- shell: true
209
- });
210
-
211
- if (versionResult.status === 0) {
212
- version = versionResult.stdout.trim() || versionResult.stderr.trim();
213
- }
214
- } catch (error) {
215
- // Use default version
216
- }
217
- }
218
-
219
- if (!rawHelp) {
220
- throw new Error(`Unable to get help information for ${cliName}`);
221
- }
222
-
223
- return { rawHelp, version, method };
224
- }
225
-
226
- /**
227
- * Detect CLI type based on help content and naming
228
- */
229
- detectCLIType(helpText, cliName) {
230
- const text = helpText.toLowerCase();
231
- const name = cliName.toLowerCase();
232
-
233
- // Detect based on CLI name
234
- if (name.includes('claude') || name.includes('anthropic')) {
235
- return 'anthropic';
236
- }
237
- if (name.includes('gemini') || name.includes('google')) {
238
- return 'google';
239
- }
240
- if (name.includes('codex') || name.includes('openai') || name.includes('chatgpt')) {
241
- return 'openai';
242
- }
243
-
244
- // Detect based on help content patterns
245
- if (text.includes('anthropic') || text.includes('claude')) {
246
- return 'anthropic';
247
- }
248
- if (text.includes('google') || text.includes('gemini') || text.includes('vertex')) {
249
- return 'google';
250
- }
251
- if (text.includes('openai') || text.includes('gpt') || text.includes('codex')) {
252
- return 'openai';
253
- }
254
-
255
- // Default to generic
256
- return 'generic';
257
- }
258
-
259
- /**
260
- * Extract command patterns from help text
261
- */
262
- extractPatterns(helpText, cliType) {
263
- const rules = this.patternRules[cliType] || this.patternRules.generic;
264
- const patterns = {
265
- commands: [],
266
- options: [],
267
- subcommands: [],
268
- arguments: [],
269
- flags: []
270
- };
271
-
272
- // Extract subcommands
273
- const subcommandMatches = helpText.match(rules.subcommandPattern);
274
- if (subcommandMatches) {
275
- patterns.subcommands = subcommandMatches.map(match => {
276
- const parts = match.trim().split(/\s+/);
277
- return {
278
- name: parts[0],
279
- description: parts.slice(1).join(' '),
280
- syntax: match.trim()
281
- };
282
- });
283
- }
284
-
285
- // Extract options/flags
286
- const optionMatches = helpText.match(rules.optionPattern);
287
- if (optionMatches) {
288
- patterns.options = [...new Set(optionMatches)];
289
- patterns.flags = optionMatches.filter(opt => opt.startsWith('--')).map(opt => opt.replace(/^--/, ''));
290
- }
291
-
292
- // Extract main commands (first level)
293
- const lines = helpText.split('\n');
294
- for (const line of lines) {
295
- const trimmed = line.trim();
296
-
297
- // Look for commands that start at beginning of line
298
- if (/^[a-z][a-z0-9_-]+\s+.+$/.test(trimmed)) {
299
- const parts = trimmed.split(/\s+/);
300
- const command = parts[0];
301
- const description = parts.slice(1).join(' ');
302
-
303
- if (!patterns.commands.find(cmd => cmd.name === command)) {
304
- patterns.commands.push({
305
- name: command,
306
- description,
307
- syntax: trimmed
308
- });
309
- }
310
- }
311
- }
312
-
313
- return patterns;
314
- }
315
-
316
- /**
317
- * Analyze command structure and calling patterns
318
- */
319
- analyzeCommandStructure(patterns) {
320
- const structure = {
321
- primaryCommand: '',
322
- commandFormat: '',
323
- argumentStyle: '',
324
- optionStyle: '',
325
- interactiveMode: false,
326
- hasSubcommands: patterns.subcommands.length > 0,
327
- complexity: 'simple'
328
- };
329
-
330
- // Determine complexity based on available commands
331
- if (patterns.commands.length > 10 || patterns.subcommands.length > 5) {
332
- structure.complexity = 'complex';
333
- } else if (patterns.commands.length > 3 || patterns.options.length > 10) {
334
- structure.complexity = 'moderate';
335
- }
336
-
337
- // Determine command format based on patterns
338
- if (patterns.subcommands.length > 0) {
339
- structure.commandFormat = 'cli <subcommand> [options] [args]';
340
- } else if (patterns.options.length > 0) {
341
- structure.commandFormat = 'cli [options] [args]';
342
- } else {
343
- structure.commandFormat = 'cli [args]';
344
- }
345
-
346
- // Check for interactive mode indicators
347
- const hasInteractiveIndicators = patterns.commands.some(cmd =>
348
- cmd.name.includes('chat') ||
349
- cmd.name.includes('interactive') ||
350
- cmd.name.includes('shell') ||
351
- cmd.description.toLowerCase().includes('interactive')
352
- );
353
-
354
- structure.interactiveMode = hasInteractiveIndicators;
355
-
356
- return structure;
357
- }
358
-
359
- /**
360
- * Extract usage examples from help text
361
- */
362
- extractUsageExamples(helpText, cliType) {
363
- const rules = this.patternRules[cliType] || this.patternRules.generic;
364
- const examples = [];
365
-
366
- // Find example sections
367
- const exampleMatches = helpText.match(rules.examplePattern);
368
-
369
- if (exampleMatches) {
370
- for (const match of exampleMatches) {
371
- const exampleText = match.replace(/^(example|usage|用法|使用)[::]\s*/i, '').trim();
372
-
373
- // Split by lines and extract command examples
374
- const lines = exampleText.split('\n')
375
- .map(line => line.trim())
376
- .filter(line => line && !line.startsWith('#') && !line.startsWith('//'));
377
-
378
- for (const line of lines) {
379
- if (line.includes('$') || line.includes('>') || line.startsWith('cli') || /^[a-z][\w-]*\s/.test(line)) {
380
- // Extract clean command
381
- const command = line.replace(/^[>$\s]+/, '').replace(/^cli\s*/, '').trim();
382
- if (command) {
383
- examples.push({
384
- command,
385
- raw: line,
386
- description: ''
387
- });
388
- }
389
- }
390
- }
391
- }
392
- }
393
-
394
- return examples;
395
- }
396
-
397
- /**
398
- * Determine CLI interaction mode
399
- */
400
- determineInteractionMode(helpInfo, patterns) {
401
- const text = helpInfo.rawHelp.toLowerCase();
402
-
403
- // Check for different interaction modes
404
- if (text.includes('chat') || text.includes('conversation') || text.includes('interactive')) {
405
- return 'chat';
406
- }
407
-
408
- if (text.includes('api') || text.includes('endpoint') || text.includes('request')) {
409
- return 'api';
410
- }
411
-
412
- if (patterns.subcommands.length > 0) {
413
- return 'subcommand';
414
- }
415
-
416
- if (patterns.options.length > 5) {
417
- return 'option';
418
- }
419
-
420
- return 'simple';
421
- }
422
-
423
- /**
424
- * Cache analysis results
425
- */
426
- async cacheAnalysis(cliName, analysis) {
427
- try {
428
- const config = await this.loadPersistentConfig();
429
- config.cliPatterns[cliName] = analysis;
430
- config.lastUpdated = new Date().toISOString();
431
-
432
- // Remove from failed attempts if it was there
433
- delete config.failedAttempts[cliName];
434
-
435
- await this.savePersistentConfig(config);
436
-
437
- // Also save last analysis timestamp
438
- await fs.writeFile(
439
- this.lastAnalysisFile,
440
- JSON.stringify({ [cliName]: analysis.timestamp }, null, 2)
441
- );
442
-
443
- } catch (error) {
444
- console.error(`Failed to cache analysis for ${cliName}:`, error.message);
445
- }
446
- }
447
-
448
- /**
449
- * Get cached analysis if available
450
- */
451
- async getCachedAnalysis(cliName) {
452
- try {
453
- const config = await this.loadPersistentConfig();
454
- return config.cliPatterns[cliName] || null;
455
- } catch (error) {
456
- return null;
457
- }
458
- }
459
-
460
- /**
461
- * Check if cache is expired (24 hours)
462
- */
463
- isCacheExpired(timestamp) {
464
- const cacheTime = new Date(timestamp);
465
- const now = new Date();
466
- const hoursDiff = (now - cacheTime) / (1000 * 60 * 60);
467
- return hoursDiff > 24;
468
- }
469
-
470
- /**
471
- * Record failed analysis attempt
472
- */
473
- async recordFailedAttempt(cliName, error) {
474
- try {
475
- const config = await this.loadPersistentConfig();
476
- config.failedAttempts[cliName] = {
477
- error: error.message,
478
- timestamp: new Date().toISOString(),
479
- attempts: (config.failedAttempts[cliName]?.attempts || 0) + 1
480
- };
481
- config.lastUpdated = new Date().toISOString();
482
- await this.savePersistentConfig(config);
483
- } catch (err) {
484
- console.error('Failed to record failed attempt:', err.message);
485
- }
486
- }
487
-
488
- /**
489
- * Load persistent configuration
490
- */
491
- async loadPersistentConfig() {
492
- try {
493
- const data = await fs.readFile(this.persistentConfig, 'utf8');
494
- return JSON.parse(data);
495
- } catch (error) {
496
- return {
497
- version: '1.0.0',
498
- lastUpdated: new Date().toISOString(),
499
- cliPatterns: {},
500
- failedAttempts: {}
501
- };
502
- }
503
- }
504
-
505
- /**
506
- * Save persistent configuration
507
- */
508
- async savePersistentConfig(config) {
509
- await fs.writeFile(this.persistentConfig, JSON.stringify(config, null, 2));
510
- }
511
-
512
- /**
513
- * Check if file exists
514
- */
515
- async fileExists(filePath) {
516
- try {
517
- await fs.access(filePath);
518
- return true;
519
- } catch {
520
- return false;
521
- }
522
- }
523
-
524
- /**
525
- * Get CLI pattern for specific tool
526
- */
527
- async getCLIPattern(cliName) {
528
- const cached = await this.getCachedAnalysis(cliName);
529
-
530
- if (cached && !this.isCacheExpired(cached.timestamp)) {
531
- return cached;
532
- }
533
-
534
- // Re-analyze if cache expired or not available
535
- return await this.analyzeCLI(cliName);
536
- }
537
-
538
- /**
539
- * Update CLI pattern when call fails
540
- */
541
- async updatePatternOnFailure(cliName, error, attemptedCommand) {
542
- console.log(`Updating pattern for ${cliName} due to failure:`, error.message);
543
-
544
- try {
545
- // Re-analyze the CLI
546
- const newAnalysis = await this.analyzeCLI(cliName);
547
-
548
- // Add failure context
549
- newAnalysis.lastFailure = {
550
- error: error.message,
551
- attemptedCommand,
552
- timestamp: new Date().toISOString()
553
- };
554
-
555
- return newAnalysis;
556
- } catch (analysisError) {
557
- console.error(`Failed to re-analyze ${cliName}:`, analysisError.message);
558
- return null;
559
- }
560
- }
561
- }
562
-
563
- module.exports = CLIHelpAnalyzer;
1
+ // Enhanced CLI Help Analyzer with better pattern extraction
2
+ const { spawnSync } = require("child_process");
3
+ const fs = require("fs/promises");
4
+ const path = require("path");
5
+ const os = require("os");
6
+ const { CLI_TOOLS } = require("./cli_tools");
7
+ const { errorHandler, ERROR_TYPES } = require("./error_handler");
8
+
9
+ class CLIHelpAnalyzer {
10
+ constructor() {
11
+ this.configDir = path.join(os.homedir(), ".stigmergy", "cli-patterns");
12
+ this.persistentConfig = path.join(this.configDir, "cli-patterns.json");
13
+ this.lastAnalysisFile = path.join(this.configDir, "last-analysis.json");
14
+ this.cliTools = CLI_TOOLS;
15
+
16
+ // Pattern recognition rules for different CLI types
17
+ this.patternRules = {
18
+ // OpenAI style CLI (codex, chatgpt)
19
+ openai: {
20
+ commandPattern: /^(\w+)(?:\s+--?[\w-]+(?:\s+[\w-]+)?)?\s*(.*)$/,
21
+ optionPattern: /--?[\w-]+(?:\s+[\w-]+)?/g,
22
+ examplePattern:
23
+ /(?:example|usage|Usage)[::]\s*\n([\s\S]*?)(?=\n\n|\n[A-Z]|\n$)/gi,
24
+ subcommandPattern: /^\s{2,4}(\w+)\s+.+$/gm,
25
+ },
26
+ // Anthropic style CLI (claude)
27
+ anthropic: {
28
+ commandPattern: /^(\w+)(?:\s+(?:--?\w+|\[\w+\]))*\s*(.*)$/,
29
+ optionPattern: /--?\w+(?:\s+<\w+>)?/g,
30
+ examplePattern:
31
+ /(?:Examples?|例子)[::]\s*\n([\s\S]*?)(?=\n\n|\n[A-Z]|\n$)/gi,
32
+ subcommandPattern: /^\s{2}(\w+)\s{2,}.+$/gm,
33
+ },
34
+ // Google style CLI (gemini)
35
+ google: {
36
+ commandPattern: /^(\w+)(?:\s+(?:--?\w+(?:=\w+)?|<\w+>))*\s*(.*)$/,
37
+ optionPattern: /--?\w+(?:=\w+)?/g,
38
+ examplePattern:
39
+ /(?:Examples?|SAMPLE)[::]\s*\n([\s\S]*?)(?=\n\n|\n[A-Z]|\n$)/gi,
40
+ subcommandPattern: /^\s{2,4}(\w+)\s{2,}.+$/gm,
41
+ },
42
+ // Generic CLI pattern
43
+ generic: {
44
+ commandPattern: /^(\w+)(?:\s+.*)?$/,
45
+ optionPattern: /--?[a-zA-Z][\w-]*/g,
46
+ examplePattern:
47
+ /(?:example|usage|用法|使用)[::]\s*\n([\s\S]*?)(?=\n\n|\n[A-Z]|\n$)/gi,
48
+ subcommandPattern: /^\s{2,6}([a-z][a-z0-9_-]+)\s+.+$/gm,
49
+ },
50
+ };
51
+ }
52
+
53
+ /**
54
+ * Set CLI tools configuration
55
+ * @param {Object} cliTools - CLI tools configuration
56
+ */
57
+ setCLITools(cliTools) {
58
+ this.cliTools = cliTools;
59
+ }
60
+
61
+ /**
62
+ * Initialize the analyzer and ensure config directory exists
63
+ */
64
+ async initialize() {
65
+ try {
66
+ await fs.mkdir(this.configDir, { recursive: true });
67
+
68
+ // Initialize persistent config if not exists
69
+ const configExists = await this.fileExists(this.persistentConfig);
70
+ if (!configExists) {
71
+ await this.savePersistentConfig({
72
+ version: "1.0.0",
73
+ lastUpdated: new Date().toISOString(),
74
+ cliPatterns: {},
75
+ failedAttempts: {},
76
+ });
77
+ }
78
+
79
+ return true;
80
+ } catch (error) {
81
+ await errorHandler.logError(error, "ERROR", "CLIHelpAnalyzer.initialize");
82
+ return false;
83
+ }
84
+ }
85
+
86
+ /**
87
+ * Analyze all configured CLI tools
88
+ */
89
+ async analyzeAllCLI() {
90
+ const results = {};
91
+
92
+ for (const [cliName, cliConfig] of Object.entries(this.cliTools)) {
93
+ try {
94
+ if (process.env.DEBUG === "true") {
95
+ console.log(`Analyzing ${cliName}...`);
96
+ }
97
+ results[cliName] = await this.analyzeCLI(cliName);
98
+ } catch (error) {
99
+ await errorHandler.logError(
100
+ error,
101
+ "WARN",
102
+ `CLIHelpAnalyzer.analyzeAllCLI.${cliName}`,
103
+ );
104
+ results[cliName] = { success: false, error: error.message };
105
+ }
106
+ }
107
+
108
+ return results;
109
+ }
110
+
111
+ /**
112
+ * Analyze specific CLI tool
113
+ */
114
+ async analyzeCLI(cliName) {
115
+ const cliConfig = this.cliTools[cliName];
116
+ if (!cliConfig) {
117
+ throw new Error(`CLI tool ${cliName} not found in configuration`);
118
+ }
119
+
120
+ try {
121
+ // Get help information
122
+ const helpInfo = await this.getHelpInfo(cliName, cliConfig);
123
+
124
+ // Detect CLI type
125
+ const cliType = this.detectCLIType(helpInfo.rawHelp, cliName);
126
+
127
+ // Extract patterns
128
+ const patterns = this.extractPatterns(helpInfo.rawHelp, cliType);
129
+
130
+ // Analyze command structure
131
+ const commandStructure = this.analyzeCommandStructure(patterns);
132
+
133
+ // Extract usage examples
134
+ const examples = this.extractUsageExamples(helpInfo.rawHelp, cliType);
135
+
136
+ // Determine interaction mode
137
+ const interactionMode = this.determineInteractionMode(helpInfo, patterns);
138
+
139
+ const analysis = {
140
+ success: true,
141
+ cliName,
142
+ cliType,
143
+ version: helpInfo.version,
144
+ helpMethod: helpInfo.method,
145
+ patterns,
146
+ commandStructure,
147
+ examples,
148
+ interactionMode,
149
+ timestamp: new Date().toISOString(),
150
+ };
151
+
152
+ // Cache the analysis
153
+ await this.cacheAnalysis(cliName, analysis);
154
+
155
+ return analysis;
156
+ } catch (error) {
157
+ // Record failed attempt
158
+ await this.recordFailedAttempt(cliName, error);
159
+ throw error;
160
+ }
161
+ }
162
+
163
+ /**
164
+ * Get help information using multiple methods
165
+ */
166
+ async getHelpInfo(cliName, cliConfig) {
167
+ const helpMethods = [
168
+ ["--help"],
169
+ ["-h"],
170
+ ["help"],
171
+ ["--usage"],
172
+ [""],
173
+ ["version"],
174
+ ["--version"],
175
+ ["-v"],
176
+ ];
177
+
178
+ let rawHelp = "";
179
+ let version = "unknown";
180
+ let method = "unknown";
181
+
182
+ // Try different help commands
183
+ for (const helpArgs of helpMethods) {
184
+ try {
185
+ const result = spawnSync(cliName, helpArgs, {
186
+ encoding: "utf8",
187
+ timeout: 15000,
188
+ shell: true,
189
+ });
190
+
191
+ if (result.status === 0 && result.stdout) {
192
+ rawHelp = result.stdout;
193
+ method = `${cliName} ${helpArgs.join(" ")}`;
194
+ break;
195
+ } else if (result.stderr) {
196
+ rawHelp = result.stderr;
197
+ method = `${cliName} ${helpArgs.join(" ")} (stderr)`;
198
+ break;
199
+ }
200
+ } catch (error) {
201
+ // Try next method
202
+ continue;
203
+ }
204
+ }
205
+
206
+ // Try to get version separately
207
+ if (cliConfig.version) {
208
+ try {
209
+ const versionCmd = cliConfig.version.split(" ");
210
+ const versionResult = spawnSync(versionCmd[0], versionCmd.slice(1), {
211
+ encoding: "utf8",
212
+ timeout: 10000,
213
+ shell: true,
214
+ });
215
+
216
+ if (versionResult.status === 0) {
217
+ version = versionResult.stdout.trim() || versionResult.stderr.trim();
218
+ }
219
+ } catch (error) {
220
+ // Use default version
221
+ }
222
+ }
223
+
224
+ if (!rawHelp) {
225
+ throw new Error(`Unable to get help information for ${cliName}`);
226
+ }
227
+
228
+ return { rawHelp, version, method };
229
+ }
230
+
231
+ /**
232
+ * Detect CLI type based on help content and naming
233
+ */
234
+ detectCLIType(helpText, cliName) {
235
+ const text = helpText.toLowerCase();
236
+ const name = cliName.toLowerCase();
237
+
238
+ // Detect based on CLI name
239
+ if (name.includes("claude") || name.includes("anthropic")) {
240
+ return "anthropic";
241
+ }
242
+ if (name.includes("gemini") || name.includes("google")) {
243
+ return "google";
244
+ }
245
+ if (
246
+ name.includes("codex") ||
247
+ name.includes("openai") ||
248
+ name.includes("chatgpt")
249
+ ) {
250
+ return "openai";
251
+ }
252
+
253
+ // Detect based on help content patterns
254
+ if (text.includes("anthropic") || text.includes("claude")) {
255
+ return "anthropic";
256
+ }
257
+ if (
258
+ text.includes("google") ||
259
+ text.includes("gemini") ||
260
+ text.includes("vertex")
261
+ ) {
262
+ return "google";
263
+ }
264
+ if (
265
+ text.includes("openai") ||
266
+ text.includes("gpt") ||
267
+ text.includes("codex")
268
+ ) {
269
+ return "openai";
270
+ }
271
+
272
+ // Default to generic
273
+ return "generic";
274
+ }
275
+
276
+ /**
277
+ * Extract command patterns from help text
278
+ */
279
+ extractPatterns(helpText, cliType) {
280
+ const rules = this.patternRules[cliType] || this.patternRules.generic;
281
+ const patterns = {
282
+ commands: [],
283
+ options: [],
284
+ subcommands: [],
285
+ arguments: [],
286
+ flags: [],
287
+ // New fields for better parameter handling
288
+ nonInteractiveFlag: null,
289
+ promptFlag: null,
290
+ requiredFlags: [],
291
+ commonPatterns: [],
292
+ };
293
+
294
+ // Extract subcommands
295
+ const subcommandMatches = helpText.match(rules.subcommandPattern);
296
+ if (subcommandMatches) {
297
+ patterns.subcommands = subcommandMatches.map((match) => {
298
+ const parts = match.trim().split(/\s+/);
299
+ return {
300
+ name: parts[0],
301
+ description: parts.slice(1).join(" "),
302
+ syntax: match.trim(),
303
+ };
304
+ });
305
+ }
306
+
307
+ // Extract options/flags
308
+ const optionMatches = helpText.match(rules.optionPattern);
309
+ if (optionMatches) {
310
+ patterns.options = [...new Set(optionMatches)];
311
+ patterns.flags = optionMatches
312
+ .filter((opt) => opt.startsWith("--"))
313
+ .map((opt) => opt.replace(/^--/, ""));
314
+ }
315
+
316
+ // Extract main commands (first level)
317
+ const lines = helpText.split("\n");
318
+ for (const line of lines) {
319
+ const trimmed = line.trim();
320
+
321
+ // Look for commands that start at beginning of line
322
+ if (/^[a-z][a-z0-9_-]+\s+.+$/.test(trimmed)) {
323
+ const parts = trimmed.split(/\s+/);
324
+ const command = parts[0];
325
+ const description = parts.slice(1).join(" ");
326
+
327
+ if (!patterns.commands.find((cmd) => cmd.name === command)) {
328
+ patterns.commands.push({
329
+ name: command,
330
+ description,
331
+ syntax: trimmed,
332
+ });
333
+ }
334
+ }
335
+ }
336
+
337
+ // Enhanced pattern extraction for non-interactive mode and prompt handling
338
+ this.extractEnhancedPatterns(helpText, patterns);
339
+
340
+ return patterns;
341
+ }
342
+
343
+ /**
344
+ * Extract enhanced patterns for better parameter handling
345
+ */
346
+ extractEnhancedPatterns(helpText, patterns) {
347
+ const text = helpText.toLowerCase();
348
+
349
+ // Look for non-interactive mode flags
350
+ if (
351
+ text.includes("print") ||
352
+ text.includes("non-interactive") ||
353
+ text.includes("output")
354
+ ) {
355
+ patterns.nonInteractiveFlag = "--print";
356
+ }
357
+
358
+ // Look for prompt-related flags
359
+ if (text.includes("prompt") || text.includes("-p ")) {
360
+ patterns.promptFlag = "-p";
361
+ }
362
+
363
+ // Look for required flags for non-interactive mode
364
+ if (text.includes("non-interactive") && text.includes("prompt")) {
365
+ patterns.requiredFlags.push("-p");
366
+ }
367
+
368
+ // Extract common usage patterns from examples
369
+ const exampleLines = helpText.split("\n");
370
+ for (const line of exampleLines) {
371
+ if (
372
+ line.includes('-p "') ||
373
+ line.includes("--prompt") ||
374
+ line.includes(" -p ")
375
+ ) {
376
+ patterns.commonPatterns.push(line.trim());
377
+ }
378
+ }
379
+ }
380
+
381
+ /**
382
+ * Analyze command structure and calling patterns
383
+ */
384
+ analyzeCommandStructure(patterns) {
385
+ const structure = {
386
+ primaryCommand: "",
387
+ commandFormat: "",
388
+ argumentStyle: "",
389
+ optionStyle: "",
390
+ interactiveMode: false,
391
+ hasSubcommands: patterns.subcommands.length > 0,
392
+ complexity: "simple",
393
+ // Fields for better execution
394
+ nonInteractiveSupport: !!patterns.nonInteractiveFlag,
395
+ promptStyle: patterns.promptFlag ? "flag" : "argument",
396
+ executionPattern: "",
397
+ // Additional fields for CLI parameter handling
398
+ nonInteractiveFlag: patterns.nonInteractiveFlag,
399
+ promptFlag: patterns.promptFlag,
400
+ requiredFlags: patterns.requiredFlags,
401
+ commonPatterns: patterns.commonPatterns,
402
+ };
403
+
404
+ // Determine complexity based on available commands
405
+ if (patterns.commands.length > 10 || patterns.subcommands.length > 5) {
406
+ structure.complexity = "complex";
407
+ } else if (patterns.commands.length > 3 || patterns.options.length > 10) {
408
+ structure.complexity = "moderate";
409
+ }
410
+
411
+ // Determine command format based on patterns
412
+ if (patterns.subcommands.length > 0) {
413
+ structure.commandFormat = "cli <subcommand> [options] [args]";
414
+ } else if (patterns.options.length > 0) {
415
+ structure.commandFormat = "cli [options] [args]";
416
+ } else {
417
+ structure.commandFormat = "cli [args]";
418
+ }
419
+
420
+ // Check for interactive mode indicators
421
+ const hasInteractiveIndicators = patterns.commands.some(
422
+ (cmd) =>
423
+ cmd.name.includes("chat") ||
424
+ cmd.name.includes("interactive") ||
425
+ cmd.name.includes("shell") ||
426
+ (cmd.description &&
427
+ cmd.description.toLowerCase().includes("interactive")),
428
+ );
429
+
430
+ structure.interactiveMode = hasInteractiveIndicators;
431
+
432
+ // Determine execution pattern
433
+ if (patterns.nonInteractiveFlag && patterns.promptFlag) {
434
+ structure.executionPattern = "flag-based";
435
+ } else if (patterns.nonInteractiveFlag) {
436
+ structure.executionPattern = "argument-based";
437
+ } else {
438
+ structure.executionPattern = "interactive-default";
439
+ }
440
+
441
+ return structure;
442
+ }
443
+
444
+ /**
445
+ * Extract usage examples from help text
446
+ */
447
+ extractUsageExamples(helpText, cliType) {
448
+ const rules = this.patternRules[cliType] || this.patternRules.generic;
449
+ const examples = [];
450
+
451
+ // Find example sections
452
+ const exampleMatches = helpText.match(rules.examplePattern);
453
+
454
+ if (exampleMatches) {
455
+ for (const match of exampleMatches) {
456
+ const exampleText = match
457
+ .replace(/^(example|usage|用法|使用)[::]\s*/i, "")
458
+ .trim();
459
+
460
+ // Split by lines and extract command examples
461
+ const lines = exampleText
462
+ .split("\n")
463
+ .map((line) => line.trim())
464
+ .filter(
465
+ (line) => line && !line.startsWith("#") && !line.startsWith("//"),
466
+ );
467
+
468
+ for (const line of lines) {
469
+ if (
470
+ line.includes("$") ||
471
+ line.includes(">") ||
472
+ line.startsWith("cli") ||
473
+ /^[a-z][\w-]*\s/.test(line)
474
+ ) {
475
+ // Extract clean command
476
+ const command = line
477
+ .replace(/^[>$\s]+/, "")
478
+ .replace(/^cli\s*/, "")
479
+ .trim();
480
+ if (command) {
481
+ examples.push({
482
+ command,
483
+ raw: line,
484
+ description: "",
485
+ });
486
+ }
487
+ }
488
+ }
489
+ }
490
+ }
491
+
492
+ return examples;
493
+ }
494
+
495
+ /**
496
+ * Determine CLI interaction mode
497
+ */
498
+ determineInteractionMode(helpInfo, patterns) {
499
+ const text = helpInfo.rawHelp.toLowerCase();
500
+
501
+ // Check for different interaction modes
502
+ if (
503
+ text.includes("chat") ||
504
+ text.includes("conversation") ||
505
+ text.includes("interactive")
506
+ ) {
507
+ return "chat";
508
+ }
509
+
510
+ if (
511
+ text.includes("api") ||
512
+ text.includes("endpoint") ||
513
+ text.includes("request")
514
+ ) {
515
+ return "api";
516
+ }
517
+
518
+ if (patterns.subcommands.length > 0) {
519
+ return "subcommand";
520
+ }
521
+
522
+ if (patterns.options.length > 5) {
523
+ return "option";
524
+ }
525
+
526
+ return "simple";
527
+ }
528
+
529
+ /**
530
+ * Cache analysis results
531
+ */
532
+ async cacheAnalysis(cliName, analysis) {
533
+ try {
534
+ const config = await this.loadPersistentConfig();
535
+ config.cliPatterns[cliName] = analysis;
536
+ config.lastUpdated = new Date().toISOString();
537
+
538
+ // Remove from failed attempts if it was there
539
+ delete config.failedAttempts[cliName];
540
+
541
+ await this.savePersistentConfig(config);
542
+
543
+ // Also save last analysis timestamp
544
+ await fs.writeFile(
545
+ this.lastAnalysisFile,
546
+ JSON.stringify({ [cliName]: analysis.timestamp }, null, 2),
547
+ );
548
+ } catch (error) {
549
+ console.error(`Failed to cache analysis for ${cliName}:`, error.message);
550
+ }
551
+ }
552
+
553
+ /**
554
+ * Get cached analysis if available
555
+ */
556
+ async getCachedAnalysis(cliName) {
557
+ try {
558
+ const config = await this.loadPersistentConfig();
559
+ return config.cliPatterns[cliName] || null;
560
+ } catch (error) {
561
+ return null;
562
+ }
563
+ }
564
+
565
+ /**
566
+ * Check if cache is expired (24 hours)
567
+ */
568
+ isCacheExpired(timestamp) {
569
+ const cacheTime = new Date(timestamp);
570
+ const now = new Date();
571
+ const hoursDiff = (now - cacheTime) / (1000 * 60 * 60);
572
+ return hoursDiff > 24;
573
+ }
574
+
575
+ /**
576
+ * Record failed analysis attempt
577
+ */
578
+ async recordFailedAttempt(cliName, error) {
579
+ try {
580
+ const config = await this.loadPersistentConfig();
581
+ config.failedAttempts[cliName] = {
582
+ error: error.message,
583
+ timestamp: new Date().toISOString(),
584
+ attempts: (config.failedAttempts[cliName]?.attempts || 0) + 1,
585
+ };
586
+ config.lastUpdated = new Date().toISOString();
587
+ await this.savePersistentConfig(config);
588
+ } catch (err) {
589
+ console.error("Failed to record failed attempt:", err.message);
590
+ }
591
+ }
592
+
593
+ /**
594
+ * Load persistent configuration
595
+ */
596
+ async loadPersistentConfig() {
597
+ try {
598
+ const data = await fs.readFile(this.persistentConfig, "utf8");
599
+ return JSON.parse(data);
600
+ } catch (error) {
601
+ return {
602
+ version: "1.0.0",
603
+ lastUpdated: new Date().toISOString(),
604
+ cliPatterns: {},
605
+ failedAttempts: {},
606
+ };
607
+ }
608
+ }
609
+
610
+ /**
611
+ * Save persistent configuration
612
+ */
613
+ async savePersistentConfig(config) {
614
+ await fs.writeFile(this.persistentConfig, JSON.stringify(config, null, 2));
615
+ }
616
+
617
+ /**
618
+ * Check if file exists
619
+ */
620
+ async fileExists(filePath) {
621
+ try {
622
+ await fs.access(filePath);
623
+ return true;
624
+ } catch {
625
+ return false;
626
+ }
627
+ }
628
+
629
+ /**
630
+ * Get CLI pattern for specific tool
631
+ */
632
+ async getCLIPattern(cliName) {
633
+ const cached = await this.getCachedAnalysis(cliName);
634
+
635
+ if (cached && !this.isCacheExpired(cached.timestamp)) {
636
+ return cached;
637
+ }
638
+
639
+ // Re-analyze if cache expired or not available
640
+ return await this.analyzeCLI(cliName);
641
+ }
642
+
643
+ /**
644
+ * Update CLI pattern when call fails
645
+ */
646
+ async updatePatternOnFailure(cliName, error, attemptedCommand) {
647
+ console.log(
648
+ `Updating pattern for ${cliName} due to failure:`,
649
+ error.message,
650
+ );
651
+
652
+ try {
653
+ // Re-analyze the CLI
654
+ const newAnalysis = await this.analyzeCLI(cliName);
655
+
656
+ // Add failure context
657
+ newAnalysis.lastFailure = {
658
+ error: error.message,
659
+ attemptedCommand,
660
+ timestamp: new Date().toISOString(),
661
+ };
662
+
663
+ // Update the cached analysis
664
+ await this.cacheAnalysis(cliName, newAnalysis);
665
+
666
+ return newAnalysis;
667
+ } catch (analysisError) {
668
+ console.error(`Failed to re-analyze ${cliName}:`, analysisError.message);
669
+ return null;
670
+ }
671
+ }
672
+ }
673
+
674
+ module.exports = CLIHelpAnalyzer;