stigmergy 1.0.95 → 1.0.98

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