stigmergy 1.2.13 → 1.3.2-beta.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.
Files changed (50) hide show
  1. package/README.md +39 -3
  2. package/STIGMERGY.md +3 -0
  3. package/config/enhanced-cli-config.json +438 -0
  4. package/docs/CLI_TOOLS_AGENT_SKILL_ANALYSIS.md +463 -0
  5. package/docs/ENHANCED_CLI_AGENT_SKILL_CONFIG.md +285 -0
  6. package/docs/INSTALLER_ARCHITECTURE.md +257 -0
  7. package/docs/SUDO_PROBLEM_AND_SOLUTION.md +529 -0
  8. package/package.json +16 -5
  9. package/scripts/analyze-router.js +168 -0
  10. package/scripts/run-comprehensive-tests.js +230 -0
  11. package/scripts/run-quick-tests.js +90 -0
  12. package/scripts/test-runner.js +344 -0
  13. package/src/cli/commands/autoinstall.js +158 -0
  14. package/src/cli/commands/errors.js +190 -0
  15. package/src/cli/commands/install.js +142 -0
  16. package/src/cli/commands/permissions.js +108 -0
  17. package/src/cli/commands/project.js +449 -0
  18. package/src/cli/commands/resume.js +136 -0
  19. package/src/cli/commands/scan.js +97 -0
  20. package/src/cli/commands/skills.js +158 -0
  21. package/src/cli/commands/status.js +106 -0
  22. package/src/cli/commands/system.js +301 -0
  23. package/src/cli/router-beta.js +477 -0
  24. package/src/cli/utils/environment.js +75 -0
  25. package/src/cli/utils/formatters.js +47 -0
  26. package/src/cli/utils/skills_cache.js +92 -0
  27. package/src/core/cache_cleaner.js +1 -0
  28. package/src/core/cli_adapters.js +345 -0
  29. package/src/core/cli_help_analyzer.js +473 -1
  30. package/src/core/cli_path_detector.js +2 -1
  31. package/src/core/cli_tools.js +107 -0
  32. package/src/core/coordination/nodejs/HookDeploymentManager.js +204 -416
  33. package/src/core/coordination/nodejs/HookDeploymentManager.refactored.js +323 -0
  34. package/src/core/coordination/nodejs/generators/CLIAdapterGenerator.js +363 -0
  35. package/src/core/coordination/nodejs/generators/ResumeSessionGenerator.js +703 -0
  36. package/src/core/coordination/nodejs/generators/SkillsIntegrationGenerator.js +1210 -0
  37. package/src/core/coordination/nodejs/generators/index.js +12 -0
  38. package/src/core/enhanced_cli_installer.js +375 -31
  39. package/src/core/enhanced_cli_parameter_handler.js +395 -0
  40. package/src/core/execution_mode_detector.js +222 -0
  41. package/src/core/installer.js +83 -67
  42. package/src/core/local_skill_scanner.js +732 -0
  43. package/src/core/multilingual/language-pattern-manager.js +1 -1
  44. package/src/core/skills/StigmergySkillManager.js +26 -8
  45. package/src/core/smart_router.js +279 -2
  46. package/src/index.js +10 -4
  47. package/test/cli-integration.test.js +304 -0
  48. package/test/enhanced-cli-agent-skill-test.js +485 -0
  49. package/test/specific-cli-agent-skill-analysis.js +385 -0
  50. package/src/cli/router.js +0 -1783
