stigmergy 1.3.13 ā 1.3.15
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/package.json +1 -1
- package/src/cli/commands/project.js +2 -1
- package/src/cli/commands/resume.js +5 -7
- package/src/cli/router-beta.js +4 -21
- package/src/core/cli_tools.js +16 -0
- package/src/core/coordination/nodejs/CLIIntegrationManager.js +18 -0
- package/src/core/coordination/nodejs/HookDeploymentManager.js +3 -1
- package/src/core/coordination/nodejs/generators/ResumeSessionGenerator.js +6 -5
- package/src/core/coordination/nodejs/generators/SkillsIntegrationGenerator.js +214 -29
- package/src/core/enhanced_cli_installer.js +115 -0
- package/src/core/skills/StigmergySkillManager.js +98 -9
- package/src/core/skills/embedded-openskills/SkillParser.js +7 -3
package/package.json
CHANGED
|
@@ -16,6 +16,7 @@ const LocalSkillScanner = require('../../core/local_skill_scanner');
|
|
|
16
16
|
const CLIHelpAnalyzer = require('../../core/cli_help_analyzer');
|
|
17
17
|
const { CLI_TOOLS } = require('../../core/cli_tools');
|
|
18
18
|
const { ensureSkillsCache } = require('../utils/skills_cache');
|
|
19
|
+
const packageJson = require('../../../package.json');
|
|
19
20
|
|
|
20
21
|
// Import execution mode detection and CLI adapters
|
|
21
22
|
const ExecutionModeDetector = require('../../core/execution_mode_detector');
|
|
@@ -150,7 +151,7 @@ async function handleInitCommand(options = {}) {
|
|
|
150
151
|
|
|
151
152
|
// Create basic config
|
|
152
153
|
const config = {
|
|
153
|
-
version:
|
|
154
|
+
version: packageJson.version,
|
|
154
155
|
created: new Date().toISOString(),
|
|
155
156
|
project: path.basename(projectDir)
|
|
156
157
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Resume Session Commands
|
|
3
|
-
* Modular implementation for resume
|
|
3
|
+
* Modular implementation for resume command
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
const chalk = require('chalk');
|
|
@@ -47,7 +47,7 @@ async function handleResumeCommand(args = [], options = {}) {
|
|
|
47
47
|
console.log(chalk.yellow('[INFO] ResumeSession is an optional component for session recovery'));
|
|
48
48
|
|
|
49
49
|
console.log(chalk.blue('\nš¦ To install ResumeSession:'));
|
|
50
|
-
console.log(' npm install -g
|
|
50
|
+
console.log(' npm install -g resumesession');
|
|
51
51
|
console.log('');
|
|
52
52
|
console.log(chalk.blue('š§ ResumeSession provides:'));
|
|
53
53
|
console.log(' ⢠Cross-CLI session history');
|
|
@@ -100,18 +100,16 @@ function printResumeHelp() {
|
|
|
100
100
|
console.log(chalk.cyan(`
|
|
101
101
|
š Stigmergy Resume Session System
|
|
102
102
|
|
|
103
|
-
š ResumeSession forwards to the
|
|
103
|
+
š ResumeSession forwards to the resumesession CLI tool for session management.
|
|
104
104
|
|
|
105
105
|
š ļø Available Commands:
|
|
106
106
|
stigmergy resume [args] Forward to resumesession CLI
|
|
107
|
-
stigmergy resumesession [args] Same as resume (full name)
|
|
108
|
-
stigmergy sg-resume [args] Same as resume (short alias)
|
|
109
107
|
|
|
110
108
|
š¦ Requirements:
|
|
111
|
-
|
|
109
|
+
resumesession CLI tool must be installed separately.
|
|
112
110
|
|
|
113
111
|
š¾ Installation:
|
|
114
|
-
npm install -g
|
|
112
|
+
npm install -g resumesession
|
|
115
113
|
|
|
116
114
|
š Common Usage:
|
|
117
115
|
stigmergy resume list # Show available sessions
|
package/src/cli/router-beta.js
CHANGED
|
@@ -92,10 +92,11 @@ function addOAuthAuthArgs(toolName, args = []) {
|
|
|
92
92
|
*/
|
|
93
93
|
async function main() {
|
|
94
94
|
const program = new Command();
|
|
95
|
+
const packageJson = require('../../package.json');
|
|
95
96
|
|
|
96
97
|
// Program setup
|
|
97
98
|
program
|
|
98
|
-
.version(
|
|
99
|
+
.version(packageJson.version)
|
|
99
100
|
.description('Stigmergy CLI - Multi-Agents Cross-AI CLI Tools Collaboration System')
|
|
100
101
|
.name('stigmergy');
|
|
101
102
|
|
|
@@ -319,7 +320,7 @@ async function main() {
|
|
|
319
320
|
await handleAutoInstallCommand(options);
|
|
320
321
|
});
|
|
321
322
|
|
|
322
|
-
// Resume session
|
|
323
|
+
// Resume session command
|
|
323
324
|
program
|
|
324
325
|
.command('resume')
|
|
325
326
|
.description('Resume session (forwards to @stigmergy/resume CLI tool)')
|
|
@@ -329,26 +330,8 @@ async function main() {
|
|
|
329
330
|
await handleResumeCommand(args, options);
|
|
330
331
|
});
|
|
331
332
|
|
|
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
333
|
// Route commands to CLI tools
|
|
351
|
-
for (const tool of ['claude', 'gemini', 'qwen', 'codebuddy', 'codex', 'iflow', 'qodercli', 'copilot', 'kode', '
|
|
334
|
+
for (const tool of ['claude', 'gemini', 'qwen', 'codebuddy', 'codex', 'iflow', 'qodercli', 'copilot', 'kode', 'opencode', 'oh-my-opencode']) {
|
|
352
335
|
program
|
|
353
336
|
.command(tool)
|
|
354
337
|
.description(`Use ${tool} CLI tool`)
|
package/src/core/cli_tools.js
CHANGED
|
@@ -76,6 +76,22 @@ const CLI_TOOLS = {
|
|
|
76
76
|
hooksDir: path.join(os.homedir(), '.resumesession', 'hooks'),
|
|
77
77
|
config: path.join(os.homedir(), '.resumesession', 'config.json'),
|
|
78
78
|
},
|
|
79
|
+
opencode: {
|
|
80
|
+
name: 'OpenCode AI CLI',
|
|
81
|
+
version: 'opencode --version',
|
|
82
|
+
install: 'npm install -g opencode-ai',
|
|
83
|
+
hooksDir: path.join(os.homedir(), '.opencode', 'hooks'),
|
|
84
|
+
config: path.join(os.homedir(), '.opencode', 'config.json'),
|
|
85
|
+
},
|
|
86
|
+
'oh-my-opencode': {
|
|
87
|
+
name: 'Oh-My-OpenCode Plugin Manager',
|
|
88
|
+
version: 'oh-my-opencode --version',
|
|
89
|
+
install: 'npm install bun -g && bunx oh-my-opencode install --no-tui --claude=no --chatgpt=no --gemini=no',
|
|
90
|
+
hooksDir: path.join(os.homedir(), '.opencode', 'plugins'),
|
|
91
|
+
config: path.join(os.homedir(), '.opencode', 'config.json'),
|
|
92
|
+
skipVersionCheck: true, // Version check may not work properly for bunx packages
|
|
93
|
+
requiresBun: true, // Requires bun runtime
|
|
94
|
+
},
|
|
79
95
|
};
|
|
80
96
|
|
|
81
97
|
/**
|
|
@@ -72,6 +72,24 @@ class CLIIntegrationManager {
|
|
|
72
72
|
executable: 'copilot',
|
|
73
73
|
mcp: ['cross_cli_execute'],
|
|
74
74
|
},
|
|
75
|
+
opencode: {
|
|
76
|
+
name: 'OpenCode AI CLI',
|
|
77
|
+
executable: 'opencode',
|
|
78
|
+
skills: [
|
|
79
|
+
'on_skill_invocation',
|
|
80
|
+
'on_code_generation',
|
|
81
|
+
'on_cross_cli_task',
|
|
82
|
+
],
|
|
83
|
+
},
|
|
84
|
+
'oh-my-opencode': {
|
|
85
|
+
name: 'Oh-My-OpenCode Plugin Manager',
|
|
86
|
+
executable: 'oh-my-opencode',
|
|
87
|
+
plugins: [
|
|
88
|
+
'on_plugin_install',
|
|
89
|
+
'on_plugin_load',
|
|
90
|
+
'on_cross_cli_integration',
|
|
91
|
+
],
|
|
92
|
+
},
|
|
75
93
|
};
|
|
76
94
|
}
|
|
77
95
|
|
|
@@ -7,7 +7,8 @@ class ResumeSessionGenerator {
|
|
|
7
7
|
constructor() {
|
|
8
8
|
this.supportedCLIs = [
|
|
9
9
|
'claude', 'gemini', 'qwen', 'codebuddy', 'codex',
|
|
10
|
-
'iflow', 'qodercli', 'copilot', 'kode', '
|
|
10
|
+
'iflow', 'qodercli', 'copilot', 'kode', 'opencode',
|
|
11
|
+
'oh-my-opencode', 'resumesession'
|
|
11
12
|
];
|
|
12
13
|
}
|
|
13
14
|
|
|
@@ -534,8 +535,8 @@ function buildQuery(input) {
|
|
|
534
535
|
search: null
|
|
535
536
|
};
|
|
536
537
|
|
|
537
|
-
const cleanInput = input.replace(new RegExp('^\\\\\\\\/?' + '${commandName}' + '
|
|
538
|
-
const parts = cleanInput.split(
|
|
538
|
+
const cleanInput = input.replace(new RegExp('^\\\\\\\\/?' + '${commandName}' + '\\\\\\s*', 'i'), '').trim();
|
|
539
|
+
const parts = cleanInput.split(/\\\\s+/).filter(p => p.length > 0);
|
|
539
540
|
|
|
540
541
|
for (let i = 0; i < parts.length; i++) {
|
|
541
542
|
const part = parts[i].toLowerCase();
|
|
@@ -624,8 +625,8 @@ class GeminiHistoryHandler {
|
|
|
624
625
|
search: null
|
|
625
626
|
};
|
|
626
627
|
|
|
627
|
-
const cleanInput = input.replace(new RegExp('^\\\\\\\\/?' + this.commandName + '
|
|
628
|
-
const parts = cleanInput.split(
|
|
628
|
+
const cleanInput = input.replace(new RegExp('^\\\\\\\\/?' + this.commandName + '\\\\\\s*', 'i'), '').trim();
|
|
629
|
+
const parts = cleanInput.split(/\\\\s+/).filter(p => p.length > 0);
|
|
629
630
|
|
|
630
631
|
for (let i = 0; i < parts.length; i++) {
|
|
631
632
|
const part = parts[i].toLowerCase();
|
|
@@ -59,8 +59,8 @@ class SkillsIntegrationGenerator {
|
|
|
59
59
|
return `"""
|
|
60
60
|
Claude CLI Skills Integration Hook
|
|
61
61
|
Auto-generated by Stigmergy CLI v1.2.1
|
|
62
|
-
Project: ${projectPath}
|
|
63
|
-
Skills Directory: ${skillsDir}
|
|
62
|
+
Project: ${projectPath.replace(/\\/g, '/')}
|
|
63
|
+
Skills Directory: ${skillsDir.replace(/\\/g, '/')}
|
|
64
64
|
|
|
65
65
|
This hook provides skills integration for Claude CLI with dynamic loading
|
|
66
66
|
and execution of skills from the centralized skills directory.
|
|
@@ -75,7 +75,7 @@ from typing import Dict, List, Optional, Any
|
|
|
75
75
|
|
|
76
76
|
class ClaudeSkillsHook:
|
|
77
77
|
def __init__(self):
|
|
78
|
-
self.skills_dir = Path("${skillsDir.replace(/\\/g, '
|
|
78
|
+
self.skills_dir = Path("${skillsDir.replace(/\\/g, '/')}")
|
|
79
79
|
self.loaded_skills = {}
|
|
80
80
|
self.skill_metadata = {}
|
|
81
81
|
|
|
@@ -105,7 +105,21 @@ class ClaudeSkillsHook:
|
|
|
105
105
|
def load_skill(self, skill_dir):
|
|
106
106
|
"""Load a single skill from directory"""
|
|
107
107
|
try:
|
|
108
|
-
# Check for skill implementation files
|
|
108
|
+
# Check for skill implementation files in scripts subdirectory
|
|
109
|
+
scripts_dir = skill_dir / 'scripts'
|
|
110
|
+
if scripts_dir.exists() and scripts_dir.is_dir():
|
|
111
|
+
for py_file in scripts_dir.glob("*.py"):
|
|
112
|
+
if py_file.name != "test_skill.py": # Skip test files
|
|
113
|
+
spec = importlib.util.spec_from_file_location(
|
|
114
|
+
f"{skill_dir.name}.{py_file.stem}", py_file
|
|
115
|
+
)
|
|
116
|
+
if spec and spec.loader:
|
|
117
|
+
module = importlib.util.module_from_spec(spec)
|
|
118
|
+
spec.loader.exec_module(module)
|
|
119
|
+
print(f"[CLAUDE_SKILLS] Loaded module: {py_file.name}")
|
|
120
|
+
return module
|
|
121
|
+
|
|
122
|
+
# Fallback: check for Python files in root directory
|
|
109
123
|
for py_file in skill_dir.glob("*.py"):
|
|
110
124
|
if py_file.name != "test_skill.py": # Skip test files
|
|
111
125
|
spec = importlib.util.spec_from_file_location(
|
|
@@ -114,7 +128,14 @@ class ClaudeSkillsHook:
|
|
|
114
128
|
if spec and spec.loader:
|
|
115
129
|
module = importlib.util.module_from_spec(spec)
|
|
116
130
|
spec.loader.exec_module(module)
|
|
131
|
+
print(f"[CLAUDE_SKILLS] Loaded module: {py_file.name}")
|
|
117
132
|
return module
|
|
133
|
+
|
|
134
|
+
# If no Python files found, return the skill directory info
|
|
135
|
+
# This allows skills to be registered even without Python implementation
|
|
136
|
+
print(f"[CLAUDE_SKILLS] No Python files found, registering skill directory")
|
|
137
|
+
return {'type': 'directory', 'path': skill_dir}
|
|
138
|
+
|
|
118
139
|
except Exception as e:
|
|
119
140
|
print(f"[CLAUDE_SKILLS] Failed to load skill {skill_dir.name}: {e}")
|
|
120
141
|
return None
|
|
@@ -127,13 +148,25 @@ class ClaudeSkillsHook:
|
|
|
127
148
|
content = skill_md.read_text(encoding='utf-8')
|
|
128
149
|
metadata = {}
|
|
129
150
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
151
|
+
# Parse YAML-style frontmatter
|
|
152
|
+
lines = content.split('\\n')
|
|
153
|
+
in_frontmatter = False
|
|
154
|
+
for line in lines:
|
|
155
|
+
if line.strip() == '---':
|
|
156
|
+
in_frontmatter = not in_frontmatter
|
|
157
|
+
continue
|
|
158
|
+
if in_frontmatter:
|
|
159
|
+
if ':' in line:
|
|
160
|
+
key, value = line.split(':', 1)
|
|
161
|
+
metadata[key.strip()] = value.strip()
|
|
162
|
+
else:
|
|
163
|
+
# Fallback to old format
|
|
164
|
+
if line.startswith('## Description:'):
|
|
165
|
+
metadata['description'] = line.replace('## Description:', '').strip()
|
|
166
|
+
elif line.startswith('## Usage:'):
|
|
167
|
+
metadata['usage'] = line.replace('## Usage:', '').strip()
|
|
168
|
+
elif line.startswith('## Location:'):
|
|
169
|
+
metadata['location'] = line.replace('## Location:', '').strip()
|
|
137
170
|
|
|
138
171
|
return metadata
|
|
139
172
|
except Exception as e:
|
|
@@ -226,8 +259,8 @@ __all__ = ['on_prompt', 'initialize']
|
|
|
226
259
|
generateGeminiSkillsIntegration(projectPath, skillsDir) {
|
|
227
260
|
return `// Gemini CLI Skills Integration Extension
|
|
228
261
|
// Auto-generated by Stigmergy CLI v1.2.1
|
|
229
|
-
// Project: ${projectPath}
|
|
230
|
-
// Skills Directory: ${skillsDir}
|
|
262
|
+
// Project: ${projectPath.replace(/\\/g, '/')}
|
|
263
|
+
// Skills Directory: ${skillsDir.replace(/\\/g, '/')}
|
|
231
264
|
|
|
232
265
|
const fs = require('fs');
|
|
233
266
|
const path = require('path');
|
|
@@ -235,7 +268,7 @@ const os = require('os');
|
|
|
235
268
|
|
|
236
269
|
class GeminiSkillsExtension {
|
|
237
270
|
constructor() {
|
|
238
|
-
this.skillsDir = path.join('${skillsDir.replace(/\\/g, '
|
|
271
|
+
this.skillsDir = path.join('${skillsDir.replace(/\\/g, '/')}');
|
|
239
272
|
this.loadedSkills = new Map();
|
|
240
273
|
this.skillMetadata = new Map();
|
|
241
274
|
}
|
|
@@ -280,6 +313,27 @@ class GeminiSkillsExtension {
|
|
|
280
313
|
|
|
281
314
|
async loadSkill(skillPath) {
|
|
282
315
|
try {
|
|
316
|
+
// Check for skill implementation files in scripts subdirectory
|
|
317
|
+
const scriptsDir = path.join(skillPath, 'scripts');
|
|
318
|
+
if (fs.existsSync(scriptsDir) && fs.statSync(scriptsDir).isDirectory()) {
|
|
319
|
+
const jsFiles = fs.readdirSync(scriptsDir).filter(file => file.endsWith('.js'));
|
|
320
|
+
|
|
321
|
+
for (const jsFile of jsFiles) {
|
|
322
|
+
if (!jsFile.includes('test')) {
|
|
323
|
+
const skillModulePath = path.join(scriptsDir, jsFile);
|
|
324
|
+
try {
|
|
325
|
+
delete require.cache[require.resolve(skillModulePath)];
|
|
326
|
+
const skillModule = require(skillModulePath);
|
|
327
|
+
console.log(\`[GEMINI_SKILLS] Loaded module: \${jsFile}\`);
|
|
328
|
+
return skillModule;
|
|
329
|
+
} catch (moduleError) {
|
|
330
|
+
console.warn(\`[GEMINI_SKILLS] Could not load module \${jsFile}: \${moduleError.message}\`);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// Fallback: check for JavaScript files in root directory
|
|
283
337
|
const jsFiles = fs.readdirSync(skillPath).filter(file => file.endsWith('.js'));
|
|
284
338
|
|
|
285
339
|
for (const jsFile of jsFiles) {
|
|
@@ -288,12 +342,18 @@ class GeminiSkillsExtension {
|
|
|
288
342
|
try {
|
|
289
343
|
delete require.cache[require.resolve(skillModulePath)];
|
|
290
344
|
const skillModule = require(skillModulePath);
|
|
345
|
+
console.log(\`[GEMINI_SKILLS] Loaded module: \${jsFile}\`);
|
|
291
346
|
return skillModule;
|
|
292
347
|
} catch (moduleError) {
|
|
293
348
|
console.warn(\`[GEMINI_SKILLS] Could not load module \${jsFile}: \${moduleError.message}\`);
|
|
294
349
|
}
|
|
295
350
|
}
|
|
296
351
|
}
|
|
352
|
+
|
|
353
|
+
// If no JavaScript files found, return skill directory info
|
|
354
|
+
console.log(\`[GEMINI_SKILLS] No JavaScript files found, registering skill directory\`);
|
|
355
|
+
return { type: 'directory', path: skillPath };
|
|
356
|
+
|
|
297
357
|
} catch (error) {
|
|
298
358
|
console.error(\`[GEMINI_SKILLS] Failed to load skill from \${skillPath}:\`, error);
|
|
299
359
|
}
|
|
@@ -454,8 +514,8 @@ if (typeof geminiCLI !== 'undefined') {
|
|
|
454
514
|
generateQwenSkillsIntegration(projectPath, skillsDir) {
|
|
455
515
|
return `// Qwen CLI Skills Integration Hook
|
|
456
516
|
// Auto-generated by Stigmergy CLI v1.2.1
|
|
457
|
-
// Project: ${projectPath}
|
|
458
|
-
// Skills Directory: ${skillsDir}
|
|
517
|
+
// Project: ${projectPath.replace(/\\/g, '/')}
|
|
518
|
+
// Skills Directory: ${skillsDir.replace(/\\/g, '/')}
|
|
459
519
|
|
|
460
520
|
const fs = require('fs');
|
|
461
521
|
const path = require('path');
|
|
@@ -463,7 +523,7 @@ const path = require('path');
|
|
|
463
523
|
class QwenSkillsHook {
|
|
464
524
|
constructor() {
|
|
465
525
|
this.toolName = 'qwen';
|
|
466
|
-
this.skillsDir = path.join('${skillsDir.replace(/\\/g, '
|
|
526
|
+
this.skillsDir = path.join('${skillsDir.replace(/\\/g, '/')}');
|
|
467
527
|
this.loadedSkills = new Map();
|
|
468
528
|
}
|
|
469
529
|
|
|
@@ -503,6 +563,25 @@ class QwenSkillsHook {
|
|
|
503
563
|
|
|
504
564
|
async loadSkill(skillPath) {
|
|
505
565
|
try {
|
|
566
|
+
// Check for skill implementation files in scripts subdirectory
|
|
567
|
+
const scriptsDir = path.join(skillPath, 'scripts');
|
|
568
|
+
if (fs.existsSync(scriptsDir) && fs.statSync(scriptsDir).isDirectory()) {
|
|
569
|
+
const files = fs.readdirSync(scriptsDir);
|
|
570
|
+
const jsFiles = files.filter(file => file.endsWith('.js') && !file.includes('test'));
|
|
571
|
+
|
|
572
|
+
for (const jsFile of jsFiles) {
|
|
573
|
+
const skillModulePath = path.join(scriptsDir, jsFile);
|
|
574
|
+
try {
|
|
575
|
+
const skillModule = require(skillModulePath);
|
|
576
|
+
console.log(\`[QWEN_SKILLS] Loaded module: \${jsFile}\`);
|
|
577
|
+
return skillModule;
|
|
578
|
+
} catch (moduleError) {
|
|
579
|
+
console.warn(\`[QWEN_SKILLS] Could not load \${jsFile}: \${moduleError.message}\`);
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
// Fallback: check for JavaScript files in root directory
|
|
506
585
|
const files = fs.readdirSync(skillPath);
|
|
507
586
|
const jsFiles = files.filter(file => file.endsWith('.js') && !file.includes('test'));
|
|
508
587
|
|
|
@@ -510,11 +589,17 @@ class QwenSkillsHook {
|
|
|
510
589
|
const skillModulePath = path.join(skillPath, jsFile);
|
|
511
590
|
try {
|
|
512
591
|
const skillModule = require(skillModulePath);
|
|
592
|
+
console.log(\`[QWEN_SKILLS] Loaded module: \${jsFile}\`);
|
|
513
593
|
return skillModule;
|
|
514
594
|
} catch (moduleError) {
|
|
515
595
|
console.warn(\`[QWEN_SKILLS] Could not load \${jsFile}: \${moduleError.message}\`);
|
|
516
596
|
}
|
|
517
597
|
}
|
|
598
|
+
|
|
599
|
+
// If no JavaScript files found, return skill directory info
|
|
600
|
+
console.log(\`[QWEN_SKILLS] No JavaScript files found, registering skill directory\`);
|
|
601
|
+
return { type: 'directory', path: skillPath };
|
|
602
|
+
|
|
518
603
|
} catch (error) {
|
|
519
604
|
console.error(\`[QWEN_SKILLS] Failed to load skill from \${skillPath}:\`, error);
|
|
520
605
|
}
|
|
@@ -618,8 +703,8 @@ module.exports = QwenSkillsHook;
|
|
|
618
703
|
generateCodeBuddySkillsIntegration(projectPath, skillsDir) {
|
|
619
704
|
return `// CodeBuddy CLI Skills Integration
|
|
620
705
|
// Auto-generated by Stigmergy CLI v1.2.1
|
|
621
|
-
// Project: ${projectPath}
|
|
622
|
-
// Skills Directory: ${skillsDir}
|
|
706
|
+
// Project: ${projectPath.replace(/\\/g, '/')}
|
|
707
|
+
// Skills Directory: ${skillsDir.replace(/\\/g, '/')}
|
|
623
708
|
|
|
624
709
|
const fs = require('fs');
|
|
625
710
|
const path = require('path');
|
|
@@ -627,7 +712,7 @@ const path = require('path');
|
|
|
627
712
|
class CodeBuddySkillsIntegration {
|
|
628
713
|
constructor() {
|
|
629
714
|
this.toolName = 'codebuddy';
|
|
630
|
-
this.skillsDir = path.join('${skillsDir.replace(/\\/g, '
|
|
715
|
+
this.skillsDir = path.join('${skillsDir.replace(/\\/g, '/')}');
|
|
631
716
|
this.loadedSkills = new Map();
|
|
632
717
|
}
|
|
633
718
|
|
|
@@ -667,6 +752,25 @@ class CodeBuddySkillsIntegration {
|
|
|
667
752
|
|
|
668
753
|
async loadSkill(skillPath) {
|
|
669
754
|
try {
|
|
755
|
+
// Check for skill implementation files in scripts subdirectory
|
|
756
|
+
const scriptsDir = path.join(skillPath, 'scripts');
|
|
757
|
+
if (fs.existsSync(scriptsDir) && fs.statSync(scriptsDir).isDirectory()) {
|
|
758
|
+
const files = fs.readdirSync(scriptsDir);
|
|
759
|
+
const jsFiles = files.filter(file => file.endsWith('.js') && !file.includes('test'));
|
|
760
|
+
|
|
761
|
+
for (const jsFile of jsFiles) {
|
|
762
|
+
const skillModulePath = path.join(scriptsDir, jsFile);
|
|
763
|
+
try {
|
|
764
|
+
const skillModule = require(skillModulePath);
|
|
765
|
+
console.log(\`[CODEBUDDY_SKILLS] Loaded module: \${jsFile}\`);
|
|
766
|
+
return skillModule;
|
|
767
|
+
} catch (moduleError) {
|
|
768
|
+
console.warn(\`[CODEBUDDY_SKILLS] Could not load \${jsFile}: \${moduleError.message}\`);
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
// Fallback: check for JavaScript files in root directory
|
|
670
774
|
const files = fs.readdirSync(skillPath);
|
|
671
775
|
const jsFiles = files.filter(file => file.endsWith('.js') && !file.includes('test'));
|
|
672
776
|
|
|
@@ -674,11 +778,17 @@ class CodeBuddySkillsIntegration {
|
|
|
674
778
|
const skillModulePath = path.join(skillPath, jsFile);
|
|
675
779
|
try {
|
|
676
780
|
const skillModule = require(skillModulePath);
|
|
781
|
+
console.log(\`[CODEBUDDY_SKILLS] Loaded module: \${jsFile}\`);
|
|
677
782
|
return skillModule;
|
|
678
783
|
} catch (moduleError) {
|
|
679
784
|
console.warn(\`[CODEBUDDY_SKILLS] Could not load \${jsFile}: \${moduleError.message}\`);
|
|
680
785
|
}
|
|
681
786
|
}
|
|
787
|
+
|
|
788
|
+
// If no JavaScript files found, return skill directory info
|
|
789
|
+
console.log(\`[CODEBUDDY_SKILLS] No JavaScript files found, registering skill directory\`);
|
|
790
|
+
return { type: 'directory', path: skillPath };
|
|
791
|
+
|
|
682
792
|
} catch (error) {
|
|
683
793
|
console.error(\`[CODEBUDDY_SKILLS] Failed to load skill from \${skillPath}:\`, error);
|
|
684
794
|
}
|
|
@@ -781,8 +891,8 @@ module.exports = CodeBuddySkillsIntegration;
|
|
|
781
891
|
generateCodexSkillsIntegration(projectPath, skillsDir) {
|
|
782
892
|
return `// Codex CLI Skills Integration Handler
|
|
783
893
|
// Auto-generated by Stigmergy CLI v1.2.1
|
|
784
|
-
// Project: ${projectPath}
|
|
785
|
-
// Skills Directory: ${skillsDir}
|
|
894
|
+
// Project: ${projectPath.replace(/\\/g, '/')}
|
|
895
|
+
// Skills Directory: ${skillsDir.replace(/\\/g, '/')}
|
|
786
896
|
|
|
787
897
|
const fs = require('fs');
|
|
788
898
|
const path = require('path');
|
|
@@ -790,7 +900,7 @@ const path = require('path');
|
|
|
790
900
|
class CodexSkillsHandler {
|
|
791
901
|
constructor() {
|
|
792
902
|
this.toolName = 'codex';
|
|
793
|
-
this.skillsDir = path.join('${skillsDir.replace(/\\/g, '
|
|
903
|
+
this.skillsDir = path.join('${skillsDir.replace(/\\/g, '/')}');
|
|
794
904
|
this.loadedSkills = new Map();
|
|
795
905
|
}
|
|
796
906
|
|
|
@@ -830,6 +940,25 @@ class CodexSkillsHandler {
|
|
|
830
940
|
|
|
831
941
|
async loadSkill(skillPath) {
|
|
832
942
|
try {
|
|
943
|
+
// Check for skill implementation files in scripts subdirectory
|
|
944
|
+
const scriptsDir = path.join(skillPath, 'scripts');
|
|
945
|
+
if (fs.existsSync(scriptsDir) && fs.statSync(scriptsDir).isDirectory()) {
|
|
946
|
+
const files = fs.readdirSync(scriptsDir);
|
|
947
|
+
const jsFiles = files.filter(file => file.endsWith('.js') && !file.includes('test'));
|
|
948
|
+
|
|
949
|
+
for (const jsFile of jsFiles) {
|
|
950
|
+
const skillModulePath = path.join(scriptsDir, jsFile);
|
|
951
|
+
try {
|
|
952
|
+
const skillModule = require(skillModulePath);
|
|
953
|
+
console.log(\`[CODEX_SKILLS] Loaded module: \${jsFile}\`);
|
|
954
|
+
return skillModule;
|
|
955
|
+
} catch (moduleError) {
|
|
956
|
+
console.warn(\`[CODEX_SKILLS] Could not load \${jsFile}: \${moduleError.message}\`);
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
// Fallback: check for JavaScript files in root directory
|
|
833
962
|
const files = fs.readdirSync(skillPath);
|
|
834
963
|
const jsFiles = files.filter(file => file.endsWith('.js') && !file.includes('test'));
|
|
835
964
|
|
|
@@ -837,11 +966,17 @@ class CodexSkillsHandler {
|
|
|
837
966
|
const skillModulePath = path.join(skillPath, jsFile);
|
|
838
967
|
try {
|
|
839
968
|
const skillModule = require(skillModulePath);
|
|
969
|
+
console.log(\`[CODEX_SKILLS] Loaded module: \${jsFile}\`);
|
|
840
970
|
return skillModule;
|
|
841
971
|
} catch (moduleError) {
|
|
842
972
|
console.warn(\`[CODEX_SKILLS] Could not load \${jsFile}: \${moduleError.message}\`);
|
|
843
973
|
}
|
|
844
974
|
}
|
|
975
|
+
|
|
976
|
+
// If no JavaScript files found, return skill directory info
|
|
977
|
+
console.log(\`[CODEX_SKILLS] No JavaScript files found, registering skill directory\`);
|
|
978
|
+
return { type: 'directory', path: skillPath };
|
|
979
|
+
|
|
845
980
|
} catch (error) {
|
|
846
981
|
console.error(\`[CODEX_SKILLS] Failed to load skill from \${skillPath}:\`, error);
|
|
847
982
|
}
|
|
@@ -945,8 +1080,8 @@ module.exports = CodexSkillsHandler;
|
|
|
945
1080
|
generateQoderCliSkillsIntegration(projectPath, skillsDir) {
|
|
946
1081
|
return `// QoderCLI Skills Integration Extension
|
|
947
1082
|
// Auto-generated by Stigmergy CLI v1.2.1
|
|
948
|
-
// Project: ${projectPath}
|
|
949
|
-
// Skills Directory: ${skillsDir}
|
|
1083
|
+
// Project: ${projectPath.replace(/\\/g, '/')}
|
|
1084
|
+
// Skills Directory: ${skillsDir.replace(/\\/g, '/')}
|
|
950
1085
|
|
|
951
1086
|
const fs = require('fs');
|
|
952
1087
|
const path = require('path');
|
|
@@ -954,7 +1089,7 @@ const path = require('path');
|
|
|
954
1089
|
class QoderCliSkillsExtension {
|
|
955
1090
|
constructor() {
|
|
956
1091
|
this.toolName = 'qodercli';
|
|
957
|
-
this.skillsDir = path.join('${skillsDir.replace(/\\/g, '
|
|
1092
|
+
this.skillsDir = path.join('${skillsDir.replace(/\\/g, '/')}');
|
|
958
1093
|
this.loadedSkills = new Map();
|
|
959
1094
|
}
|
|
960
1095
|
|
|
@@ -994,6 +1129,25 @@ class QoderCliSkillsExtension {
|
|
|
994
1129
|
|
|
995
1130
|
async loadSkill(skillPath) {
|
|
996
1131
|
try {
|
|
1132
|
+
// Check for skill implementation files in scripts subdirectory
|
|
1133
|
+
const scriptsDir = path.join(skillPath, 'scripts');
|
|
1134
|
+
if (fs.existsSync(scriptsDir) && fs.statSync(scriptsDir).isDirectory()) {
|
|
1135
|
+
const files = fs.readdirSync(scriptsDir);
|
|
1136
|
+
const jsFiles = files.filter(file => file.endsWith('.js') && !file.includes('test'));
|
|
1137
|
+
|
|
1138
|
+
for (const jsFile of jsFiles) {
|
|
1139
|
+
const skillModulePath = path.join(scriptsDir, jsFile);
|
|
1140
|
+
try {
|
|
1141
|
+
const skillModule = require(skillModulePath);
|
|
1142
|
+
console.log(\`[QODERCLI_SKILLS] Loaded module: \${jsFile}\`);
|
|
1143
|
+
return skillModule;
|
|
1144
|
+
} catch (moduleError) {
|
|
1145
|
+
console.warn(\`[QODERCLI_SKILLS] Could not load \${jsFile}: \${moduleError.message}\`);
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
// Fallback: check for JavaScript files in root directory
|
|
997
1151
|
const files = fs.readdirSync(skillPath);
|
|
998
1152
|
const jsFiles = files.filter(file => file.endsWith('.js') && !file.includes('test'));
|
|
999
1153
|
|
|
@@ -1001,11 +1155,17 @@ class QoderCliSkillsExtension {
|
|
|
1001
1155
|
const skillModulePath = path.join(skillPath, jsFile);
|
|
1002
1156
|
try {
|
|
1003
1157
|
const skillModule = require(skillModulePath);
|
|
1158
|
+
console.log(\`[QODERCLI_SKILLS] Loaded module: \${jsFile}\`);
|
|
1004
1159
|
return skillModule;
|
|
1005
1160
|
} catch (moduleError) {
|
|
1006
1161
|
console.warn(\`[QODERCLI_SKILLS] Could not load \${jsFile}: \${moduleError.message}\`);
|
|
1007
1162
|
}
|
|
1008
1163
|
}
|
|
1164
|
+
|
|
1165
|
+
// If no JavaScript files found, return skill directory info
|
|
1166
|
+
console.log(\`[QODERCLI_SKILLS] No JavaScript files found, registering skill directory\`);
|
|
1167
|
+
return { type: 'directory', path: skillPath };
|
|
1168
|
+
|
|
1009
1169
|
} catch (error) {
|
|
1010
1170
|
console.error(\`[QODERCLI_SKILLS] Failed to load skill from \${skillPath}:\`, error);
|
|
1011
1171
|
}
|
|
@@ -1109,8 +1269,8 @@ module.exports = QoderCliSkillsExtension;
|
|
|
1109
1269
|
|
|
1110
1270
|
return `// ${className}
|
|
1111
1271
|
// Auto-generated by Stigmergy CLI v1.2.1
|
|
1112
|
-
// Project: ${projectPath}
|
|
1113
|
-
// Skills Directory: ${skillsDir}
|
|
1272
|
+
// Project: ${projectPath.replace(/\\/g, '/')}
|
|
1273
|
+
// Skills Directory: ${skillsDir.replace(/\\/g, '/')}
|
|
1114
1274
|
|
|
1115
1275
|
const fs = require('fs');
|
|
1116
1276
|
const path = require('path');
|
|
@@ -1118,7 +1278,7 @@ const path = require('path');
|
|
|
1118
1278
|
class ${className} {
|
|
1119
1279
|
constructor() {
|
|
1120
1280
|
this.toolName = '${cliName}';
|
|
1121
|
-
this.skillsDir = path.join('${skillsDir.replace(/\\/g, '
|
|
1281
|
+
this.skillsDir = path.join('${skillsDir.replace(/\\/g, '/')}');
|
|
1122
1282
|
this.loadedSkills = new Map();
|
|
1123
1283
|
}
|
|
1124
1284
|
|
|
@@ -1158,6 +1318,25 @@ class ${className} {
|
|
|
1158
1318
|
|
|
1159
1319
|
async loadSkill(skillPath) {
|
|
1160
1320
|
try {
|
|
1321
|
+
// Check for skill implementation files in scripts subdirectory
|
|
1322
|
+
const scriptsDir = path.join(skillPath, 'scripts');
|
|
1323
|
+
if (fs.existsSync(scriptsDir) && fs.statSync(scriptsDir).isDirectory()) {
|
|
1324
|
+
const files = fs.readdirSync(scriptsDir);
|
|
1325
|
+
const jsFiles = files.filter(file => file.endsWith('.js') && !file.includes('test'));
|
|
1326
|
+
|
|
1327
|
+
for (const jsFile of jsFiles) {
|
|
1328
|
+
const skillModulePath = path.join(scriptsDir, jsFile);
|
|
1329
|
+
try {
|
|
1330
|
+
const skillModule = require(skillModulePath);
|
|
1331
|
+
console.log(\`[\${this.toolName.toUpperCase()}_SKILLS] Loaded module: \${jsFile}\`);
|
|
1332
|
+
return skillModule;
|
|
1333
|
+
} catch (moduleError) {
|
|
1334
|
+
console.warn(\`[\${this.toolName.toUpperCase()}_SKILLS] Could not load \${jsFile}: \${moduleError.message}\`);
|
|
1335
|
+
}
|
|
1336
|
+
}
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1339
|
+
// Fallback: check for JavaScript files in root directory
|
|
1161
1340
|
const files = fs.readdirSync(skillPath);
|
|
1162
1341
|
const jsFiles = files.filter(file => file.endsWith('.js') && !file.includes('test'));
|
|
1163
1342
|
|
|
@@ -1165,11 +1344,17 @@ class ${className} {
|
|
|
1165
1344
|
const skillModulePath = path.join(skillPath, jsFile);
|
|
1166
1345
|
try {
|
|
1167
1346
|
const skillModule = require(skillModulePath);
|
|
1347
|
+
console.log(\`[\${this.toolName.toUpperCase()}_SKILLS] Loaded module: \${jsFile}\`);
|
|
1168
1348
|
return skillModule;
|
|
1169
1349
|
} catch (moduleError) {
|
|
1170
1350
|
console.warn(\`[\${this.toolName.toUpperCase()}_SKILLS] Could not load \${jsFile}: \${moduleError.message}\`);
|
|
1171
1351
|
}
|
|
1172
1352
|
}
|
|
1353
|
+
|
|
1354
|
+
// If no JavaScript files found, return skill directory info
|
|
1355
|
+
console.log(\`[\${this.toolName.toUpperCase()}_SKILLS] No JavaScript files found, registering skill directory\`);
|
|
1356
|
+
return { type: 'directory', path: skillPath };
|
|
1357
|
+
|
|
1173
1358
|
} catch (error) {
|
|
1174
1359
|
console.error(\`[\${this.toolName.toUpperCase()}_SKILLS] Failed to load skill from \${skillPath}:\`, error);
|
|
1175
1360
|
}
|
|
@@ -465,6 +465,31 @@ class EnhancedCLIInstaller {
|
|
|
465
465
|
};
|
|
466
466
|
}
|
|
467
467
|
|
|
468
|
+
// Check if tool requires bun runtime
|
|
469
|
+
if (toolInfo.requiresBun) {
|
|
470
|
+
const bunAvailable = await this.checkBunAvailable();
|
|
471
|
+
if (!bunAvailable) {
|
|
472
|
+
this.log('warn', `${toolInfo.name} requires bun, but bun is not installed`);
|
|
473
|
+
this.log('info', `Installing bun first...`);
|
|
474
|
+
|
|
475
|
+
const bunResult = await this.executeInstallationCommand('npm install bun -g');
|
|
476
|
+
|
|
477
|
+
if (!bunResult.success) {
|
|
478
|
+
return {
|
|
479
|
+
success: false,
|
|
480
|
+
error: `Failed to install bun (required for ${toolInfo.name}): ${bunResult.error}`
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
this.log('success', 'Bun installed successfully');
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
// Check if install command contains multiple steps (&&)
|
|
489
|
+
if (toolInfo.install.includes('&&')) {
|
|
490
|
+
return await this.executeMultiStepInstallation(toolInfo);
|
|
491
|
+
}
|
|
492
|
+
|
|
468
493
|
const [command, ...args] = toolInfo.install.split(' ');
|
|
469
494
|
|
|
470
495
|
this.log('debug', `Executing: ${toolInfo.install}`);
|
|
@@ -1089,6 +1114,96 @@ class EnhancedCLIInstaller {
|
|
|
1089
1114
|
details: installations
|
|
1090
1115
|
};
|
|
1091
1116
|
}
|
|
1117
|
+
|
|
1118
|
+
/**
|
|
1119
|
+
* Check if bun runtime is available
|
|
1120
|
+
* @returns {Promise<boolean>} True if bun is available
|
|
1121
|
+
*/
|
|
1122
|
+
async checkBunAvailable() {
|
|
1123
|
+
const { spawnSync } = require('child_process');
|
|
1124
|
+
|
|
1125
|
+
try {
|
|
1126
|
+
const result = spawnSync('bun', ['--version'], {
|
|
1127
|
+
stdio: 'pipe',
|
|
1128
|
+
shell: true
|
|
1129
|
+
});
|
|
1130
|
+
|
|
1131
|
+
return result.status === 0 || result.error === undefined;
|
|
1132
|
+
} catch (error) {
|
|
1133
|
+
return false;
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
/**
|
|
1138
|
+
* Execute multi-step installation (commands with &&)
|
|
1139
|
+
* @param {Object} toolInfo - Tool information
|
|
1140
|
+
* @returns {Promise<Object>} Installation result
|
|
1141
|
+
*/
|
|
1142
|
+
async executeMultiStepInstallation(toolInfo) {
|
|
1143
|
+
const steps = toolInfo.install.split('&&').map(s => s.trim());
|
|
1144
|
+
|
|
1145
|
+
this.log('info', `Executing multi-step installation (${steps.length} steps) for ${toolInfo.name}...`);
|
|
1146
|
+
|
|
1147
|
+
for (let i = 0; i < steps.length; i++) {
|
|
1148
|
+
const step = steps[i];
|
|
1149
|
+
const stepNumber = i + 1;
|
|
1150
|
+
|
|
1151
|
+
this.log('info', `Step ${stepNumber}/${steps.length}: ${step}`);
|
|
1152
|
+
|
|
1153
|
+
const result = await this.executeInstallationCommand(step);
|
|
1154
|
+
|
|
1155
|
+
if (!result.success) {
|
|
1156
|
+
this.log('error', `Step ${stepNumber} failed: ${result.error}`);
|
|
1157
|
+
return {
|
|
1158
|
+
success: false,
|
|
1159
|
+
error: `Installation failed at step ${stepNumber}: ${result.error}`,
|
|
1160
|
+
failedAtStep: stepNumber,
|
|
1161
|
+
failedCommand: step
|
|
1162
|
+
};
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
this.log('success', `Step ${stepNumber}/${steps.length} completed`);
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
this.log('success', `All ${steps.length} steps completed successfully for ${toolInfo.name}`);
|
|
1169
|
+
|
|
1170
|
+
return {
|
|
1171
|
+
success: true,
|
|
1172
|
+
stepsCompleted: steps.length
|
|
1173
|
+
};
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
/**
|
|
1177
|
+
* Execute a single installation command
|
|
1178
|
+
* @param {string} command - Installation command
|
|
1179
|
+
* @returns {Promise<Object>} Execution result
|
|
1180
|
+
*/
|
|
1181
|
+
async executeInstallationCommand(command) {
|
|
1182
|
+
const { executeCommand } = require('../utils');
|
|
1183
|
+
|
|
1184
|
+
try {
|
|
1185
|
+
const result = await executeCommand(command, [], {
|
|
1186
|
+
stdio: this.verbose ? 'inherit' : 'pipe',
|
|
1187
|
+
shell: true,
|
|
1188
|
+
timeout: 300000 // 5 minutes
|
|
1189
|
+
});
|
|
1190
|
+
|
|
1191
|
+
if (result.success) {
|
|
1192
|
+
return { success: true };
|
|
1193
|
+
} else {
|
|
1194
|
+
return {
|
|
1195
|
+
success: false,
|
|
1196
|
+
error: result.error || `Command exited with code ${result.code}`,
|
|
1197
|
+
code: result.code
|
|
1198
|
+
};
|
|
1199
|
+
}
|
|
1200
|
+
} catch (error) {
|
|
1201
|
+
return {
|
|
1202
|
+
success: false,
|
|
1203
|
+
error: error.message
|
|
1204
|
+
};
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1092
1207
|
}
|
|
1093
1208
|
|
|
1094
1209
|
module.exports = EnhancedCLIInstaller;
|
|
@@ -121,11 +121,11 @@ export class StigmergySkillManager {
|
|
|
121
121
|
|
|
122
122
|
/**
|
|
123
123
|
* Sync skills to CLI configuration files
|
|
124
|
-
* Also
|
|
124
|
+
* Also copies skills to CLI-specific directories and refreshes cache
|
|
125
125
|
* @returns {Promise<void>}
|
|
126
126
|
*/
|
|
127
127
|
async sync() {
|
|
128
|
-
console.log('[INFO] Syncing skills to CLI
|
|
128
|
+
console.log('[INFO] Syncing skills to CLI directories...');
|
|
129
129
|
|
|
130
130
|
try {
|
|
131
131
|
// Refresh the LocalSkillScanner cache
|
|
@@ -152,10 +152,14 @@ export class StigmergySkillManager {
|
|
|
152
152
|
return;
|
|
153
153
|
}
|
|
154
154
|
|
|
155
|
-
//
|
|
155
|
+
// Step 1: Copy skills to CLI-specific directories
|
|
156
|
+
console.log('[INFO] Copying skills to CLI directories...');
|
|
157
|
+
const copyResults = await this.syncSkillFiles(skills);
|
|
158
|
+
|
|
159
|
+
// Step 2: Generate <available_skills> XML
|
|
156
160
|
const skillsXml = this.generateSkillsXml(skills);
|
|
157
161
|
|
|
158
|
-
// All CLI configuration files to update
|
|
162
|
+
// Step 3: All CLI configuration files to update
|
|
159
163
|
const cliFiles = [
|
|
160
164
|
'AGENTS.md', // Universal config
|
|
161
165
|
'claude.md', // Claude CLI
|
|
@@ -165,7 +169,9 @@ export class StigmergySkillManager {
|
|
|
165
169
|
'qodercli.md', // Qoder CLI
|
|
166
170
|
'codebuddy.md', // CodeBuddy CLI
|
|
167
171
|
'copilot.md', // Copilot CLI
|
|
168
|
-
'codex.md'
|
|
172
|
+
'codex.md', // Codex CLI
|
|
173
|
+
'opencode.md', // OpenCode CLI
|
|
174
|
+
'oh-my-opencode.md' // Oh-My-OpenCode Plugin Manager
|
|
169
175
|
];
|
|
170
176
|
|
|
171
177
|
let syncedCount = 0;
|
|
@@ -191,19 +197,102 @@ export class StigmergySkillManager {
|
|
|
191
197
|
|
|
192
198
|
// Output sync result summary
|
|
193
199
|
console.log(`\n[OK] Sync completed:`);
|
|
194
|
-
console.log(` -
|
|
200
|
+
console.log(` - Skills copied: ${copyResults.copied} to ${copyResults.targets} CLI directory(ies)`);
|
|
201
|
+
console.log(` - Config updated: ${syncedCount} file(s)`);
|
|
195
202
|
if (createdCount > 0) {
|
|
196
|
-
console.log(` -
|
|
203
|
+
console.log(` - Config created: ${createdCount} file(s)`);
|
|
197
204
|
}
|
|
198
205
|
if (skippedCount > 0) {
|
|
199
|
-
console.log(` -
|
|
206
|
+
console.log(` - Config skipped: ${skippedCount} file(s)`);
|
|
200
207
|
}
|
|
201
|
-
console.log(` -
|
|
208
|
+
console.log(` - Total skills: ${skills.length}`);
|
|
202
209
|
} catch (err) {
|
|
203
210
|
console.error(`[X] Sync failed: ${err.message}`);
|
|
204
211
|
throw err;
|
|
205
212
|
}
|
|
206
213
|
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Copy skill files to CLI-specific directories
|
|
217
|
+
* @private
|
|
218
|
+
* @param {Array} skills - List of skills to copy
|
|
219
|
+
* @returns {Promise<Object>} Copy results
|
|
220
|
+
*/
|
|
221
|
+
async syncSkillFiles(skills) {
|
|
222
|
+
const fsPromises = await import('fs/promises');
|
|
223
|
+
|
|
224
|
+
// Define CLI skill directories
|
|
225
|
+
const cliSkillDirs = [
|
|
226
|
+
{ name: 'Claude', path: path.join(os.homedir(), '.claude', 'skills') },
|
|
227
|
+
{ name: 'Universal', path: path.join(process.cwd(), '.agent', 'skills') },
|
|
228
|
+
{ name: 'Universal (global)', path: path.join(os.homedir(), '.agent', 'skills') }
|
|
229
|
+
];
|
|
230
|
+
|
|
231
|
+
let copiedCount = 0;
|
|
232
|
+
let targets = 0;
|
|
233
|
+
|
|
234
|
+
for (const cliDir of cliSkillDirs) {
|
|
235
|
+
try {
|
|
236
|
+
// Ensure CLI directory exists
|
|
237
|
+
await fsPromises.mkdir(cliDir.path, { recursive: true });
|
|
238
|
+
|
|
239
|
+
// Copy each skill
|
|
240
|
+
for (const skill of skills) {
|
|
241
|
+
const skillName = skill.name;
|
|
242
|
+
const sourceDir = path.join(this.skillsDir, skillName);
|
|
243
|
+
const targetDir = path.join(cliDir.path, skillName);
|
|
244
|
+
|
|
245
|
+
try {
|
|
246
|
+
// Check if source exists
|
|
247
|
+
await fsPromises.access(sourceDir);
|
|
248
|
+
|
|
249
|
+
// Remove existing if present
|
|
250
|
+
await fsPromises.rm(targetDir, { recursive: true, force: true });
|
|
251
|
+
|
|
252
|
+
// Copy directory
|
|
253
|
+
await this.copyDirectoryRecursive(sourceDir, targetDir);
|
|
254
|
+
copiedCount++;
|
|
255
|
+
} catch (err) {
|
|
256
|
+
// Skip if source doesn't exist or copy fails
|
|
257
|
+
if (process.env.DEBUG === 'true') {
|
|
258
|
+
console.log(`[DEBUG] Skipped ${skillName} for ${cliDir.name}: ${err.message}`);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
targets++;
|
|
264
|
+
console.log(` [OK] Synced to ${cliDir.name}: ${cliDir.path}`);
|
|
265
|
+
} catch (err) {
|
|
266
|
+
console.log(` [INFO] Skipped ${cliDir.name}: ${err.message}`);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
return { copied: copiedCount, targets: targets };
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Recursively copy a directory
|
|
275
|
+
* @private
|
|
276
|
+
* @param {string} src - Source directory
|
|
277
|
+
* @param {string} dest - Destination directory
|
|
278
|
+
*/
|
|
279
|
+
async copyDirectoryRecursive(src, dest) {
|
|
280
|
+
const fsPromises = await import('fs/promises');
|
|
281
|
+
|
|
282
|
+
await fsPromises.mkdir(dest, { recursive: true });
|
|
283
|
+
const entries = await fsPromises.readdir(src, { withFileTypes: true });
|
|
284
|
+
|
|
285
|
+
for (const entry of entries) {
|
|
286
|
+
const srcPath = path.join(src, entry.name);
|
|
287
|
+
const destPath = path.join(dest, entry.name);
|
|
288
|
+
|
|
289
|
+
if (entry.isDirectory()) {
|
|
290
|
+
await this.copyDirectoryRecursive(srcPath, destPath);
|
|
291
|
+
} else {
|
|
292
|
+
await fsPromises.copyFile(srcPath, destPath);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
207
296
|
|
|
208
297
|
/**
|
|
209
298
|
* Sync skills to a single file
|
|
@@ -13,7 +13,9 @@ export class SkillParser {
|
|
|
13
13
|
* @returns {Object} Parsed metadata
|
|
14
14
|
*/
|
|
15
15
|
parseMetadata(content) {
|
|
16
|
-
|
|
16
|
+
// Normalize line endings to LF
|
|
17
|
+
const normalizedContent = content.replace(/\r\n/g, '\n');
|
|
18
|
+
const match = normalizedContent.match(/^---\n(.*?)\n---/s);
|
|
17
19
|
if (!match) {
|
|
18
20
|
return {};
|
|
19
21
|
}
|
|
@@ -78,8 +80,10 @@ export class SkillParser {
|
|
|
78
80
|
* @returns {string} Content without frontmatter
|
|
79
81
|
*/
|
|
80
82
|
extractContent(content) {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
+
// Normalize line endings to LF
|
|
84
|
+
const normalizedContent = content.replace(/\r\n/g, '\n');
|
|
85
|
+
const match = normalizedContent.match(/^---\n.*?\n---\n(.*)$/s);
|
|
86
|
+
return match ? match[1].trim() : normalizedContent;
|
|
83
87
|
}
|
|
84
88
|
|
|
85
89
|
/**
|