stigmergy 1.2.6 → 1.2.8
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.
- package/README.md +32 -17
- package/STIGMERGY.md +16 -7
- package/docs/MULTI_USER_WIKI_COLLABORATION_SYSTEM.md +523 -0
- package/docs/PROMPT_BASED_SKILLS_SYSTEM_DESIGN.md +458 -0
- package/docs/SKILL_IMPLEMENTATION_CONSTRAINTS_AND_ALIGNMENT.md +423 -0
- package/docs/TECHNICAL_FEASIBILITY_ANALYSIS.md +308 -0
- package/examples/multilingual-hook-demo.js +125 -0
- package/package.json +14 -17
- package/scripts/dependency-analyzer.js +101 -0
- package/scripts/generate-cli-docs.js +64 -0
- package/scripts/postuninstall.js +46 -0
- package/scripts/preuninstall.js +75 -0
- package/scripts/run-layered-tests.js +3 -3
- package/src/adapters/claude/install_claude_integration.js +17 -17
- package/src/adapters/codebuddy/install_codebuddy_integration.js +13 -13
- package/src/adapters/codex/install_codex_integration.js +27 -27
- package/src/adapters/copilot/install_copilot_integration.js +46 -46
- package/src/adapters/gemini/install_gemini_integration.js +10 -10
- package/src/adapters/iflow/install_iflow_integration.js +7 -7
- package/src/adapters/qoder/install_qoder_integration.js +12 -12
- package/src/adapters/qwen/install_qwen_integration.js +17 -17
- package/src/auth.js +173 -173
- package/src/auth_command.js +208 -208
- package/src/calculator.js +313 -313
- package/src/cli/router.js +151 -7
- package/src/core/cache_cleaner.js +767 -767
- package/src/core/cli_help_analyzer.js +680 -680
- package/src/core/cli_parameter_handler.js +132 -132
- package/src/core/cli_tools.js +89 -89
- package/src/core/coordination/index.js +16 -16
- package/src/core/coordination/nodejs/AdapterManager.js +102 -102
- package/src/core/coordination/nodejs/CLCommunication.js +132 -132
- package/src/core/coordination/nodejs/CLIIntegrationManager.js +272 -272
- package/src/core/coordination/nodejs/HealthChecker.js +76 -76
- package/src/core/coordination/nodejs/HookDeploymentManager.js +463 -274
- package/src/core/coordination/nodejs/StatisticsCollector.js +71 -71
- package/src/core/coordination/nodejs/index.js +90 -90
- package/src/core/coordination/nodejs/utils/Logger.js +29 -29
- package/src/core/enhanced_installer.js +479 -479
- package/src/core/enhanced_uninstaller.js +638 -638
- package/src/core/error_handler.js +406 -406
- package/src/core/installer.js +32 -32
- package/src/core/memory_manager.js +83 -83
- package/src/core/multilingual/language-pattern-manager.js +172 -0
- package/src/core/rest_client.js +160 -160
- package/src/core/smart_router.js +261 -249
- package/src/core/upgrade_manager.js +48 -20
- package/src/data_encryption.js +143 -143
- package/src/data_structures.js +440 -440
- package/src/deploy.js +55 -55
- package/src/index.js +30 -30
- package/src/test/cli-availability-checker.js +194 -194
- package/src/test/test-environment.js +289 -289
- package/src/utils/helpers.js +35 -35
- package/src/utils.js +921 -921
- package/src/weatherProcessor.js +228 -228
- package/test/multilingual/hook-deployment.test.js +91 -0
- package/test/multilingual/language-pattern-manager.test.js +140 -0
- package/test/multilingual/system-test.js +85 -0
|
@@ -1,680 +1,680 @@
|
|
|
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 } = 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
|
-
async initialize() {
|
|
54
|
-
try {
|
|
55
|
-
await fs.mkdir(this.configDir, { recursive: true });
|
|
56
|
-
// Initialize persistent config if not exists
|
|
57
|
-
const configExists = await this.fileExists(this.persistentConfig);
|
|
58
|
-
if (!configExists) {
|
|
59
|
-
const initialConfig = {
|
|
60
|
-
version: '1.0.0',
|
|
61
|
-
lastUpdated: new Date().toISOString(),
|
|
62
|
-
cliPatterns: {},
|
|
63
|
-
failedAttempts: {},
|
|
64
|
-
};
|
|
65
|
-
// Suppress error if file creation fails initially
|
|
66
|
-
try {
|
|
67
|
-
await this.savePersistentConfig(initialConfig);
|
|
68
|
-
} catch (createError) {
|
|
69
|
-
// Only show error if it's not a permissions issue or similar expected issue
|
|
70
|
-
if (
|
|
71
|
-
!createError.message.includes('EACCES') &&
|
|
72
|
-
!createError.message.includes('EPERM')
|
|
73
|
-
) {
|
|
74
|
-
await errorHandler.logError(
|
|
75
|
-
createError,
|
|
76
|
-
'ERROR',
|
|
77
|
-
'CLIHelpAnalyzer.initialize',
|
|
78
|
-
);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
return true;
|
|
83
|
-
} catch (error) {
|
|
84
|
-
// Don't spam error messages on initialization issues
|
|
85
|
-
// These are often expected on first run
|
|
86
|
-
if (process.env.DEBUG === 'true') {
|
|
87
|
-
await errorHandler.logError(
|
|
88
|
-
error,
|
|
89
|
-
'ERROR',
|
|
90
|
-
'CLIHelpAnalyzer.initialize',
|
|
91
|
-
);
|
|
92
|
-
}
|
|
93
|
-
return false;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* Analyze all configured CLI tools with optimized error handling
|
|
99
|
-
*/
|
|
100
|
-
async analyzeAllCLI() {
|
|
101
|
-
const results = {};
|
|
102
|
-
for (const [cliName, _] of Object.entries(this.cliTools)) {
|
|
103
|
-
try {
|
|
104
|
-
if (process.env.DEBUG === 'true') {
|
|
105
|
-
console.log(`Analyzing ${cliName}...`);
|
|
106
|
-
}
|
|
107
|
-
results[cliName] = await this.analyzeCLI(cliName);
|
|
108
|
-
} catch (error) {
|
|
109
|
-
// Only log important errors, suppress expected file not found errors
|
|
110
|
-
if (
|
|
111
|
-
!error.message.includes('ENOENT') &&
|
|
112
|
-
!error.message.includes('no such file or directory') &&
|
|
113
|
-
!error.message.includes(
|
|
114
|
-
'not recognized as an internal or external command',
|
|
115
|
-
)
|
|
116
|
-
) {
|
|
117
|
-
await errorHandler.logError(
|
|
118
|
-
error,
|
|
119
|
-
'WARN',
|
|
120
|
-
`CLIHelpAnalyzer.analyzeAllCLI.${cliName}`,
|
|
121
|
-
);
|
|
122
|
-
}
|
|
123
|
-
results[cliName] = { success: false, error: error.message };
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
return results;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* Analyze specific CLI tool
|
|
131
|
-
*/
|
|
132
|
-
async analyzeCLI(cliName) {
|
|
133
|
-
const cliConfig = this.cliTools[cliName];
|
|
134
|
-
if (!cliConfig) {
|
|
135
|
-
throw new Error(`CLI tool ${cliName} not found in configuration`);
|
|
136
|
-
}
|
|
137
|
-
try {
|
|
138
|
-
// Get help information
|
|
139
|
-
const helpInfo = await this.getHelpInfo(cliName, cliConfig);
|
|
140
|
-
// Detect CLI type
|
|
141
|
-
const cliType = this.detectCLIType(helpInfo.rawHelp, cliName);
|
|
142
|
-
// Extract patterns
|
|
143
|
-
const patterns = this.extractPatterns(helpInfo.rawHelp, cliType, cliName);
|
|
144
|
-
// Analyze command structure
|
|
145
|
-
const commandStructure = this.analyzeCommandStructure(patterns);
|
|
146
|
-
// Extract usage examples
|
|
147
|
-
const examples = this.extractUsageExamples(helpInfo.rawHelp, cliType);
|
|
148
|
-
// Determine interaction mode
|
|
149
|
-
const interactionMode = this.determineInteractionMode(helpInfo, patterns);
|
|
150
|
-
const analysis = {
|
|
151
|
-
success: true,
|
|
152
|
-
cliName,
|
|
153
|
-
cliType,
|
|
154
|
-
version: helpInfo.version,
|
|
155
|
-
helpMethod: helpInfo.method,
|
|
156
|
-
patterns,
|
|
157
|
-
commandStructure,
|
|
158
|
-
examples,
|
|
159
|
-
interactionMode,
|
|
160
|
-
timestamp: new Date().toISOString(),
|
|
161
|
-
};
|
|
162
|
-
// Cache the analysis
|
|
163
|
-
await this.cacheAnalysis(cliName, analysis);
|
|
164
|
-
return analysis;
|
|
165
|
-
} catch (error) {
|
|
166
|
-
// Record failed attempt but suppress error if it's an expected issue
|
|
167
|
-
await this.recordFailedAttempt(cliName, error);
|
|
168
|
-
// Only throw if it's not an expected "tool not installed" error
|
|
169
|
-
if (
|
|
170
|
-
!error.message.includes(
|
|
171
|
-
'not recognized as an internal or external command',
|
|
172
|
-
) &&
|
|
173
|
-
!error.message.includes('command not found')
|
|
174
|
-
) {
|
|
175
|
-
throw error;
|
|
176
|
-
}
|
|
177
|
-
// For tool not found errors, return minimal analysis instead of throwing
|
|
178
|
-
return {
|
|
179
|
-
success: false,
|
|
180
|
-
cliName,
|
|
181
|
-
error: error.message,
|
|
182
|
-
timestamp: new Date().toISOString(),
|
|
183
|
-
};
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
/**
|
|
188
|
-
* Get help information using multiple methods
|
|
189
|
-
*/
|
|
190
|
-
async getHelpInfo(cliName, cliConfig) {
|
|
191
|
-
// Special handling for codex to avoid opening files or executing commands
|
|
192
|
-
if (cliName === 'codex') {
|
|
193
|
-
return await this.getCodexHelpInfo(cliConfig);
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
const helpMethods = [
|
|
197
|
-
['--help'],
|
|
198
|
-
['-h'],
|
|
199
|
-
['help'],
|
|
200
|
-
['--usage'],
|
|
201
|
-
[''],
|
|
202
|
-
['version'],
|
|
203
|
-
['--version'],
|
|
204
|
-
['-v'],
|
|
205
|
-
];
|
|
206
|
-
let rawHelp = '';
|
|
207
|
-
let version = 'unknown';
|
|
208
|
-
let method = 'unknown';
|
|
209
|
-
// Try different help commands
|
|
210
|
-
for (const helpArgs of helpMethods) {
|
|
211
|
-
try {
|
|
212
|
-
const result = spawnSync(cliName, helpArgs, {
|
|
213
|
-
encoding: 'utf8',
|
|
214
|
-
timeout: 15000,
|
|
215
|
-
shell: true,
|
|
216
|
-
});
|
|
217
|
-
if (result.status === 0 && result.stdout) {
|
|
218
|
-
rawHelp = result.stdout;
|
|
219
|
-
method = `${cliName} ${helpArgs.join(' ')}`;
|
|
220
|
-
break;
|
|
221
|
-
} else if (result.stderr) {
|
|
222
|
-
rawHelp = result.stderr;
|
|
223
|
-
method = `${cliName} ${helpArgs.join(' ')} (stderr)`;
|
|
224
|
-
break;
|
|
225
|
-
}
|
|
226
|
-
} catch (error) {
|
|
227
|
-
// Try next method
|
|
228
|
-
continue;
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
// Try to get version separately
|
|
232
|
-
if (cliConfig.version) {
|
|
233
|
-
try {
|
|
234
|
-
const versionCmd = cliConfig.version.split(' ');
|
|
235
|
-
const versionResult = spawnSync(versionCmd[0], versionCmd.slice(1), {
|
|
236
|
-
encoding: 'utf8',
|
|
237
|
-
timeout: 10000,
|
|
238
|
-
shell: true,
|
|
239
|
-
});
|
|
240
|
-
if (versionResult.status === 0) {
|
|
241
|
-
version = versionResult.stdout.trim() || versionResult.stderr.trim();
|
|
242
|
-
}
|
|
243
|
-
} catch (error) {
|
|
244
|
-
// Use default version
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
if (!rawHelp) {
|
|
248
|
-
throw new Error(`Unable to get help information for ${cliName}`);
|
|
249
|
-
}
|
|
250
|
-
return { rawHelp, version, method };
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
/**
|
|
254
|
-
* Get Codex help information by checking file paths instead of executing commands
|
|
255
|
-
*/
|
|
256
|
-
async getCodexHelpInfo(cliConfig) {
|
|
257
|
-
try {
|
|
258
|
-
// Check if codex is likely installed by checking common installation paths
|
|
259
|
-
const os = require('os');
|
|
260
|
-
const path = require('path');
|
|
261
|
-
const fs = require('fs');
|
|
262
|
-
|
|
263
|
-
// Common Codex installation paths
|
|
264
|
-
const possiblePaths = [
|
|
265
|
-
path.join(os.homedir(), '.codex'),
|
|
266
|
-
path.join(os.homedir(), '.config', 'codex'),
|
|
267
|
-
path.join(
|
|
268
|
-
os.homedir(),
|
|
269
|
-
'AppData',
|
|
270
|
-
'Roaming',
|
|
271
|
-
'npm',
|
|
272
|
-
'node_modules',
|
|
273
|
-
'openai
|
|
274
|
-
),
|
|
275
|
-
path.join('/usr/local/lib/node_modules/
|
|
276
|
-
path.join('/usr/lib/node_modules/
|
|
277
|
-
];
|
|
278
|
-
|
|
279
|
-
// Check if any of these paths exist
|
|
280
|
-
let codexInstalled = false;
|
|
281
|
-
let configPath = '';
|
|
282
|
-
|
|
283
|
-
for (const possiblePath of possiblePaths) {
|
|
284
|
-
if (fs.existsSync(possiblePath)) {
|
|
285
|
-
codexInstalled = true;
|
|
286
|
-
configPath = possiblePath;
|
|
287
|
-
break;
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
// If Codex is not installed, throw an error without trying to execute commands
|
|
292
|
-
if (!codexInstalled) {
|
|
293
|
-
throw new Error('Codex CLI not found in standard installation paths');
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
// If Codex appears to be installed, return minimal help information
|
|
297
|
-
// without actually executing any commands
|
|
298
|
-
return {
|
|
299
|
-
rawHelp:
|
|
300
|
-
'Codex CLI - OpenAI Codex Command Line Interface\nUsage: codex [options] [prompt]\n\nOptions:\n --help, -h Show help\n --version Show version\n --model Specify model to use\n --temperature Set temperature for generation',
|
|
301
|
-
version: 'unknown',
|
|
302
|
-
method: 'path-detection',
|
|
303
|
-
};
|
|
304
|
-
} catch (error) {
|
|
305
|
-
throw new Error(
|
|
306
|
-
`Unable to get help information for codex: ${error.message}`,
|
|
307
|
-
);
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
/**
|
|
312
|
-
* Detect CLI type based on help content and naming
|
|
313
|
-
*/
|
|
314
|
-
detectCLIType(helpText, cliName) {
|
|
315
|
-
const text = helpText.toLowerCase();
|
|
316
|
-
const name = cliName.toLowerCase();
|
|
317
|
-
// Detect based on CLI name
|
|
318
|
-
if (name.includes('claude') || name.includes('anthropic')) {
|
|
319
|
-
return 'anthropic';
|
|
320
|
-
}
|
|
321
|
-
if (name.includes('gemini') || name.includes('google')) {
|
|
322
|
-
return 'google';
|
|
323
|
-
}
|
|
324
|
-
if (
|
|
325
|
-
name.includes('codex') ||
|
|
326
|
-
name.includes('openai') ||
|
|
327
|
-
name.includes('chatgpt')
|
|
328
|
-
) {
|
|
329
|
-
return 'openai';
|
|
330
|
-
}
|
|
331
|
-
// Detect based on help content patterns
|
|
332
|
-
if (text.includes('anthropic') || text.includes('claude')) {
|
|
333
|
-
return 'anthropic';
|
|
334
|
-
}
|
|
335
|
-
if (
|
|
336
|
-
text.includes('google') ||
|
|
337
|
-
text.includes('gemini') ||
|
|
338
|
-
text.includes('vertex')
|
|
339
|
-
) {
|
|
340
|
-
return 'google';
|
|
341
|
-
}
|
|
342
|
-
if (
|
|
343
|
-
text.includes('openai') ||
|
|
344
|
-
text.includes('gpt') ||
|
|
345
|
-
text.includes('codex')
|
|
346
|
-
) {
|
|
347
|
-
return 'openai';
|
|
348
|
-
}
|
|
349
|
-
if (
|
|
350
|
-
text.includes('qwen') ||
|
|
351
|
-
text.includes('alibaba') ||
|
|
352
|
-
text.includes('tongyi')
|
|
353
|
-
) {
|
|
354
|
-
return 'alibaba';
|
|
355
|
-
}
|
|
356
|
-
if (text.includes('iflow') || text.includes('intelligent')) {
|
|
357
|
-
return 'iflow';
|
|
358
|
-
}
|
|
359
|
-
if (text.includes('copilot') || text.includes('github')) {
|
|
360
|
-
return 'github';
|
|
361
|
-
}
|
|
362
|
-
if (text.includes('codebuddy') || text.includes('buddy')) {
|
|
363
|
-
return 'codebuddy';
|
|
364
|
-
}
|
|
365
|
-
if (text.includes('qoder') || text.includes('code')) {
|
|
366
|
-
return 'qoder';
|
|
367
|
-
}
|
|
368
|
-
// Default to generic
|
|
369
|
-
return 'generic';
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
/**
|
|
373
|
-
* Extract patterns from help text
|
|
374
|
-
*/
|
|
375
|
-
extractPatterns(helpText, cliType, cliName) {
|
|
376
|
-
const rules = this.patternRules[cliType] || this.patternRules.generic;
|
|
377
|
-
const patterns = {
|
|
378
|
-
commands: [],
|
|
379
|
-
options: [],
|
|
380
|
-
subcommands: [],
|
|
381
|
-
arguments: [],
|
|
382
|
-
flags: [],
|
|
383
|
-
nonInteractiveFlag: null,
|
|
384
|
-
promptFlag: null,
|
|
385
|
-
requiredFlags: [],
|
|
386
|
-
commonPatterns: [],
|
|
387
|
-
};
|
|
388
|
-
|
|
389
|
-
// Extract options/flags
|
|
390
|
-
const optionMatches = helpText.match(rules.optionPattern) || [];
|
|
391
|
-
patterns.options = [...new Set(optionMatches)];
|
|
392
|
-
|
|
393
|
-
// Extract subcommands
|
|
394
|
-
const subcommandMatches = helpText.match(rules.subcommandPattern) || [];
|
|
395
|
-
patterns.subcommands = subcommandMatches.map((match) => {
|
|
396
|
-
const subcommand = match.trim().split(/\s+/)[0];
|
|
397
|
-
return { name: subcommand, description: match.trim() };
|
|
398
|
-
});
|
|
399
|
-
|
|
400
|
-
// Try to identify non-interactive flag
|
|
401
|
-
const nonInteractiveFlags = patterns.options.filter(
|
|
402
|
-
(option) =>
|
|
403
|
-
option.includes('non-interactive') ||
|
|
404
|
-
option.includes('batch') ||
|
|
405
|
-
option.includes('no-input') ||
|
|
406
|
-
option.includes('stdin'),
|
|
407
|
-
);
|
|
408
|
-
patterns.nonInteractiveFlag =
|
|
409
|
-
nonInteractiveFlags.length > 0 ? nonInteractiveFlags[0] : null;
|
|
410
|
-
|
|
411
|
-
// Try to identify prompt flag
|
|
412
|
-
const promptFlags = patterns.options.filter(
|
|
413
|
-
(option) =>
|
|
414
|
-
option.includes('prompt') ||
|
|
415
|
-
option.includes('input') ||
|
|
416
|
-
option.includes('query') ||
|
|
417
|
-
option.includes('question'),
|
|
418
|
-
);
|
|
419
|
-
patterns.promptFlag = promptFlags.length > 0 ? promptFlags[0] : null;
|
|
420
|
-
|
|
421
|
-
// Identify required flags
|
|
422
|
-
patterns.requiredFlags = patterns.options.filter(
|
|
423
|
-
(option) =>
|
|
424
|
-
option.includes('<') || // Angle brackets indicate required parameters
|
|
425
|
-
option.includes('*'), // Asterisk indicates required
|
|
426
|
-
);
|
|
427
|
-
|
|
428
|
-
return patterns;
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
/**
|
|
432
|
-
* Analyze command structure
|
|
433
|
-
*/
|
|
434
|
-
analyzeCommandStructure(patterns) {
|
|
435
|
-
const structure = {
|
|
436
|
-
primaryCommand: '',
|
|
437
|
-
commandFormat: 'cli [args]',
|
|
438
|
-
argumentStyle: '',
|
|
439
|
-
optionStyle: '',
|
|
440
|
-
interactiveMode: false,
|
|
441
|
-
hasSubcommands: patterns.subcommands.length > 0,
|
|
442
|
-
complexity: 'simple',
|
|
443
|
-
nonInteractiveSupport: !!patterns.nonInteractiveFlag,
|
|
444
|
-
promptStyle: patterns.promptFlag ? 'flag' : 'argument',
|
|
445
|
-
executionPattern: 'interactive-default',
|
|
446
|
-
nonInteractiveFlag: patterns.nonInteractiveFlag,
|
|
447
|
-
promptFlag: patterns.promptFlag,
|
|
448
|
-
requiredFlags: patterns.requiredFlags,
|
|
449
|
-
commonPatterns: patterns.commonPatterns,
|
|
450
|
-
};
|
|
451
|
-
|
|
452
|
-
// Determine complexity
|
|
453
|
-
if (patterns.subcommands.length > 5) {
|
|
454
|
-
structure.complexity = 'complex';
|
|
455
|
-
} else if (patterns.subcommands.length > 0) {
|
|
456
|
-
structure.complexity = 'moderate';
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
// Determine execution pattern
|
|
460
|
-
if (patterns.nonInteractiveFlag) {
|
|
461
|
-
structure.executionPattern = 'flag-based';
|
|
462
|
-
} else if (patterns.promptFlag) {
|
|
463
|
-
structure.executionPattern = 'prompt-flag';
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
return structure;
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
/**
|
|
470
|
-
* Extract usage examples
|
|
471
|
-
*/
|
|
472
|
-
extractUsageExamples(helpText, cliType) {
|
|
473
|
-
const rules = this.patternRules[cliType] || this.patternRules.generic;
|
|
474
|
-
const examples = [];
|
|
475
|
-
let match;
|
|
476
|
-
|
|
477
|
-
while ((match = rules.examplePattern.exec(helpText)) !== null) {
|
|
478
|
-
examples.push(match[1].trim());
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
return examples;
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
/**
|
|
485
|
-
* Determine interaction mode
|
|
486
|
-
*/
|
|
487
|
-
determineInteractionMode(helpInfo, patterns) {
|
|
488
|
-
const helpText = helpInfo.rawHelp.toLowerCase();
|
|
489
|
-
|
|
490
|
-
// Check for explicit non-interactive support
|
|
491
|
-
if (patterns.nonInteractiveFlag) {
|
|
492
|
-
return 'non-interactive';
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
// Check for stdin support
|
|
496
|
-
if (helpText.includes('stdin') || helpText.includes('pipe')) {
|
|
497
|
-
return 'stdin-support';
|
|
498
|
-
}
|
|
499
|
-
|
|
500
|
-
// Check for batch mode
|
|
501
|
-
if (helpText.includes('batch') || helpText.includes('script')) {
|
|
502
|
-
return 'batch-mode';
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
// Default to interactive
|
|
506
|
-
return 'interactive';
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
/**
|
|
510
|
-
* Cache analysis results
|
|
511
|
-
*/
|
|
512
|
-
async cacheAnalysis(cliName, analysis) {
|
|
513
|
-
try {
|
|
514
|
-
const config = await this.loadPersistentConfig();
|
|
515
|
-
config.cliPatterns[cliName] = analysis;
|
|
516
|
-
config.lastUpdated = new Date().toISOString();
|
|
517
|
-
await this.savePersistentConfig(config);
|
|
518
|
-
} catch (error) {
|
|
519
|
-
// Don't spam errors for cache issues
|
|
520
|
-
if (process.env.DEBUG === 'true') {
|
|
521
|
-
await errorHandler.logError(
|
|
522
|
-
error,
|
|
523
|
-
'WARN',
|
|
524
|
-
'CLIHelpAnalyzer.cacheAnalysis',
|
|
525
|
-
);
|
|
526
|
-
}
|
|
527
|
-
}
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
/**
|
|
531
|
-
* Get cached analysis
|
|
532
|
-
*/
|
|
533
|
-
async getCachedAnalysis(cliName) {
|
|
534
|
-
try {
|
|
535
|
-
const config = await this.loadPersistentConfig();
|
|
536
|
-
return config.cliPatterns[cliName] || null;
|
|
537
|
-
} catch (error) {
|
|
538
|
-
return null;
|
|
539
|
-
}
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
/**
|
|
543
|
-
* Get CLI pattern (wrapper for getCachedAnalysis)
|
|
544
|
-
*/
|
|
545
|
-
async getCLIPattern(cliName) {
|
|
546
|
-
return await this.getCachedAnalysis(cliName);
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
/**
|
|
550
|
-
* Check if cache is expired (1 day)
|
|
551
|
-
*/
|
|
552
|
-
isCacheExpired(timestamp) {
|
|
553
|
-
if (!timestamp) return true;
|
|
554
|
-
const cacheTime = new Date(timestamp);
|
|
555
|
-
const now = new Date();
|
|
556
|
-
const diffHours = (now - cacheTime) / (1000 * 60 * 60);
|
|
557
|
-
return diffHours > 24; // Expire after 24 hours
|
|
558
|
-
}
|
|
559
|
-
|
|
560
|
-
/**
|
|
561
|
-
* Load persistent configuration
|
|
562
|
-
*/
|
|
563
|
-
async loadPersistentConfig() {
|
|
564
|
-
try {
|
|
565
|
-
const configExists = await this.fileExists(this.persistentConfig);
|
|
566
|
-
if (!configExists) {
|
|
567
|
-
return {
|
|
568
|
-
version: '1.0.0',
|
|
569
|
-
lastUpdated: new Date().toISOString(),
|
|
570
|
-
cliPatterns: {},
|
|
571
|
-
failedAttempts: {},
|
|
572
|
-
};
|
|
573
|
-
}
|
|
574
|
-
const configData = await fs.readFile(this.persistentConfig, 'utf8');
|
|
575
|
-
return JSON.parse(configData);
|
|
576
|
-
} catch (error) {
|
|
577
|
-
// Return default config if loading fails
|
|
578
|
-
return {
|
|
579
|
-
version: '1.0.0',
|
|
580
|
-
lastUpdated: new Date().toISOString(),
|
|
581
|
-
cliPatterns: {},
|
|
582
|
-
failedAttempts: {},
|
|
583
|
-
};
|
|
584
|
-
}
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
/**
|
|
588
|
-
* Save persistent configuration
|
|
589
|
-
*/
|
|
590
|
-
async savePersistentConfig(config) {
|
|
591
|
-
try {
|
|
592
|
-
await fs.writeFile(
|
|
593
|
-
this.persistentConfig,
|
|
594
|
-
JSON.stringify(config, null, 2),
|
|
595
|
-
);
|
|
596
|
-
} catch (error) {
|
|
597
|
-
throw error;
|
|
598
|
-
}
|
|
599
|
-
}
|
|
600
|
-
|
|
601
|
-
/**
|
|
602
|
-
* Record failed attempt
|
|
603
|
-
*/
|
|
604
|
-
async recordFailedAttempt(cliName, error) {
|
|
605
|
-
try {
|
|
606
|
-
const config = await this.loadPersistentConfig();
|
|
607
|
-
config.failedAttempts[cliName] = {
|
|
608
|
-
error: error.message,
|
|
609
|
-
timestamp: new Date().toISOString(),
|
|
610
|
-
attempts: (config.failedAttempts[cliName]?.attempts || 0) + 1,
|
|
611
|
-
};
|
|
612
|
-
await this.savePersistentConfig(config);
|
|
613
|
-
} catch (saveError) {
|
|
614
|
-
// Don't spam errors for failed attempt recording
|
|
615
|
-
if (process.env.DEBUG === 'true') {
|
|
616
|
-
await errorHandler.logError(
|
|
617
|
-
saveError,
|
|
618
|
-
'WARN',
|
|
619
|
-
'CLIHelpAnalyzer.recordFailedAttempt',
|
|
620
|
-
);
|
|
621
|
-
}
|
|
622
|
-
}
|
|
623
|
-
}
|
|
624
|
-
|
|
625
|
-
/**
|
|
626
|
-
* Update CLI pattern when call fails
|
|
627
|
-
*/
|
|
628
|
-
async updatePatternOnFailure(cliName, error, attemptedCommand) {
|
|
629
|
-
// Only log in debug mode to reduce console noise
|
|
630
|
-
if (process.env.DEBUG === 'true') {
|
|
631
|
-
console.log(
|
|
632
|
-
`Updating pattern for ${cliName} due to failure:`,
|
|
633
|
-
error.message,
|
|
634
|
-
);
|
|
635
|
-
}
|
|
636
|
-
try {
|
|
637
|
-
// Re-analyze the CLI
|
|
638
|
-
const newAnalysis = await this.analyzeCLI(cliName);
|
|
639
|
-
// Add failure context
|
|
640
|
-
newAnalysis.lastFailure = {
|
|
641
|
-
error: error.message,
|
|
642
|
-
attemptedCommand,
|
|
643
|
-
timestamp: new Date().toISOString(),
|
|
644
|
-
};
|
|
645
|
-
// Update the cached analysis
|
|
646
|
-
await this.cacheAnalysis(cliName, newAnalysis);
|
|
647
|
-
return newAnalysis;
|
|
648
|
-
} catch (analysisError) {
|
|
649
|
-
// Only log analysis errors in debug mode
|
|
650
|
-
if (process.env.DEBUG === 'true') {
|
|
651
|
-
console.error(
|
|
652
|
-
`Failed to re-analyze ${cliName}:`,
|
|
653
|
-
analysisError.message,
|
|
654
|
-
);
|
|
655
|
-
}
|
|
656
|
-
return null;
|
|
657
|
-
}
|
|
658
|
-
}
|
|
659
|
-
|
|
660
|
-
/**
|
|
661
|
-
* Check if file exists
|
|
662
|
-
*/
|
|
663
|
-
async fileExists(filePath) {
|
|
664
|
-
try {
|
|
665
|
-
await fs.access(filePath);
|
|
666
|
-
return true;
|
|
667
|
-
} catch (error) {
|
|
668
|
-
return false;
|
|
669
|
-
}
|
|
670
|
-
}
|
|
671
|
-
|
|
672
|
-
/**
|
|
673
|
-
* Set CLI tools
|
|
674
|
-
*/
|
|
675
|
-
setCLITools(tools) {
|
|
676
|
-
this.cliTools = tools;
|
|
677
|
-
}
|
|
678
|
-
}
|
|
679
|
-
|
|
680
|
-
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 } = 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
|
+
async initialize() {
|
|
54
|
+
try {
|
|
55
|
+
await fs.mkdir(this.configDir, { recursive: true });
|
|
56
|
+
// Initialize persistent config if not exists
|
|
57
|
+
const configExists = await this.fileExists(this.persistentConfig);
|
|
58
|
+
if (!configExists) {
|
|
59
|
+
const initialConfig = {
|
|
60
|
+
version: '1.0.0',
|
|
61
|
+
lastUpdated: new Date().toISOString(),
|
|
62
|
+
cliPatterns: {},
|
|
63
|
+
failedAttempts: {},
|
|
64
|
+
};
|
|
65
|
+
// Suppress error if file creation fails initially
|
|
66
|
+
try {
|
|
67
|
+
await this.savePersistentConfig(initialConfig);
|
|
68
|
+
} catch (createError) {
|
|
69
|
+
// Only show error if it's not a permissions issue or similar expected issue
|
|
70
|
+
if (
|
|
71
|
+
!createError.message.includes('EACCES') &&
|
|
72
|
+
!createError.message.includes('EPERM')
|
|
73
|
+
) {
|
|
74
|
+
await errorHandler.logError(
|
|
75
|
+
createError,
|
|
76
|
+
'ERROR',
|
|
77
|
+
'CLIHelpAnalyzer.initialize',
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return true;
|
|
83
|
+
} catch (error) {
|
|
84
|
+
// Don't spam error messages on initialization issues
|
|
85
|
+
// These are often expected on first run
|
|
86
|
+
if (process.env.DEBUG === 'true') {
|
|
87
|
+
await errorHandler.logError(
|
|
88
|
+
error,
|
|
89
|
+
'ERROR',
|
|
90
|
+
'CLIHelpAnalyzer.initialize',
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Analyze all configured CLI tools with optimized error handling
|
|
99
|
+
*/
|
|
100
|
+
async analyzeAllCLI() {
|
|
101
|
+
const results = {};
|
|
102
|
+
for (const [cliName, _] of Object.entries(this.cliTools)) {
|
|
103
|
+
try {
|
|
104
|
+
if (process.env.DEBUG === 'true') {
|
|
105
|
+
console.log(`Analyzing ${cliName}...`);
|
|
106
|
+
}
|
|
107
|
+
results[cliName] = await this.analyzeCLI(cliName);
|
|
108
|
+
} catch (error) {
|
|
109
|
+
// Only log important errors, suppress expected file not found errors
|
|
110
|
+
if (
|
|
111
|
+
!error.message.includes('ENOENT') &&
|
|
112
|
+
!error.message.includes('no such file or directory') &&
|
|
113
|
+
!error.message.includes(
|
|
114
|
+
'not recognized as an internal or external command',
|
|
115
|
+
)
|
|
116
|
+
) {
|
|
117
|
+
await errorHandler.logError(
|
|
118
|
+
error,
|
|
119
|
+
'WARN',
|
|
120
|
+
`CLIHelpAnalyzer.analyzeAllCLI.${cliName}`,
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
results[cliName] = { success: false, error: error.message };
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return results;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Analyze specific CLI tool
|
|
131
|
+
*/
|
|
132
|
+
async analyzeCLI(cliName) {
|
|
133
|
+
const cliConfig = this.cliTools[cliName];
|
|
134
|
+
if (!cliConfig) {
|
|
135
|
+
throw new Error(`CLI tool ${cliName} not found in configuration`);
|
|
136
|
+
}
|
|
137
|
+
try {
|
|
138
|
+
// Get help information
|
|
139
|
+
const helpInfo = await this.getHelpInfo(cliName, cliConfig);
|
|
140
|
+
// Detect CLI type
|
|
141
|
+
const cliType = this.detectCLIType(helpInfo.rawHelp, cliName);
|
|
142
|
+
// Extract patterns
|
|
143
|
+
const patterns = this.extractPatterns(helpInfo.rawHelp, cliType, cliName);
|
|
144
|
+
// Analyze command structure
|
|
145
|
+
const commandStructure = this.analyzeCommandStructure(patterns);
|
|
146
|
+
// Extract usage examples
|
|
147
|
+
const examples = this.extractUsageExamples(helpInfo.rawHelp, cliType);
|
|
148
|
+
// Determine interaction mode
|
|
149
|
+
const interactionMode = this.determineInteractionMode(helpInfo, patterns);
|
|
150
|
+
const analysis = {
|
|
151
|
+
success: true,
|
|
152
|
+
cliName,
|
|
153
|
+
cliType,
|
|
154
|
+
version: helpInfo.version,
|
|
155
|
+
helpMethod: helpInfo.method,
|
|
156
|
+
patterns,
|
|
157
|
+
commandStructure,
|
|
158
|
+
examples,
|
|
159
|
+
interactionMode,
|
|
160
|
+
timestamp: new Date().toISOString(),
|
|
161
|
+
};
|
|
162
|
+
// Cache the analysis
|
|
163
|
+
await this.cacheAnalysis(cliName, analysis);
|
|
164
|
+
return analysis;
|
|
165
|
+
} catch (error) {
|
|
166
|
+
// Record failed attempt but suppress error if it's an expected issue
|
|
167
|
+
await this.recordFailedAttempt(cliName, error);
|
|
168
|
+
// Only throw if it's not an expected "tool not installed" error
|
|
169
|
+
if (
|
|
170
|
+
!error.message.includes(
|
|
171
|
+
'not recognized as an internal or external command',
|
|
172
|
+
) &&
|
|
173
|
+
!error.message.includes('command not found')
|
|
174
|
+
) {
|
|
175
|
+
throw error;
|
|
176
|
+
}
|
|
177
|
+
// For tool not found errors, return minimal analysis instead of throwing
|
|
178
|
+
return {
|
|
179
|
+
success: false,
|
|
180
|
+
cliName,
|
|
181
|
+
error: error.message,
|
|
182
|
+
timestamp: new Date().toISOString(),
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Get help information using multiple methods
|
|
189
|
+
*/
|
|
190
|
+
async getHelpInfo(cliName, cliConfig) {
|
|
191
|
+
// Special handling for codex to avoid opening files or executing commands
|
|
192
|
+
if (cliName === 'codex') {
|
|
193
|
+
return await this.getCodexHelpInfo(cliConfig);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const helpMethods = [
|
|
197
|
+
['--help'],
|
|
198
|
+
['-h'],
|
|
199
|
+
['help'],
|
|
200
|
+
['--usage'],
|
|
201
|
+
[''],
|
|
202
|
+
['version'],
|
|
203
|
+
['--version'],
|
|
204
|
+
['-v'],
|
|
205
|
+
];
|
|
206
|
+
let rawHelp = '';
|
|
207
|
+
let version = 'unknown';
|
|
208
|
+
let method = 'unknown';
|
|
209
|
+
// Try different help commands
|
|
210
|
+
for (const helpArgs of helpMethods) {
|
|
211
|
+
try {
|
|
212
|
+
const result = spawnSync(cliName, helpArgs, {
|
|
213
|
+
encoding: 'utf8',
|
|
214
|
+
timeout: 15000,
|
|
215
|
+
shell: true,
|
|
216
|
+
});
|
|
217
|
+
if (result.status === 0 && result.stdout) {
|
|
218
|
+
rawHelp = result.stdout;
|
|
219
|
+
method = `${cliName} ${helpArgs.join(' ')}`;
|
|
220
|
+
break;
|
|
221
|
+
} else if (result.stderr) {
|
|
222
|
+
rawHelp = result.stderr;
|
|
223
|
+
method = `${cliName} ${helpArgs.join(' ')} (stderr)`;
|
|
224
|
+
break;
|
|
225
|
+
}
|
|
226
|
+
} catch (error) {
|
|
227
|
+
// Try next method
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
// Try to get version separately
|
|
232
|
+
if (cliConfig.version) {
|
|
233
|
+
try {
|
|
234
|
+
const versionCmd = cliConfig.version.split(' ');
|
|
235
|
+
const versionResult = spawnSync(versionCmd[0], versionCmd.slice(1), {
|
|
236
|
+
encoding: 'utf8',
|
|
237
|
+
timeout: 10000,
|
|
238
|
+
shell: true,
|
|
239
|
+
});
|
|
240
|
+
if (versionResult.status === 0) {
|
|
241
|
+
version = versionResult.stdout.trim() || versionResult.stderr.trim();
|
|
242
|
+
}
|
|
243
|
+
} catch (error) {
|
|
244
|
+
// Use default version
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
if (!rawHelp) {
|
|
248
|
+
throw new Error(`Unable to get help information for ${cliName}`);
|
|
249
|
+
}
|
|
250
|
+
return { rawHelp, version, method };
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Get Codex help information by checking file paths instead of executing commands
|
|
255
|
+
*/
|
|
256
|
+
async getCodexHelpInfo(cliConfig) {
|
|
257
|
+
try {
|
|
258
|
+
// Check if codex is likely installed by checking common installation paths
|
|
259
|
+
const os = require('os');
|
|
260
|
+
const path = require('path');
|
|
261
|
+
const fs = require('fs');
|
|
262
|
+
|
|
263
|
+
// Common Codex installation paths
|
|
264
|
+
const possiblePaths = [
|
|
265
|
+
path.join(os.homedir(), '.codex'),
|
|
266
|
+
path.join(os.homedir(), '.config', 'codex'),
|
|
267
|
+
path.join(
|
|
268
|
+
os.homedir(),
|
|
269
|
+
'AppData',
|
|
270
|
+
'Roaming',
|
|
271
|
+
'npm',
|
|
272
|
+
'node_modules',
|
|
273
|
+
'@openai/codex',
|
|
274
|
+
),
|
|
275
|
+
path.join('/usr/local/lib/node_modules/@openai/codex'),
|
|
276
|
+
path.join('/usr/lib/node_modules/@openai/codex'),
|
|
277
|
+
];
|
|
278
|
+
|
|
279
|
+
// Check if any of these paths exist
|
|
280
|
+
let codexInstalled = false;
|
|
281
|
+
let configPath = '';
|
|
282
|
+
|
|
283
|
+
for (const possiblePath of possiblePaths) {
|
|
284
|
+
if (fs.existsSync(possiblePath)) {
|
|
285
|
+
codexInstalled = true;
|
|
286
|
+
configPath = possiblePath;
|
|
287
|
+
break;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// If Codex is not installed, throw an error without trying to execute commands
|
|
292
|
+
if (!codexInstalled) {
|
|
293
|
+
throw new Error('Codex CLI not found in standard installation paths');
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// If Codex appears to be installed, return minimal help information
|
|
297
|
+
// without actually executing any commands
|
|
298
|
+
return {
|
|
299
|
+
rawHelp:
|
|
300
|
+
'Codex CLI - OpenAI Codex Command Line Interface\nUsage: codex [options] [prompt]\n\nOptions:\n --help, -h Show help\n --version Show version\n --model Specify model to use\n --temperature Set temperature for generation',
|
|
301
|
+
version: 'unknown',
|
|
302
|
+
method: 'path-detection',
|
|
303
|
+
};
|
|
304
|
+
} catch (error) {
|
|
305
|
+
throw new Error(
|
|
306
|
+
`Unable to get help information for codex: ${error.message}`,
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Detect CLI type based on help content and naming
|
|
313
|
+
*/
|
|
314
|
+
detectCLIType(helpText, cliName) {
|
|
315
|
+
const text = helpText.toLowerCase();
|
|
316
|
+
const name = cliName.toLowerCase();
|
|
317
|
+
// Detect based on CLI name
|
|
318
|
+
if (name.includes('claude') || name.includes('anthropic')) {
|
|
319
|
+
return 'anthropic';
|
|
320
|
+
}
|
|
321
|
+
if (name.includes('gemini') || name.includes('google')) {
|
|
322
|
+
return 'google';
|
|
323
|
+
}
|
|
324
|
+
if (
|
|
325
|
+
name.includes('codex') ||
|
|
326
|
+
name.includes('openai') ||
|
|
327
|
+
name.includes('chatgpt')
|
|
328
|
+
) {
|
|
329
|
+
return 'openai';
|
|
330
|
+
}
|
|
331
|
+
// Detect based on help content patterns
|
|
332
|
+
if (text.includes('anthropic') || text.includes('claude')) {
|
|
333
|
+
return 'anthropic';
|
|
334
|
+
}
|
|
335
|
+
if (
|
|
336
|
+
text.includes('google') ||
|
|
337
|
+
text.includes('gemini') ||
|
|
338
|
+
text.includes('vertex')
|
|
339
|
+
) {
|
|
340
|
+
return 'google';
|
|
341
|
+
}
|
|
342
|
+
if (
|
|
343
|
+
text.includes('openai') ||
|
|
344
|
+
text.includes('gpt') ||
|
|
345
|
+
text.includes('codex')
|
|
346
|
+
) {
|
|
347
|
+
return 'openai';
|
|
348
|
+
}
|
|
349
|
+
if (
|
|
350
|
+
text.includes('qwen') ||
|
|
351
|
+
text.includes('alibaba') ||
|
|
352
|
+
text.includes('tongyi')
|
|
353
|
+
) {
|
|
354
|
+
return 'alibaba';
|
|
355
|
+
}
|
|
356
|
+
if (text.includes('iflow') || text.includes('intelligent')) {
|
|
357
|
+
return 'iflow';
|
|
358
|
+
}
|
|
359
|
+
if (text.includes('copilot') || text.includes('github')) {
|
|
360
|
+
return 'github';
|
|
361
|
+
}
|
|
362
|
+
if (text.includes('codebuddy') || text.includes('buddy')) {
|
|
363
|
+
return 'codebuddy';
|
|
364
|
+
}
|
|
365
|
+
if (text.includes('qoder') || text.includes('code')) {
|
|
366
|
+
return 'qoder';
|
|
367
|
+
}
|
|
368
|
+
// Default to generic
|
|
369
|
+
return 'generic';
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Extract patterns from help text
|
|
374
|
+
*/
|
|
375
|
+
extractPatterns(helpText, cliType, cliName) {
|
|
376
|
+
const rules = this.patternRules[cliType] || this.patternRules.generic;
|
|
377
|
+
const patterns = {
|
|
378
|
+
commands: [],
|
|
379
|
+
options: [],
|
|
380
|
+
subcommands: [],
|
|
381
|
+
arguments: [],
|
|
382
|
+
flags: [],
|
|
383
|
+
nonInteractiveFlag: null,
|
|
384
|
+
promptFlag: null,
|
|
385
|
+
requiredFlags: [],
|
|
386
|
+
commonPatterns: [],
|
|
387
|
+
};
|
|
388
|
+
|
|
389
|
+
// Extract options/flags
|
|
390
|
+
const optionMatches = helpText.match(rules.optionPattern) || [];
|
|
391
|
+
patterns.options = [...new Set(optionMatches)];
|
|
392
|
+
|
|
393
|
+
// Extract subcommands
|
|
394
|
+
const subcommandMatches = helpText.match(rules.subcommandPattern) || [];
|
|
395
|
+
patterns.subcommands = subcommandMatches.map((match) => {
|
|
396
|
+
const subcommand = match.trim().split(/\s+/)[0];
|
|
397
|
+
return { name: subcommand, description: match.trim() };
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
// Try to identify non-interactive flag
|
|
401
|
+
const nonInteractiveFlags = patterns.options.filter(
|
|
402
|
+
(option) =>
|
|
403
|
+
option.includes('non-interactive') ||
|
|
404
|
+
option.includes('batch') ||
|
|
405
|
+
option.includes('no-input') ||
|
|
406
|
+
option.includes('stdin'),
|
|
407
|
+
);
|
|
408
|
+
patterns.nonInteractiveFlag =
|
|
409
|
+
nonInteractiveFlags.length > 0 ? nonInteractiveFlags[0] : null;
|
|
410
|
+
|
|
411
|
+
// Try to identify prompt flag
|
|
412
|
+
const promptFlags = patterns.options.filter(
|
|
413
|
+
(option) =>
|
|
414
|
+
option.includes('prompt') ||
|
|
415
|
+
option.includes('input') ||
|
|
416
|
+
option.includes('query') ||
|
|
417
|
+
option.includes('question'),
|
|
418
|
+
);
|
|
419
|
+
patterns.promptFlag = promptFlags.length > 0 ? promptFlags[0] : null;
|
|
420
|
+
|
|
421
|
+
// Identify required flags
|
|
422
|
+
patterns.requiredFlags = patterns.options.filter(
|
|
423
|
+
(option) =>
|
|
424
|
+
option.includes('<') || // Angle brackets indicate required parameters
|
|
425
|
+
option.includes('*'), // Asterisk indicates required
|
|
426
|
+
);
|
|
427
|
+
|
|
428
|
+
return patterns;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* Analyze command structure
|
|
433
|
+
*/
|
|
434
|
+
analyzeCommandStructure(patterns) {
|
|
435
|
+
const structure = {
|
|
436
|
+
primaryCommand: '',
|
|
437
|
+
commandFormat: 'cli [args]',
|
|
438
|
+
argumentStyle: '',
|
|
439
|
+
optionStyle: '',
|
|
440
|
+
interactiveMode: false,
|
|
441
|
+
hasSubcommands: patterns.subcommands.length > 0,
|
|
442
|
+
complexity: 'simple',
|
|
443
|
+
nonInteractiveSupport: !!patterns.nonInteractiveFlag,
|
|
444
|
+
promptStyle: patterns.promptFlag ? 'flag' : 'argument',
|
|
445
|
+
executionPattern: 'interactive-default',
|
|
446
|
+
nonInteractiveFlag: patterns.nonInteractiveFlag,
|
|
447
|
+
promptFlag: patterns.promptFlag,
|
|
448
|
+
requiredFlags: patterns.requiredFlags,
|
|
449
|
+
commonPatterns: patterns.commonPatterns,
|
|
450
|
+
};
|
|
451
|
+
|
|
452
|
+
// Determine complexity
|
|
453
|
+
if (patterns.subcommands.length > 5) {
|
|
454
|
+
structure.complexity = 'complex';
|
|
455
|
+
} else if (patterns.subcommands.length > 0) {
|
|
456
|
+
structure.complexity = 'moderate';
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// Determine execution pattern
|
|
460
|
+
if (patterns.nonInteractiveFlag) {
|
|
461
|
+
structure.executionPattern = 'flag-based';
|
|
462
|
+
} else if (patterns.promptFlag) {
|
|
463
|
+
structure.executionPattern = 'prompt-flag';
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
return structure;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
/**
|
|
470
|
+
* Extract usage examples
|
|
471
|
+
*/
|
|
472
|
+
extractUsageExamples(helpText, cliType) {
|
|
473
|
+
const rules = this.patternRules[cliType] || this.patternRules.generic;
|
|
474
|
+
const examples = [];
|
|
475
|
+
let match;
|
|
476
|
+
|
|
477
|
+
while ((match = rules.examplePattern.exec(helpText)) !== null) {
|
|
478
|
+
examples.push(match[1].trim());
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
return examples;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
/**
|
|
485
|
+
* Determine interaction mode
|
|
486
|
+
*/
|
|
487
|
+
determineInteractionMode(helpInfo, patterns) {
|
|
488
|
+
const helpText = helpInfo.rawHelp.toLowerCase();
|
|
489
|
+
|
|
490
|
+
// Check for explicit non-interactive support
|
|
491
|
+
if (patterns.nonInteractiveFlag) {
|
|
492
|
+
return 'non-interactive';
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// Check for stdin support
|
|
496
|
+
if (helpText.includes('stdin') || helpText.includes('pipe')) {
|
|
497
|
+
return 'stdin-support';
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
// Check for batch mode
|
|
501
|
+
if (helpText.includes('batch') || helpText.includes('script')) {
|
|
502
|
+
return 'batch-mode';
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
// Default to interactive
|
|
506
|
+
return 'interactive';
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
/**
|
|
510
|
+
* Cache analysis results
|
|
511
|
+
*/
|
|
512
|
+
async cacheAnalysis(cliName, analysis) {
|
|
513
|
+
try {
|
|
514
|
+
const config = await this.loadPersistentConfig();
|
|
515
|
+
config.cliPatterns[cliName] = analysis;
|
|
516
|
+
config.lastUpdated = new Date().toISOString();
|
|
517
|
+
await this.savePersistentConfig(config);
|
|
518
|
+
} catch (error) {
|
|
519
|
+
// Don't spam errors for cache issues
|
|
520
|
+
if (process.env.DEBUG === 'true') {
|
|
521
|
+
await errorHandler.logError(
|
|
522
|
+
error,
|
|
523
|
+
'WARN',
|
|
524
|
+
'CLIHelpAnalyzer.cacheAnalysis',
|
|
525
|
+
);
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
/**
|
|
531
|
+
* Get cached analysis
|
|
532
|
+
*/
|
|
533
|
+
async getCachedAnalysis(cliName) {
|
|
534
|
+
try {
|
|
535
|
+
const config = await this.loadPersistentConfig();
|
|
536
|
+
return config.cliPatterns[cliName] || null;
|
|
537
|
+
} catch (error) {
|
|
538
|
+
return null;
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
/**
|
|
543
|
+
* Get CLI pattern (wrapper for getCachedAnalysis)
|
|
544
|
+
*/
|
|
545
|
+
async getCLIPattern(cliName) {
|
|
546
|
+
return await this.getCachedAnalysis(cliName);
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
/**
|
|
550
|
+
* Check if cache is expired (1 day)
|
|
551
|
+
*/
|
|
552
|
+
isCacheExpired(timestamp) {
|
|
553
|
+
if (!timestamp) return true;
|
|
554
|
+
const cacheTime = new Date(timestamp);
|
|
555
|
+
const now = new Date();
|
|
556
|
+
const diffHours = (now - cacheTime) / (1000 * 60 * 60);
|
|
557
|
+
return diffHours > 24; // Expire after 24 hours
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
/**
|
|
561
|
+
* Load persistent configuration
|
|
562
|
+
*/
|
|
563
|
+
async loadPersistentConfig() {
|
|
564
|
+
try {
|
|
565
|
+
const configExists = await this.fileExists(this.persistentConfig);
|
|
566
|
+
if (!configExists) {
|
|
567
|
+
return {
|
|
568
|
+
version: '1.0.0',
|
|
569
|
+
lastUpdated: new Date().toISOString(),
|
|
570
|
+
cliPatterns: {},
|
|
571
|
+
failedAttempts: {},
|
|
572
|
+
};
|
|
573
|
+
}
|
|
574
|
+
const configData = await fs.readFile(this.persistentConfig, 'utf8');
|
|
575
|
+
return JSON.parse(configData);
|
|
576
|
+
} catch (error) {
|
|
577
|
+
// Return default config if loading fails
|
|
578
|
+
return {
|
|
579
|
+
version: '1.0.0',
|
|
580
|
+
lastUpdated: new Date().toISOString(),
|
|
581
|
+
cliPatterns: {},
|
|
582
|
+
failedAttempts: {},
|
|
583
|
+
};
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
/**
|
|
588
|
+
* Save persistent configuration
|
|
589
|
+
*/
|
|
590
|
+
async savePersistentConfig(config) {
|
|
591
|
+
try {
|
|
592
|
+
await fs.writeFile(
|
|
593
|
+
this.persistentConfig,
|
|
594
|
+
JSON.stringify(config, null, 2),
|
|
595
|
+
);
|
|
596
|
+
} catch (error) {
|
|
597
|
+
throw error;
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
/**
|
|
602
|
+
* Record failed attempt
|
|
603
|
+
*/
|
|
604
|
+
async recordFailedAttempt(cliName, error) {
|
|
605
|
+
try {
|
|
606
|
+
const config = await this.loadPersistentConfig();
|
|
607
|
+
config.failedAttempts[cliName] = {
|
|
608
|
+
error: error.message,
|
|
609
|
+
timestamp: new Date().toISOString(),
|
|
610
|
+
attempts: (config.failedAttempts[cliName]?.attempts || 0) + 1,
|
|
611
|
+
};
|
|
612
|
+
await this.savePersistentConfig(config);
|
|
613
|
+
} catch (saveError) {
|
|
614
|
+
// Don't spam errors for failed attempt recording
|
|
615
|
+
if (process.env.DEBUG === 'true') {
|
|
616
|
+
await errorHandler.logError(
|
|
617
|
+
saveError,
|
|
618
|
+
'WARN',
|
|
619
|
+
'CLIHelpAnalyzer.recordFailedAttempt',
|
|
620
|
+
);
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
/**
|
|
626
|
+
* Update CLI pattern when call fails
|
|
627
|
+
*/
|
|
628
|
+
async updatePatternOnFailure(cliName, error, attemptedCommand) {
|
|
629
|
+
// Only log in debug mode to reduce console noise
|
|
630
|
+
if (process.env.DEBUG === 'true') {
|
|
631
|
+
console.log(
|
|
632
|
+
`Updating pattern for ${cliName} due to failure:`,
|
|
633
|
+
error.message,
|
|
634
|
+
);
|
|
635
|
+
}
|
|
636
|
+
try {
|
|
637
|
+
// Re-analyze the CLI
|
|
638
|
+
const newAnalysis = await this.analyzeCLI(cliName);
|
|
639
|
+
// Add failure context
|
|
640
|
+
newAnalysis.lastFailure = {
|
|
641
|
+
error: error.message,
|
|
642
|
+
attemptedCommand,
|
|
643
|
+
timestamp: new Date().toISOString(),
|
|
644
|
+
};
|
|
645
|
+
// Update the cached analysis
|
|
646
|
+
await this.cacheAnalysis(cliName, newAnalysis);
|
|
647
|
+
return newAnalysis;
|
|
648
|
+
} catch (analysisError) {
|
|
649
|
+
// Only log analysis errors in debug mode
|
|
650
|
+
if (process.env.DEBUG === 'true') {
|
|
651
|
+
console.error(
|
|
652
|
+
`Failed to re-analyze ${cliName}:`,
|
|
653
|
+
analysisError.message,
|
|
654
|
+
);
|
|
655
|
+
}
|
|
656
|
+
return null;
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
/**
|
|
661
|
+
* Check if file exists
|
|
662
|
+
*/
|
|
663
|
+
async fileExists(filePath) {
|
|
664
|
+
try {
|
|
665
|
+
await fs.access(filePath);
|
|
666
|
+
return true;
|
|
667
|
+
} catch (error) {
|
|
668
|
+
return false;
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
/**
|
|
673
|
+
* Set CLI tools
|
|
674
|
+
*/
|
|
675
|
+
setCLITools(tools) {
|
|
676
|
+
this.cliTools = tools;
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
module.exports = CLIHelpAnalyzer;
|