@@ -0,0 +1,477 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Stigmergy CLI - Modular Router
5
+ * Multi-Agents Cross-AI CLI Tools Collaboration System
6
+ * Modular implementation with separated concerns
7
+ * Version: 2.0.0
8
+ */
9
+
10
+ // Core imports
11
+ const path = require('path');
12
+ const os = require('os');
13
+ const { Command } = require('commander');
14
+ const chalk = require('chalk');
15
+
16
+ // Import CLI tools configuration
17
+ const { CLI_TOOLS } = require('../core/cli_tools');
18
+
19
+ // Import modular components
20
+ const { formatBytes } = require('./utils/formatters');
21
+ const { getWorkingDirectoryForTool, getEnvironmentForTool } = require('./utils/environment');
22
+
23
+ // Import execution mode detection and CLI adapters
24
+ const ExecutionModeDetector = require('../core/execution_mode_detector');
25
+ const { CLIAdapterManager } = require('../core/cli_adapters');
26
+
27
+ // Create instances
28
+ const modeDetector = new ExecutionModeDetector();
29
+ const cliAdapterManager = new CLIAdapterManager();
30
+ const { handleInstallCommand } = require('./commands/install');
31
+ const { handleStatusCommand } = require('./commands/status');
32
+ const { handleScanCommand } = require('./commands/scan');
33
+ const { handlePermCheckCommand, handleFixPermsCommand } = require('./commands/permissions');
34
+ const { handleDiagnosticCommand, handleCleanCommand } = require('./commands/system');
35
+ const { handleSkillMainCommand, printSkillsHelp } = require('./commands/skills');
36
+ const { handleErrorsCommand } = require('./commands/errors');
37
+ const { handleAutoInstallCommand } = require('./commands/autoinstall');
38
+ const { handleResumeCommand, printResumeHelp } = require('./commands/resume');
39
+ const { getCLIPath } = require('../core/cli_tools');
40
+ const {
41
+ handleUpgradeCommand,
42
+ handleDeployCommand,
43
+ handleInitCommand,
44
+ handleSetupCommand,
45
+ handleCallCommand
46
+ } = require('./commands/project');
47
+ const SmartRouter = require('../core/smart_router');
48
+ const { errorHandler } = require('../core/error_handler');
49
+ const { executeCommand } = require('../utils');
50
+ const { setupGlobalErrorHandlers } = require('../core/error_handler');
51
+
52
+ // Set up global error handlers
53
+ setupGlobalErrorHandlers();
54
+
55
+ /**
56
+ * Add OAuth authentication arguments to command
57
+ * @param {Command} command - Commander command instance
58
+ * @returns {Command} Command with OAuth args added
59
+ */
60
+ function addOAuthAuthArgsCommand(command) {
61
+ return command
62
+ .option('--client-id <id>', 'OAuth client ID')
63
+ .option('--client-secret <secret>', 'OAuth client secret')
64
+ .option('--access-token <token>', 'OAuth access token')
65
+ .option('--auth-url <url>', 'OAuth authentication URL');
66
+ }
67
+
68
+ /**
69
+ * Add OAuth authentication arguments to command args for execution
70
+ * @param {string} toolName - Name of the CLI tool
71
+ * @param {Array} args - Current arguments array
72
+ * @returns {Array} Arguments with OAuth auth added
73
+ */
74
+ function addOAuthAuthArgs(toolName, args = []) {
75
+ const toolConfig = CLI_TOOLS[toolName];
76
+
77
+ if (toolConfig && toolConfig.oauth) {
78
+ const oauth = toolConfig.oauth;
79
+ if (oauth.authRequired) {
80
+ // Qwen-specific OAuth handling
81
+ if (toolName === 'qwen' && process.env.QWEN_ACCESS_TOKEN) {
82
+ return [...args, '--access-token', process.env.QWEN_ACCESS_TOKEN];
83
+ }
84
+ }
85
+ }
86
+
87
+ return args;
88
+ }
89
+
90
+ /**
91
+ * Main CLI router function
92
+ */
93
+ async function main() {
94
+ const program = new Command();
95
+
96
+ // Program setup
97
+ program
98
+ .version('1.3.0-beta.0')
99
+ .description('Stigmergy CLI - Multi-Agents Cross-AI CLI Tools Collaboration System')
100
+ .name('stigmergy');
101
+
102
+ // Version command (override the built-in --version)
103
+ program
104
+ .command('version')
105
+ .description('Show version information')
106
+ .action(() => {
107
+ const packageJson = require('../../package.json');
108
+ console.log(`Stigmergy CLI v${packageJson.version}`);
109
+ });
110
+
111
+ // Error reporting command
112
+ program
113
+ .command('errors')
114
+ .description('Generate comprehensive error report')
115
+ .option('--save', 'Save report to file')
116
+ .option('-v, --verbose', 'Verbose output')
117
+ .action(async (options) => {
118
+ await handleErrorsCommand(options);
119
+ });
120
+
121
+ // Install command
122
+ program
123
+ .command('install')
124
+ .alias('inst')
125
+ .description('Install CLI tools')
126
+ .option('-c, --cli <cli>', 'Install specific CLI tool')
127
+ .option('-v, --verbose', 'Verbose output')
128
+ .option('-f, --force', 'Force installation')
129
+ .action(async (options) => {
130
+ await handleInstallCommand(options);
131
+ });
132
+
133
+ // Project management commands
134
+ program
135
+ .command('upgrade')
136
+ .description('Upgrade AI CLI tools to latest versions')
137
+ .option('--dry-run', 'Show what would be upgraded without actually upgrading')
138
+ .option('-f, --force', 'Force upgrade')
139
+ .option('-v, --verbose', 'Verbose output')
140
+ .action(async (options) => {
141
+ await handleUpgradeCommand(options);
142
+ });
143
+
144
+ program
145
+ .command('deploy')
146
+ .description('Deploy integration hooks to CLI tools')
147
+ .option('-v, --verbose', 'Verbose output')
148
+ .action(async (options) => {
149
+ await handleDeployCommand(options);
150
+ });
151
+
152
+ program
153
+ .command('init')
154
+ .description('Initialize Stigmergy project in current directory')
155
+ .option('-v, --verbose', 'Verbose output')
156
+ .action(async (options) => {
157
+ await handleInitCommand(options);
158
+ });
159
+
160
+ program
161
+ .command('setup')
162
+ .description('Complete Stigmergy setup (install + deploy + init)')
163
+ .option('-v, --verbose', 'Verbose output')
164
+ .action(async (options) => {
165
+ await handleSetupCommand(options);
166
+ });
167
+
168
+ program
169
+ .command('call')
170
+ .description('Smart AI tool routing based on prompt')
171
+ .argument('<prompt>', 'Prompt to process with smart routing')
172
+ .option('-i, --interactive', 'Run in interactive mode (continuous conversation)')
173
+ .option('-p, --print', 'Run in one-time mode (print and exit)')
174
+ .option('-v, --verbose', 'Verbose output')
175
+ .action(async (prompt, options) => {
176
+ await handleCallCommand(prompt, options);
177
+ });
178
+
179
+ // Status command
180
+ program
181
+ .command('status')
182
+ .description('Check CLI tools status')
183
+ .option('-c, --cli <cli>', 'Check status of specific CLI tool')
184
+ .option('--json', 'Output in JSON format')
185
+ .option('-v, --verbose', 'Verbose output')
186
+ .action(async (options) => {
187
+ await handleStatusCommand(options);
188
+ });
189
+
190
+ // Scan command
191
+ program
192
+ .command('scan')
193
+ .description('Scan for available CLI tools')
194
+ .option('-d, --deep', 'Deep scan for CLI tools')
195
+ .option('--json', 'Output in JSON format')
196
+ .option('-v, --verbose', 'Verbose output')
197
+ .action(async (options) => {
198
+ await handleScanCommand(options);
199
+ });
200
+
201
+ // Permission management commands
202
+ program
203
+ .command('fix-perms')
204
+ .description('Fix directory permissions automatically')
205
+ .option('-v, --verbose', 'Verbose output')
206
+ .action(async (options) => {
207
+ await handleFixPermsCommand(options);
208
+ });
209
+
210
+ program
211
+ .command('perm-check')
212
+ .description('Check directory permissions')
213
+ .option('-v, --verbose', 'Verbose output')
214
+ .action(async (options) => {
215
+ await handlePermCheckCommand(options);
216
+ });
217
+
218
+ // System commands
219
+ program
220
+ .command('clean')
221
+ .alias('c')
222
+ .description('Intelligent cache cleaning')
223
+ .option('--dry-run', 'Show what would be cleaned without actually cleaning')
224
+ .option('-q, --quiet', 'Suppress detailed output, show only summary')
225
+ .option('-v, --verbose', 'Verbose output (show permission errors)')
226
+ .action(async (options) => {
227
+ await handleCleanCommand(options);
228
+ });
229
+
230
+ program
231
+ .command('diagnostic')
232
+ .aliases(['diag', 'd'])
233
+ .description('System diagnostic')
234
+ .option('-v, --verbose', 'Verbose output')
235
+ .action(async (options) => {
236
+ await handleDiagnosticCommand(options);
237
+ });
238
+
239
+ // Skills management commands
240
+ program
241
+ .command('skill')
242
+ .description('Skills management system')
243
+ .argument('[subcommand]', 'Skill subcommand (install/list/read/validate/remove/sync)')
244
+ .argument('[args...]', 'Additional arguments')
245
+ .option('-v, --verbose', 'Verbose output')
246
+ .option('-f, --force', 'Force operation')
247
+ .option('--no-auto-sync', 'Disable auto-sync')
248
+ .action(async (subcommand, args, options) => {
249
+ if (!subcommand) {
250
+ printSkillsHelp();
251
+ return;
252
+ }
253
+ await handleSkillMainCommand(subcommand, args, options);
254
+ });
255
+
256
+ // Skill command aliases (shortcuts)
257
+ program
258
+ .command('skill-i')
259
+ .description('Install a skill (alias for: skill install)')
260
+ .argument('<source>', 'Skill source to install')
261
+ .option('-v, --verbose', 'Verbose output')
262
+ .action(async (source, options) => {
263
+ await handleSkillMainCommand('install', [source], options);
264
+ });
265
+
266
+ program
267
+ .command('skill-l')
268
+ .description('List installed skills (alias for: skill list)')
269
+ .option('-v, --verbose', 'Verbose output')
270
+ .action(async (options) => {
271
+ await handleSkillMainCommand('list', [], options);
272
+ });
273
+
274
+ program
275
+ .command('skill-r')
276
+ .description('Read a skill (alias for: skill read)')
277
+ .argument('<skill-name>', 'Name of skill to read')
278
+ .option('-v, --verbose', 'Verbose output')
279
+ .action(async (skillName, options) => {
280
+ await handleSkillMainCommand('read', [skillName], options);
281
+ });
282
+
283
+ program
284
+ .command('skill-v')
285
+ .description('Validate/read skill (alias for: skill validate/read)')
286
+ .argument('<path-or-name>', 'Path to validate or skill name to read')
287
+ .option('-v, --verbose', 'Verbose output')
288
+ .action(async (pathOrName, options) => {
289
+ await handleSkillMainCommand('validate', [pathOrName], options);
290
+ });
291
+
292
+ program
293
+ .command('skill-d')
294
+ .description('Remove a skill (alias for: skill remove)')
295
+ .argument('<skill-name>', 'Name of skill to remove')
296
+ .option('-v, --verbose', 'Verbose output')
297
+ .option('-f, --force', 'Force removal')
298
+ .action(async (skillName, options) => {
299
+ await handleSkillMainCommand('remove', [skillName], options);
300
+ });
301
+
302
+ program
303
+ .command('skill-m')
304
+ .description('Remove a skill (移除 alias for: skill remove)')
305
+ .argument('<skill-name>', 'Name of skill to remove')
306
+ .option('-v, --verbose', 'Verbose output')
307
+ .option('-f, --force', 'Force removal')
308
+ .action(async (skillName, options) => {
309
+ await handleSkillMainCommand('remove', [skillName], options);
310
+ });
311
+
312
+ // Auto-install command (for npm postinstall)
313
+ program
314
+ .command('auto-install')
315
+ .description('Automated installation for npm postinstall')
316
+ .option('-v, --verbose', 'Verbose output')
317
+ .option('-f, --force', 'Force installation')
318
+ .action(async (options) => {
319
+ await handleAutoInstallCommand(options);
320
+ });
321
+
322
+ // Resume session commands
323
+ program
324
+ .command('resume')
325
+ .description('Resume session (forwards to @stigmergy/resume CLI tool)')
326
+ .argument('[args...]', 'Arguments to pass to resumesession')
327
+ .option('-v, --verbose', 'Verbose output')
328
+ .action(async (args, options) => {
329
+ await handleResumeCommand(args, options);
330
+ });
331
+
332
+ program
333
+ .command('resumesession')
334
+ .description('Resume session management (forwards to @stigmergy/resume)')
335
+ .argument('[args...]', 'Arguments to pass to resumesession')
336
+ .option('-v, --verbose', 'Verbose output')
337
+ .action(async (args, options) => {
338
+ await handleResumeCommand(args, options);
339
+ });
340
+
341
+ program
342
+ .command('sg-resume')
343
+ .description('Resume session management (short alias)')
344
+ .argument('[args...]', 'Arguments to pass to resumesession')
345
+ .option('-v, --verbose', 'Verbose output')
346
+ .action(async (args, options) => {
347
+ await handleResumeCommand(args, options);
348
+ });
349
+
350
+ // Route commands to CLI tools
351
+ for (const tool of ['claude', 'gemini', 'qwen', 'codebuddy', 'codex', 'iflow', 'qodercli', 'copilot', 'kode']) {
352
+ program
353
+ .command(tool)
354
+ .description(`Use ${tool} CLI tool`)
355
+ .option('-i, --interactive', 'Run in interactive mode (continuous conversation)')
356
+ .option('-p, --print', 'Run in one-time mode (print and exit)')
357
+ .allowUnknownOption(true)
358
+ .action(async (options, command) => {
359
+ try {
360
+ // Get the tool path directly (we know the tool name)
361
+ const toolPath = await getCLIPath(tool);
362
+
363
+ if (toolPath) {
364
+ // Join args to form the prompt
365
+ const prompt = command.args.join(' ');
366
+
367
+ if (process.env.DEBUG === 'true') {
368
+ console.log(`[DEBUG] Tool path: ${toolPath}`);
369
+ console.log(`[DEBUG] Prompt: ${prompt}`);
370
+ }
371
+
372
+ // Detect execution mode
373
+ const mode = modeDetector.detect({
374
+ interactive: options.interactive,
375
+ print: options.print,
376
+ verbose: process.env.DEBUG === 'true'
377
+ });
378
+
379
+ const modeDescription = modeDetector.getModeDescription(mode);
380
+ if (process.env.DEBUG === 'true' || options.interactive || options.print) {
381
+ console.log(chalk.gray(`[MODE] ${modeDescription}`));
382
+ }
383
+
384
+ // Use CLI adapter to get appropriate arguments for the tool and mode
385
+ const adaptedArgs = cliAdapterManager.getArguments(tool, mode, prompt);
386
+
387
+ if (process.env.DEBUG === 'true') {
388
+ console.log(`[DEBUG] Adapted args for ${tool} (${mode}): ${adaptedArgs.join(' ')}`);
389
+ }
390
+
391
+ // Use enhanced parameter handling for intelligent argument generation
392
+ let toolArgs = adaptedArgs; // Start with adapted args
393
+
394
+ try {
395
+ if (process.env.DEBUG === 'true') {
396
+ console.log('[DEBUG] Initializing parameter handler...');
397
+ }
398
+ const EnhancedCLIParameterHandler = require('../core/enhanced_cli_parameter_handler');
399
+ const paramHandler = new EnhancedCLIParameterHandler();
400
+
401
+ // Generate optimized arguments with agent/skill support
402
+ // Skip for interactive mode to avoid double-processing
403
+ if (mode === 'one-time') {
404
+ const paramResult = await paramHandler.generateArgumentsWithRetry(
405
+ tool,
406
+ prompt,
407
+ {
408
+ maxRetries: 3,
409
+ enableAgentSkillOptimization: true
410
+ }
411
+ );
412
+
413
+ toolArgs = paramResult.arguments;
414
+
415
+ if (process.env.DEBUG === 'true') {
416
+ console.log(`[DEBUG] Generated args: ${toolArgs.join(' ')}`);
417
+ }
418
+ }
419
+ } catch (paramError) {
420
+ // Fallback to adapted args if parameter handler fails
421
+ if (process.env.DEBUG === 'true') {
422
+ console.log(chalk.yellow(`[WARN] Parameter handler failed, using adapted args: ${paramError.message}`));
423
+ }
424
+ }
425
+
426
+ // Add OAuth authentication if needed
427
+ toolArgs = addOAuthAuthArgs(tool, toolArgs);
428
+
429
+ console.log(chalk.gray(`[EXEC] ${tool}: ${prompt}`));
430
+
431
+ // Set up environment for the tool
432
+ const toolEnv = getEnvironmentForTool(tool);
433
+ const workingDir = getWorkingDirectoryForTool(tool);
434
+
435
+ const result = await executeCommand(toolPath, toolArgs, {
436
+ stdio: 'inherit',
437
+ shell: true,
438
+ cwd: workingDir,
439
+ env: toolEnv
440
+ });
441
+
442
+ if (!result.success) {
443
+ console.log(chalk.yellow(`[WARN] ${tool} exited with code ${result.code}`));
444
+ }
445
+ process.exit(result.code || 0);
446
+ } else {
447
+ console.log(chalk.red(`[ERROR] Could not find ${tool}`));
448
+ console.log(chalk.yellow('[INFO] Make sure the tool is installed: stigmergy install'));
449
+ process.exit(1);
450
+ }
451
+ } catch (error) {
452
+ if (process.env.DEBUG === 'true') {
453
+ console.log(chalk.red(`[ERROR] Exception: ${error.message}`));
454
+ console.log(chalk.red(error.stack));
455
+ }
456
+ const cliError = await errorHandler.handleCLIError(tool, error, command.args.join(' '));
457
+ console.log(chalk.red(`[ERROR] Failed to execute ${tool}:`));
458
+ console.log(chalk.red(cliError.message));
459
+ process.exit(1);
460
+ }
461
+ });
462
+ }
463
+
464
+ // Add OAuth arguments for OAuth-enabled tools
465
+ const oauthTools = ['gemini', 'claude']; // Tools that support OAuth
466
+ oauthTools.forEach(tool => {
467
+ const cmd = program.commands.find(c => c.name() === tool);
468
+ if (cmd) {
469
+ addOAuthAuthArgsCommand(cmd);
470
+ }
471
+ });
472
+
473
+ // Parse command line arguments
474
+ program.parse(process.argv);
475
+ }
476
+
477
+ module.exports = main;
@@ -0,0 +1,75 @@
1
+ /**
2
+ * CLI Environment Module
3
+ * Contains environment and working directory management
4
+ */
5
+
6
+ const path = require('path');
7
+ const os = require('os');
8
+
9
+ /**
10
+ * Get appropriate working directory for CLI tools
11
+ * @param {string} toolName - Name of the CLI tool
12
+ * @returns {string} Working directory path
13
+ */
14
+ function getWorkingDirectoryForTool(toolName) {
15
+ switch (toolName) {
16
+ case 'claude':
17
+ return process.cwd(); // Current working directory for Claude
18
+ case 'gemini':
19
+ return process.cwd(); // Current working directory for Gemini
20
+ case 'qwen':
21
+ return path.join(os.homedir(), '.qwen'); // Qwen's home directory
22
+ case 'codebuddy':
23
+ return process.cwd(); // Current working directory for CodeBuddy
24
+ case 'copilot':
25
+ return process.cwd(); // Current working directory for Copilot
26
+ default:
27
+ return process.cwd(); // Default to current working directory
28
+ }
29
+ }
30
+
31
+ /**
32
+ * Get environment variables for specific CLI tool
33
+ * @param {string} toolName - Name of the CLI tool
34
+ * @returns {Object} Environment variables object
35
+ */
36
+ function getEnvironmentForTool(toolName) {
37
+ const env = { ...process.env };
38
+
39
+ // Tool-specific environment setup
40
+ switch (toolName) {
41
+ case 'qwen':
42
+ // For Qwen CLI, clear Node.js environment variables to avoid import conflicts
43
+ delete env.NODE_PATH;
44
+ delete env.NODE_OPTIONS;
45
+ // Ensure clean environment
46
+ env.PWD = getWorkingDirectoryForTool(toolName);
47
+ break;
48
+ case 'claude':
49
+ // Claude-specific environment setup if needed
50
+ break;
51
+ case 'gemini':
52
+ // Gemini-specific environment setup if needed
53
+ break;
54
+ default:
55
+ // Use default environment
56
+ break;
57
+ }
58
+
59
+ return env;
60
+ }
61
+
62
+ /**
63
+ * Check if CLI tool needs special working directory
64
+ * @param {string} toolName - Name of the CLI tool
65
+ * @returns {boolean} True if special directory needed
66
+ */
67
+ function needsSpecialDirectory(toolName) {
68
+ return ['qwen'].includes(toolName);
69
+ }
70
+
71
+ module.exports = {
72
+ getWorkingDirectoryForTool,
73
+ getEnvironmentForTool,
74
+ needsSpecialDirectory
75
+ };
@@ -0,0 +1,47 @@
1
+ /**
2
+ * CLI Formatters Module
3
+ * Contains helper functions for formatting output
4
+ */
5
+
6
+ /**
7
+ * Format bytes into human readable string
8
+ * @param {number} bytes - Number of bytes
9
+ * @returns {string} Formatted string
10
+ */
11
+ function formatBytes(bytes) {
12
+ const sizes = ['Bytes', 'KB', 'MB', 'GB'];
13
+ if (bytes === 0) return '0 Bytes';
14
+ const i = Math.floor(Math.log(bytes) / Math.log(1024));
15
+ return Math.round(bytes / Math.pow(1024, i) * 100) / 100 + ' ' + sizes[i];
16
+ }
17
+
18
+ /**
19
+ * Format duration in milliseconds to human readable string
20
+ * @param {number} ms - Duration in milliseconds
21
+ * @returns {string} Formatted duration
22
+ */
23
+ function formatDuration(ms) {
24
+ if (ms < 1000) return `${ms}ms`;
25
+ if (ms < 60000) return `${(ms / 1000).toFixed(1)}s`;
26
+ if (ms < 3600000) return `${(ms / 60000).toFixed(1)}m`;
27
+ return `${(ms / 3600000).toFixed(1)}h`;
28
+ }
29
+
30
+ /**
31
+ * Format CLI tool status for display
32
+ * @param {Object} status - Status object
33
+ * @returns {string} Formatted status
34
+ */
35
+ function formatToolStatus(status) {
36
+ if (status.installed) {
37
+ return `${status.tool}: ✅ Installed (v${status.version || 'unknown'})`;
38
+ } else {
39
+ return `${status.tool}: ❌ Not installed`;
40
+ }
41
+ }
42
+
43
+ module.exports = {
44
+ formatBytes,
45
+ formatDuration,
46
+ formatToolStatus
47
+ };
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Skills/Agents Cache Management Utilities
3
+ * Provides centralized cache initialization and management for skills and agents
4
+ */
5
+
6
+ const LocalSkillScanner = require('../../core/local_skill_scanner');
7
+
8
+ /**
9
+ * Initialize or update skills/agents cache
10
+ * This function checks if cache exists, and if not, generates it.
11
+ * If cache exists, it performs incremental update (only scans changed directories).
12
+ *
13
+ * Called during: scan, install, setup, init, upgrade commands
14
+ *
15
+ * @param {Object} options - Options
16
+ * @param {boolean} options.verbose - Show detailed output
17
+ * @param {boolean} options.force - Force full rescan
18
+ * @returns {Promise<Object>} Cache statistics
19
+ */
20
+ async function ensureSkillsCache(options = {}) {
21
+ const { verbose = false, force = false } = options;
22
+
23
+ try {
24
+ const scanner = new LocalSkillScanner();
25
+
26
+ // Check if cache exists
27
+ const hasCache = await scanner.hasCache();
28
+
29
+ if (!hasCache) {
30
+ // First time - generate cache
31
+ console.log('[CACHE] Generating skills/agents cache (first time)...');
32
+ await scanner.initialize();
33
+ const results = scanner.getScanResults();
34
+
35
+ if (results) {
36
+ const totalSkills = Object.values(results.skills || {}).flat().length;
37
+ const totalAgents = Object.values(results.agents || {}).flat().length;
38
+ console.log(`[CACHE] Cache generated: ${totalSkills} skills, ${totalAgents} agents`);
39
+
40
+ return {
41
+ success: true,
42
+ skills: totalSkills,
43
+ agents: totalAgents,
44
+ action: 'generated'
45
+ };
46
+ }
47
+ } else if (force) {
48
+ // Force refresh requested
49
+ console.log('[CACHE] Refreshing skills/agents cache...');
50
+ await scanner.initialize(true);
51
+ const results = scanner.getScanResults();
52
+
53
+ if (results) {
54
+ const totalSkills = Object.values(results.skills || {}).flat().length;
55
+ const totalAgents = Object.values(results.agents || {}).flat().length;
56
+ console.log(`[CACHE] Cache refreshed: ${totalSkills} skills, ${totalAgents} agents`);
57
+
58
+ return {
59
+ success: true,
60
+ skills: totalSkills,
61
+ agents: totalAgents,
62
+ action: 'refreshed'
63
+ };
64
+ }
65
+ } else {
66
+ // Incremental update - only scan changed directories
67
+ if (verbose || process.env.DEBUG === 'true') {
68
+ console.log('[CACHE] Updating skills/agents cache (incremental)...');
69
+ }
70
+ await scanner.scanIncremental();
71
+
72
+ return {
73
+ success: true,
74
+ action: 'updated'
75
+ };
76
+ }
77
+ } catch (error) {
78
+ // Cache initialization is not critical - don't fail the command
79
+ if (verbose || process.env.DEBUG === 'true') {
80
+ console.log(`[CACHE] Warning: ${error.message}`);
81
+ }
82
+
83
+ return {
84
+ success: false,
85
+ error: error.message
86
+ };
87
+ }
88
+ }
89
+
90
+ module.exports = {
91
+ ensureSkillsCache
92
+ };
@@ -254,6 +254,7 @@ class CacheCleaner {
254
254
  'codex',
255
255
  'copilot',
256
256
  'qwencode',
257
+ 'kode',
257
258
  ];
258
259
 
259
260
  let totalCleaned = 0;