stigmergy 1.2.13 → 1.3.1
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 +39 -3
- package/STIGMERGY.md +3 -0
- package/config/builtin-skills.json +43 -0
- package/config/enhanced-cli-config.json +438 -0
- package/docs/CLI_TOOLS_AGENT_SKILL_ANALYSIS.md +463 -0
- package/docs/DESIGN_CLI_HELP_ANALYZER_REFACTOR.md +726 -0
- package/docs/ENHANCED_CLI_AGENT_SKILL_CONFIG.md +285 -0
- package/docs/IMPLEMENTATION_CHECKLIST_CLI_HELP_ANALYZER_REFACTOR.md +1268 -0
- package/docs/INSTALLER_ARCHITECTURE.md +257 -0
- package/docs/LESSONS_LEARNED.md +252 -0
- package/docs/SPECS_CLI_HELP_ANALYZER_REFACTOR.md +287 -0
- package/docs/SUDO_PROBLEM_AND_SOLUTION.md +529 -0
- package/docs/correct-skillsio-implementation.md +368 -0
- package/docs/development_guidelines.md +276 -0
- package/docs/independent-resume-implementation.md +198 -0
- package/docs/resumesession-final-implementation.md +195 -0
- package/docs/resumesession-usage.md +87 -0
- package/package.json +146 -136
- package/scripts/analyze-router.js +168 -0
- package/scripts/run-comprehensive-tests.js +230 -0
- package/scripts/run-quick-tests.js +90 -0
- package/scripts/test-runner.js +344 -0
- package/skills/resumesession/INDEPENDENT_SKILL.md +403 -0
- package/skills/resumesession/README.md +381 -0
- package/skills/resumesession/SKILL.md +211 -0
- package/skills/resumesession/__init__.py +33 -0
- package/skills/resumesession/implementations/simple-resume.js +13 -0
- package/skills/resumesession/independent-resume.js +750 -0
- package/skills/resumesession/package.json +1 -0
- package/skills/resumesession/skill.json +1 -0
- package/src/adapters/claude/install_claude_integration.js +9 -1
- package/src/adapters/codebuddy/install_codebuddy_integration.js +3 -1
- package/src/adapters/codex/install_codex_integration.js +15 -5
- package/src/adapters/gemini/install_gemini_integration.js +3 -1
- package/src/adapters/qwen/install_qwen_integration.js +3 -1
- package/src/cli/commands/autoinstall.js +65 -0
- package/src/cli/commands/errors.js +190 -0
- package/src/cli/commands/independent-resume.js +395 -0
- package/src/cli/commands/install.js +179 -0
- package/src/cli/commands/permissions.js +108 -0
- package/src/cli/commands/project.js +485 -0
- package/src/cli/commands/scan.js +97 -0
- package/src/cli/commands/simple-resume.js +377 -0
- package/src/cli/commands/skills.js +158 -0
- package/src/cli/commands/status.js +113 -0
- package/src/cli/commands/stigmergy-resume.js +775 -0
- package/src/cli/commands/system.js +301 -0
- package/src/cli/commands/universal-resume.js +394 -0
- package/src/cli/router-beta.js +471 -0
- package/src/cli/utils/environment.js +75 -0
- package/src/cli/utils/formatters.js +47 -0
- package/src/cli/utils/skills_cache.js +92 -0
- package/src/core/cache_cleaner.js +1 -0
- package/src/core/cli_adapters.js +345 -0
- package/src/core/cli_help_analyzer.js +1236 -680
- package/src/core/cli_path_detector.js +702 -709
- package/src/core/cli_tools.js +515 -160
- package/src/core/coordination/nodejs/CLIIntegrationManager.js +18 -0
- package/src/core/coordination/nodejs/HookDeploymentManager.js +242 -412
- package/src/core/coordination/nodejs/HookDeploymentManager.refactored.js +323 -0
- package/src/core/coordination/nodejs/generators/CLIAdapterGenerator.js +363 -0
- package/src/core/coordination/nodejs/generators/ResumeSessionGenerator.js +932 -0
- package/src/core/coordination/nodejs/generators/SkillsIntegrationGenerator.js +1395 -0
- package/src/core/coordination/nodejs/generators/index.js +12 -0
- package/src/core/enhanced_cli_installer.js +1208 -608
- package/src/core/enhanced_cli_parameter_handler.js +402 -0
- package/src/core/execution_mode_detector.js +222 -0
- package/src/core/installer.js +151 -106
- package/src/core/local_skill_scanner.js +732 -0
- package/src/core/multilingual/language-pattern-manager.js +1 -1
- package/src/core/skills/BuiltinSkillsDeployer.js +188 -0
- package/src/core/skills/StigmergySkillManager.js +123 -16
- package/src/core/skills/embedded-openskills/SkillParser.js +7 -3
- package/src/core/smart_router.js +550 -261
- package/src/index.js +10 -4
- package/src/utils.js +66 -7
- package/test/cli-integration.test.js +304 -0
- package/test/direct_smart_router_test.js +88 -0
- package/test/enhanced-cli-agent-skill-test.js +485 -0
- package/test/simple_test.js +82 -0
- package/test/smart_router_test_runner.js +123 -0
- package/test/smart_routing_edge_cases.test.js +284 -0
- package/test/smart_routing_simple_verification.js +139 -0
- package/test/smart_routing_verification.test.js +346 -0
- package/test/specific-cli-agent-skill-analysis.js +385 -0
- package/test/unit/smart_router.test.js +295 -0
- package/test/very_simple_test.js +54 -0
- package/src/cli/router.js +0 -1783
package/src/core/installer.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
const chalk = require('chalk');
|
|
1
2
|
const path = require('path');
|
|
2
3
|
const fs = require('fs/promises');
|
|
3
4
|
const os = require('os');
|
|
@@ -6,12 +7,16 @@ const inquirer = require('inquirer');
|
|
|
6
7
|
const SmartRouter = require('./smart_router');
|
|
7
8
|
const { errorHandler } = require('./error_handler');
|
|
8
9
|
const MemoryManager = require('./memory_manager');
|
|
10
|
+
const EnhancedCLIInstaller = require('./enhanced_cli_installer');
|
|
9
11
|
|
|
10
|
-
class StigmergyInstaller {
|
|
11
|
-
constructor() {
|
|
12
|
+
class StigmergyInstaller extends EnhancedCLIInstaller {
|
|
13
|
+
constructor(options = {}) {
|
|
14
|
+
super(options);
|
|
15
|
+
this.concurrency = options.concurrency || 6;
|
|
12
16
|
this.router = new SmartRouter();
|
|
13
17
|
this.memory = new MemoryManager();
|
|
14
18
|
this.configDir = path.join(os.homedir(), '.stigmergy');
|
|
19
|
+
this.scanCache = new Map(); // 缓存扫描结果
|
|
15
20
|
}
|
|
16
21
|
|
|
17
22
|
async checkCLI(toolName) {
|
|
@@ -48,36 +53,38 @@ class StigmergyInstaller {
|
|
|
48
53
|
codexPath.endsWith('/codex') ||
|
|
49
54
|
codexPath.endsWith('\\codex')
|
|
50
55
|
) {
|
|
51
|
-
//
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
)
|
|
68
|
-
|
|
56
|
+
// Try to verify JS file, but don't fail if we can't
|
|
57
|
+
// The actual --version test below is more reliable
|
|
58
|
+
try {
|
|
59
|
+
const fsSync = require('fs');
|
|
60
|
+
const scriptContent = fsSync.readFileSync(codexPath, 'utf8');
|
|
61
|
+
|
|
62
|
+
// Look for JS file reference in the script
|
|
63
|
+
// Match node_modules/@openai/codex/bin/codex.js pattern
|
|
64
|
+
const jsFileMatch = scriptContent.match(/node_modules\/@openai\/codex\/bin\/codex\.js/);
|
|
65
|
+
if (jsFileMatch) {
|
|
66
|
+
// Construct actual path based on npm global directory
|
|
67
|
+
const npmGlobalDir = require('path').dirname(codexPath);
|
|
68
|
+
const jsFilePath = require('path').join(npmGlobalDir, jsFileMatch[0]);
|
|
69
|
+
|
|
70
|
+
if (fsSync.existsSync(jsFilePath)) {
|
|
71
|
+
const stats = fsSync.statSync(jsFilePath);
|
|
72
|
+
if (stats.size === 0) {
|
|
73
|
+
console.log('[DEBUG] Codex JS file is empty, marking as unavailable');
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
// File exists and has content - continue to version check
|
|
77
|
+
} else {
|
|
78
|
+
console.log('[DEBUG] Codex JS file not found at expected path, will try version check');
|
|
69
79
|
}
|
|
70
|
-
} else {
|
|
71
|
-
console.log(
|
|
72
|
-
'[DEBUG] Codex JS file does not exist, marking as unavailable',
|
|
73
|
-
);
|
|
74
|
-
return false;
|
|
75
80
|
}
|
|
81
|
+
} catch (scriptError) {
|
|
82
|
+
console.log(`[DEBUG] Could not verify codex script: ${scriptError.message}`);
|
|
83
|
+
// Continue anyway - the version check below is more reliable
|
|
76
84
|
}
|
|
77
85
|
}
|
|
78
86
|
|
|
79
|
-
// If we got here, the codex command exists
|
|
80
|
-
// Continue with normal checks below
|
|
87
|
+
// If we got here, the codex command exists - continue with normal checks below
|
|
81
88
|
} else {
|
|
82
89
|
// Codex command doesn't exist
|
|
83
90
|
return false;
|
|
@@ -109,8 +116,9 @@ class StigmergyInstaller {
|
|
|
109
116
|
};
|
|
110
117
|
|
|
111
118
|
// Additional protection for codex
|
|
119
|
+
// Note: codex requires shell=true on Windows to work properly
|
|
112
120
|
if (toolName === 'codex') {
|
|
113
|
-
|
|
121
|
+
// Keep shell=true for codex (don't override)
|
|
114
122
|
testOptions.windowsHide = true;
|
|
115
123
|
testOptions.detached = false;
|
|
116
124
|
}
|
|
@@ -132,6 +140,7 @@ class StigmergyInstaller {
|
|
|
132
140
|
// Special handling for codex to avoid opening files
|
|
133
141
|
if (toolName === 'codex') {
|
|
134
142
|
// For codex, only try --help and --version with extra precautions
|
|
143
|
+
// Note: codex requires shell=true on Windows
|
|
135
144
|
const codexChecks = [
|
|
136
145
|
{ args: ['--help'], expected: 0 },
|
|
137
146
|
{ args: ['--version'], expected: 0 },
|
|
@@ -143,7 +152,7 @@ class StigmergyInstaller {
|
|
|
143
152
|
encoding: 'utf8',
|
|
144
153
|
timeout: 10000,
|
|
145
154
|
stdio: ['pipe', 'pipe', 'pipe'], // Ensure all IO is piped
|
|
146
|
-
shell:
|
|
155
|
+
shell: true, // Use shell for codex compatibility
|
|
147
156
|
windowsHide: true, // Hide console window on Windows
|
|
148
157
|
detached: false, // Don't detach process
|
|
149
158
|
});
|
|
@@ -201,36 +210,60 @@ class StigmergyInstaller {
|
|
|
201
210
|
|
|
202
211
|
async scanCLI() {
|
|
203
212
|
console.log('[SCAN] Scanning for AI CLI tools...');
|
|
213
|
+
|
|
214
|
+
// 检查缓存
|
|
215
|
+
const cacheKey = 'scan_results';
|
|
216
|
+
if (this.scanCache.has(cacheKey)) {
|
|
217
|
+
console.log('[SCAN] Using cached scan results');
|
|
218
|
+
return this.scanCache.get(cacheKey);
|
|
219
|
+
}
|
|
220
|
+
|
|
204
221
|
const available = {};
|
|
205
222
|
const missing = {};
|
|
223
|
+
const tools = Object.entries(this.router.tools);
|
|
224
|
+
|
|
225
|
+
// 并行扫描所有工具
|
|
226
|
+
const scanPromises = tools.map(async ([toolName, toolInfo]) => {
|
|
227
|
+
// Skip internal functions without install command
|
|
228
|
+
if (!toolInfo.install) {
|
|
229
|
+
return { toolName, status: 'skip' };
|
|
230
|
+
}
|
|
206
231
|
|
|
207
|
-
for (const [toolName, toolInfo] of Object.entries(this.router.tools)) {
|
|
208
232
|
try {
|
|
209
|
-
console.log(`[SCAN] Checking ${toolInfo.name}...`);
|
|
210
233
|
const isAvailable = await this.checkCLI(toolName);
|
|
211
|
-
|
|
212
|
-
if (isAvailable) {
|
|
213
|
-
console.log(`[OK] ${toolInfo.name} is available`);
|
|
214
|
-
available[toolName] = toolInfo;
|
|
215
|
-
} else {
|
|
216
|
-
console.log(`[MISSING] ${toolInfo.name} is not installed`);
|
|
217
|
-
missing[toolName] = toolInfo;
|
|
218
|
-
}
|
|
234
|
+
return { toolName, status: isAvailable ? 'available' : 'missing', toolInfo };
|
|
219
235
|
} catch (error) {
|
|
220
236
|
await errorHandler.logError(
|
|
221
237
|
error,
|
|
222
238
|
'WARN',
|
|
223
239
|
`StigmergyInstaller.scanCLI.${toolName}`,
|
|
224
240
|
);
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
241
|
+
return { toolName, status: 'missing', toolInfo };
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
const results = await Promise.all(scanPromises);
|
|
246
|
+
|
|
247
|
+
// 处理结果
|
|
248
|
+
for (const result of results) {
|
|
249
|
+
if (result.status === 'skip') {
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (result.status === 'available') {
|
|
254
|
+
console.log(`[OK] ${result.toolInfo.name} is available`);
|
|
255
|
+
available[result.toolName] = result.toolInfo;
|
|
256
|
+
} else {
|
|
257
|
+
console.log(`[MISSING] ${result.toolInfo.name} is not installed`);
|
|
258
|
+
missing[result.toolName] = result.toolInfo;
|
|
230
259
|
}
|
|
231
260
|
}
|
|
232
261
|
|
|
233
|
-
|
|
262
|
+
// 缓存结果
|
|
263
|
+
const scanResults = { available, missing };
|
|
264
|
+
this.scanCache.set(cacheKey, scanResults);
|
|
265
|
+
|
|
266
|
+
return scanResults;
|
|
234
267
|
}
|
|
235
268
|
|
|
236
269
|
/**
|
|
@@ -305,52 +338,56 @@ class StigmergyInstaller {
|
|
|
305
338
|
|
|
306
339
|
/**
|
|
307
340
|
* Install selected CLI tools
|
|
341
|
+
* 统一调用父类EnhancedCLIInstaller的实现
|
|
308
342
|
*/
|
|
309
343
|
async installTools(selectedTools, missingTools) {
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
// Execute the installation command
|
|
325
|
-
const result = spawnSync(command, args, {
|
|
326
|
-
stdio: 'inherit', // Show output in real-time
|
|
327
|
-
shell: true, // Use shell for npm commands
|
|
328
|
-
encoding: 'utf-8'
|
|
329
|
-
});
|
|
330
|
-
|
|
331
|
-
if (result.status === 0) {
|
|
332
|
-
console.log(chalk.green(`[OK] Successfully installed ${toolInfo.name}`));
|
|
333
|
-
successCount++;
|
|
334
|
-
} else {
|
|
335
|
-
console.log(chalk.red(`[ERROR] Failed to install ${toolInfo.name}. Exit code: ${result.status}`));
|
|
336
|
-
// Continue with other tools
|
|
344
|
+
// 调用父类的增强安装功能
|
|
345
|
+
const result = await super.installTools(selectedTools, missingTools);
|
|
346
|
+
|
|
347
|
+
// 保留StigmergyInstaller的特有功能:生成cross-CLI hooks
|
|
348
|
+
if (result && result.successful > 0) {
|
|
349
|
+
console.log('\n[HOOKS] Generating cross-CLI integration hooks...');
|
|
350
|
+
for (const toolName of selectedTools) {
|
|
351
|
+
const toolInfo = missingTools[toolName];
|
|
352
|
+
if (toolInfo && this.results.installations[toolName]?.success) {
|
|
353
|
+
try {
|
|
354
|
+
await this.generateToolHook(toolName, toolInfo);
|
|
355
|
+
} catch (error) {
|
|
356
|
+
console.log(`[WARN] Failed to generate hook for ${toolName}: ${error.message}`);
|
|
357
|
+
}
|
|
337
358
|
}
|
|
338
|
-
} catch (error) {
|
|
339
|
-
console.log(chalk.red(`[ERROR] Installation failed for ${toolInfo.name}: ${error.message}`));
|
|
340
|
-
// Continue with other tools
|
|
341
359
|
}
|
|
342
360
|
}
|
|
343
361
|
|
|
344
|
-
|
|
345
|
-
|
|
362
|
+
return result;
|
|
363
|
+
}
|
|
346
364
|
|
|
347
|
-
|
|
348
|
-
|
|
365
|
+
/**
|
|
366
|
+
* Generate hook for a specific CLI tool
|
|
367
|
+
* @param {string} toolName - Name of the CLI tool
|
|
368
|
+
* @param {Object} toolInfo - Tool information object
|
|
369
|
+
*/
|
|
370
|
+
async generateToolHook(toolName, toolInfo) {
|
|
371
|
+
console.log(`[HOOK] Generating hook for ${toolName}...`);
|
|
349
372
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
373
|
+
const HookDeploymentManager = require('./coordination/nodejs/HookDeploymentManager');
|
|
374
|
+
const hookManager = new HookDeploymentManager();
|
|
375
|
+
|
|
376
|
+
await hookManager.initialize();
|
|
377
|
+
|
|
378
|
+
try {
|
|
379
|
+
const deploySuccess = await hookManager.deployHooksForCLI(toolName);
|
|
380
|
+
|
|
381
|
+
if (deploySuccess) {
|
|
382
|
+
console.log(`[OK] Hook generated successfully for ${toolName}`);
|
|
383
|
+
return true;
|
|
384
|
+
} else {
|
|
385
|
+
console.log(`[WARN] Hook generation failed for ${toolName}`);
|
|
386
|
+
return false;
|
|
387
|
+
}
|
|
388
|
+
} catch (error) {
|
|
389
|
+
console.error(`[ERROR] Hook generation error for ${toolName}:`, error.message);
|
|
390
|
+
throw error;
|
|
354
391
|
}
|
|
355
392
|
}
|
|
356
393
|
|
|
@@ -359,43 +396,39 @@ class StigmergyInstaller {
|
|
|
359
396
|
*/
|
|
360
397
|
async deployHooks(available) {
|
|
361
398
|
console.log('\n[DEPLOY] Deploying cross-CLI integration hooks...');
|
|
399
|
+
console.log(chalk.blue(`[INFO] Using parallel deployment with concurrency: ${this.concurrency || 6}`));
|
|
362
400
|
|
|
363
401
|
const HookDeploymentManager = require('./coordination/nodejs/HookDeploymentManager');
|
|
364
402
|
const hookManager = new HookDeploymentManager();
|
|
365
403
|
|
|
366
404
|
await hookManager.initialize();
|
|
367
405
|
|
|
406
|
+
const toolEntries = Object.entries(available);
|
|
368
407
|
let successCount = 0;
|
|
369
|
-
let totalCount =
|
|
370
|
-
|
|
371
|
-
for (const [toolName, toolInfo] of Object.entries(available)) {
|
|
372
|
-
console.log(`\n[DEPLOY] Deploying hooks for ${toolInfo.name}...`);
|
|
408
|
+
let totalCount = toolEntries.length;
|
|
409
|
+
const concurrency = this.concurrency || 6;
|
|
373
410
|
|
|
411
|
+
const deployForTool = async ([toolName, toolInfo]) => {
|
|
412
|
+
const startTime = Date.now();
|
|
413
|
+
|
|
374
414
|
try {
|
|
375
415
|
const fs = require('fs/promises');
|
|
376
416
|
const path = require('path');
|
|
377
417
|
|
|
378
|
-
// Create hooks directory
|
|
379
418
|
await fs.mkdir(toolInfo.hooksDir, { recursive: true });
|
|
380
|
-
console.log(`[OK] Created hooks directory: ${toolInfo.hooksDir}`);
|
|
381
|
-
|
|
382
|
-
// Create config directory
|
|
383
419
|
const configDir = path.dirname(toolInfo.config);
|
|
384
420
|
await fs.mkdir(configDir, { recursive: true });
|
|
385
|
-
console.log(`[OK] Created config directory: ${configDir}`);
|
|
386
421
|
|
|
387
|
-
// Use HookDeploymentManager to deploy Node.js hooks
|
|
388
422
|
const deploySuccess = await hookManager.deployHooksForCLI(toolName);
|
|
389
423
|
|
|
390
424
|
if (deploySuccess) {
|
|
391
|
-
|
|
425
|
+
await this.installToolIntegration(toolName, toolInfo);
|
|
426
|
+
const duration = Date.now() - startTime;
|
|
427
|
+
console.log(`[OK] ${toolInfo.name} deployed successfully (${duration}ms)`);
|
|
428
|
+
return { success: true, toolName, duration };
|
|
392
429
|
}
|
|
393
430
|
|
|
394
|
-
|
|
395
|
-
await this.installToolIntegration(toolName, toolInfo);
|
|
396
|
-
|
|
397
|
-
console.log(`[OK] Hooks deployed successfully for ${toolInfo.name}`);
|
|
398
|
-
successCount++;
|
|
431
|
+
return { success: false, toolName, duration: Date.now() - startTime };
|
|
399
432
|
|
|
400
433
|
} catch (error) {
|
|
401
434
|
const { errorHandler } = require('./error_handler');
|
|
@@ -404,16 +437,28 @@ class StigmergyInstaller {
|
|
|
404
437
|
'ERROR',
|
|
405
438
|
`StigmergyInstaller.deployHooks.${toolName}`,
|
|
406
439
|
);
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
);
|
|
410
|
-
|
|
440
|
+
|
|
441
|
+
const duration = Date.now() - startTime;
|
|
442
|
+
console.log(`[ERROR] Failed to deploy hooks for ${toolInfo.name}: ${error.message} (${duration}ms)`);
|
|
443
|
+
|
|
444
|
+
return { success: false, toolName, duration, error: error.message };
|
|
411
445
|
}
|
|
446
|
+
};
|
|
447
|
+
|
|
448
|
+
const results = [];
|
|
449
|
+
for (let i = 0; i < toolEntries.length; i += concurrency) {
|
|
450
|
+
const batch = toolEntries.slice(i, i + concurrency);
|
|
451
|
+
const batchResults = await Promise.all(batch.map(deployForTool));
|
|
452
|
+
results.push(...batchResults);
|
|
412
453
|
}
|
|
413
454
|
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
455
|
+
successCount = results.filter(r => r.success).length;
|
|
456
|
+
const totalDuration = Math.max(...results.map(r => r.duration));
|
|
457
|
+
|
|
458
|
+
console.log(`\n[SUMMARY] Hook deployment completed: ${successCount}/${totalCount} tools successful`);
|
|
459
|
+
console.log(chalk.blue(`[PERFORMANCE] Total deployment time: ${totalDuration}ms`));
|
|
460
|
+
console.log(chalk.blue(`[PERFORMANCE] Average per tool: ${Math.round(totalDuration / totalCount)}ms`));
|
|
461
|
+
|
|
417
462
|
return successCount > 0;
|
|
418
463
|
}
|
|
419
464
|
|