stigmergy 1.2.6 → 1.2.10

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 (82) hide show
  1. package/README.md +69 -20
  2. package/STIGMERGY.md +26 -7
  3. package/docs/MULTI_USER_WIKI_COLLABORATION_SYSTEM.md +523 -0
  4. package/docs/PROMPT_BASED_SKILLS_SYSTEM_DESIGN.md +458 -0
  5. package/docs/SKILL_IMPLEMENTATION_CONSTRAINTS_AND_ALIGNMENT.md +423 -0
  6. package/docs/TECHNICAL_FEASIBILITY_ANALYSIS.md +308 -0
  7. package/examples/multilingual-hook-demo.js +125 -0
  8. package/package.json +30 -19
  9. package/scripts/dependency-analyzer.js +101 -0
  10. package/scripts/generate-cli-docs.js +64 -0
  11. package/scripts/postuninstall.js +46 -0
  12. package/scripts/preuninstall.js +85 -0
  13. package/scripts/run-layered-tests.js +3 -3
  14. package/src/adapters/claude/install_claude_integration.js +37 -37
  15. package/src/adapters/codebuddy/install_codebuddy_integration.js +66 -63
  16. package/src/adapters/codex/install_codex_integration.js +54 -55
  17. package/src/adapters/copilot/install_copilot_integration.js +46 -46
  18. package/src/adapters/gemini/install_gemini_integration.js +68 -68
  19. package/src/adapters/iflow/install_iflow_integration.js +77 -77
  20. package/src/adapters/qoder/install_qoder_integration.js +76 -76
  21. package/src/adapters/qwen/install_qwen_integration.js +23 -23
  22. package/src/cli/router.js +713 -163
  23. package/src/commands/skill-bridge.js +39 -0
  24. package/src/commands/skill-handler.js +150 -0
  25. package/src/commands/skill.js +127 -0
  26. package/src/core/cache_cleaner.js +767 -767
  27. package/src/core/cli_help_analyzer.js +680 -680
  28. package/src/core/cli_parameter_handler.js +132 -132
  29. package/src/core/cli_path_detector.js +573 -0
  30. package/src/core/cli_tools.js +160 -89
  31. package/src/core/coordination/index.js +16 -16
  32. package/src/core/coordination/nodejs/AdapterManager.js +130 -102
  33. package/src/core/coordination/nodejs/CLCommunication.js +132 -132
  34. package/src/core/coordination/nodejs/CLIIntegrationManager.js +272 -272
  35. package/src/core/coordination/nodejs/HealthChecker.js +76 -76
  36. package/src/core/coordination/nodejs/HookDeploymentManager.js +463 -274
  37. package/src/core/coordination/nodejs/StatisticsCollector.js +71 -71
  38. package/src/core/coordination/nodejs/index.js +90 -90
  39. package/src/core/coordination/nodejs/utils/Logger.js +29 -29
  40. package/src/core/directory_permission_manager.js +568 -0
  41. package/src/core/enhanced_cli_installer.js +609 -0
  42. package/src/core/error_handler.js +406 -406
  43. package/src/core/installer.js +263 -119
  44. package/src/core/memory_manager.js +83 -83
  45. package/src/core/multilingual/language-pattern-manager.js +200 -0
  46. package/src/core/persistent_shell_configurator.js +468 -0
  47. package/src/core/rest_client.js +160 -160
  48. package/src/core/skills/StigmergySkillManager.js +357 -0
  49. package/src/core/skills/__tests__/SkillInstaller.test.js +275 -0
  50. package/src/core/skills/__tests__/SkillParser.test.js +202 -0
  51. package/src/core/skills/__tests__/SkillReader.test.js +189 -0
  52. package/src/core/skills/cli-command-test.js +201 -0
  53. package/src/core/skills/comprehensive-e2e-test.js +473 -0
  54. package/src/core/skills/e2e-test.js +267 -0
  55. package/src/core/skills/embedded-openskills/SkillInstaller.js +438 -0
  56. package/src/core/skills/embedded-openskills/SkillParser.js +123 -0
  57. package/src/core/skills/embedded-openskills/SkillReader.js +143 -0
  58. package/src/core/skills/integration-test.js +248 -0
  59. package/src/core/skills/package.json +6 -0
  60. package/src/core/skills/regression-test.js +285 -0
  61. package/src/core/skills/run-all-tests.js +129 -0
  62. package/src/core/skills/sync-test.js +210 -0
  63. package/src/core/skills/test-runner.js +242 -0
  64. package/src/core/smart_router.js +261 -249
  65. package/src/core/upgrade_manager.js +48 -20
  66. package/src/index.js +30 -30
  67. package/src/test/cli-availability-checker.js +194 -194
  68. package/src/test/test-environment.js +289 -289
  69. package/src/utils/helpers.js +18 -35
  70. package/src/utils.js +921 -921
  71. package/src/weatherProcessor.js +228 -228
  72. package/test/multilingual/hook-deployment.test.js +91 -0
  73. package/test/multilingual/language-pattern-manager.test.js +140 -0
  74. package/test/multilingual/system-test.js +85 -0
  75. package/src/auth.js +0 -173
  76. package/src/auth_command.js +0 -208
  77. package/src/calculator.js +0 -313
  78. package/src/core/enhanced_installer.js +0 -479
  79. package/src/core/enhanced_uninstaller.js +0 -638
  80. package/src/data_encryption.js +0 -143
  81. package/src/data_structures.js +0 -440
  82. package/src/deploy.js +0 -55
@@ -1,160 +1,160 @@
1
- /**
2
- * Simple REST API Client
3
- * Provides basic HTTP methods for interacting with REST APIs
4
- */
5
-
6
- class RestClient {
7
- /**
8
- * Create a new REST client
9
- * @param {string} baseURL - The base URL for API requests
10
- * @param {Object} defaultHeaders - Default headers to include in all requests
11
- */
12
- constructor(baseURL = '', defaultHeaders = {}) {
13
- this.baseURL = baseURL;
14
- this.defaultHeaders = {
15
- 'Content-Type': 'application/json',
16
- ...defaultHeaders,
17
- };
18
- }
19
-
20
- /**
21
- * Make an HTTP request
22
- * @param {string} method - HTTP method (GET, POST, PUT, DELETE, etc.)
23
- * @param {string} url - Request URL (will be appended to baseURL)
24
- * @param {Object} options - Request options
25
- * @returns {Promise<Object>} Response data
26
- */
27
- async request(method, url, options = {}) {
28
- const { headers = {}, body = null, params = {}, timeout = 10000 } = options;
29
-
30
- // Construct full URL
31
- let fullURL = this.baseURL + url;
32
-
33
- // Add query parameters
34
- if (Object.keys(params).length > 0) {
35
- const queryParams = new URLSearchParams(params);
36
- fullURL += (fullURL.includes('?') ? '&' : '?') + queryParams.toString();
37
- }
38
-
39
- // Merge headers
40
- const mergedHeaders = {
41
- ...this.defaultHeaders,
42
- ...headers,
43
- };
44
-
45
- // Prepare fetch options
46
- const fetchOptions = {
47
- method,
48
- headers: mergedHeaders,
49
- timeout,
50
- };
51
-
52
- // Add body for methods that support it
53
- if (body && ['POST', 'PUT', 'PATCH', 'DELETE'].includes(method)) {
54
- fetchOptions.body =
55
- typeof body === 'object' ? JSON.stringify(body) : body;
56
- }
57
-
58
- try {
59
- const response = await fetch(fullURL, fetchOptions);
60
- const contentType = response.headers.get('content-type');
61
-
62
- let data;
63
- if (contentType && contentType.includes('application/json')) {
64
- data = await response.json();
65
- } else {
66
- data = await response.text();
67
- }
68
-
69
- if (!response.ok) {
70
- throw new Error(`HTTP ${response.status}: ${response.statusText}`);
71
- }
72
-
73
- return {
74
- status: response.status,
75
- statusText: response.statusText,
76
- headers: Object.fromEntries(response.headers.entries()),
77
- data,
78
- };
79
- } catch (error) {
80
- throw new Error(`Request failed: ${error.message}`);
81
- }
82
- }
83
-
84
- /**
85
- * Make a GET request
86
- * @param {string} url - Request URL
87
- * @param {Object} options - Request options
88
- * @returns {Promise<Object>} Response data
89
- */
90
- async get(url, options = {}) {
91
- return this.request('GET', url, options);
92
- }
93
-
94
- /**
95
- * Make a POST request
96
- * @param {string} url - Request URL
97
- * @param {Object} data - Data to send in request body
98
- * @param {Object} options - Request options
99
- * @returns {Promise<Object>} Response data
100
- */
101
- async post(url, data, options = {}) {
102
- return this.request('POST', url, { ...options, body: data });
103
- }
104
-
105
- /**
106
- * Make a PUT request
107
- * @param {string} url - Request URL
108
- * @param {Object} data - Data to send in request body
109
- * @param {Object} options - Request options
110
- * @returns {Promise<Object>} Response data
111
- */
112
- async put(url, data, options = {}) {
113
- return this.request('PUT', url, { ...options, body: data });
114
- }
115
-
116
- /**
117
- * Make a PATCH request
118
- * @param {string} url - Request URL
119
- * @param {Object} data - Data to send in request body
120
- * @param {Object} options - Request options
121
- * @returns {Promise<Object>} Response data
122
- */
123
- async patch(url, data, options = {}) {
124
- return this.request('PATCH', url, { ...options, body: data });
125
- }
126
-
127
- /**
128
- * Make a DELETE request
129
- * @param {string} url - Request URL
130
- * @param {Object} options - Request options
131
- * @returns {Promise<Object>} Response data
132
- */
133
- async delete(url, options = {}) {
134
- return this.request('DELETE', url, options);
135
- }
136
-
137
- /**
138
- * Set default headers
139
- * @param {Object} headers - Headers to set as default
140
- */
141
- setDefaultHeaders(headers) {
142
- this.defaultHeaders = {
143
- ...this.defaultHeaders,
144
- ...headers,
145
- };
146
- }
147
-
148
- /**
149
- * Set authorization header
150
- * @param {string} token - Authorization token
151
- * @param {string} type - Authorization type (Bearer, Basic, etc.)
152
- */
153
- setAuthorization(token, type = 'Bearer') {
154
- this.setDefaultHeaders({
155
- Authorization: `${type} ${token}`,
156
- });
157
- }
158
- }
159
-
160
- module.exports = RestClient;
1
+ /**
2
+ * Simple REST API Client
3
+ * Provides basic HTTP methods for interacting with REST APIs
4
+ */
5
+
6
+ class RestClient {
7
+ /**
8
+ * Create a new REST client
9
+ * @param {string} baseURL - The base URL for API requests
10
+ * @param {Object} defaultHeaders - Default headers to include in all requests
11
+ */
12
+ constructor(baseURL = '', defaultHeaders = {}) {
13
+ this.baseURL = baseURL;
14
+ this.defaultHeaders = {
15
+ 'Content-Type': 'application/json',
16
+ ...defaultHeaders,
17
+ };
18
+ }
19
+
20
+ /**
21
+ * Make an HTTP request
22
+ * @param {string} method - HTTP method (GET, POST, PUT, DELETE, etc.)
23
+ * @param {string} url - Request URL (will be appended to baseURL)
24
+ * @param {Object} options - Request options
25
+ * @returns {Promise<Object>} Response data
26
+ */
27
+ async request(method, url, options = {}) {
28
+ const { headers = {}, body = null, params = {}, timeout = 10000 } = options;
29
+
30
+ // Construct full URL
31
+ let fullURL = this.baseURL + url;
32
+
33
+ // Add query parameters
34
+ if (Object.keys(params).length > 0) {
35
+ const queryParams = new URLSearchParams(params);
36
+ fullURL += (fullURL.includes('?') ? '&' : '?') + queryParams.toString();
37
+ }
38
+
39
+ // Merge headers
40
+ const mergedHeaders = {
41
+ ...this.defaultHeaders,
42
+ ...headers,
43
+ };
44
+
45
+ // Prepare fetch options
46
+ const fetchOptions = {
47
+ method,
48
+ headers: mergedHeaders,
49
+ timeout,
50
+ };
51
+
52
+ // Add body for methods that support it
53
+ if (body && ['POST', 'PUT', 'PATCH', 'DELETE'].includes(method)) {
54
+ fetchOptions.body =
55
+ typeof body === 'object' ? JSON.stringify(body) : body;
56
+ }
57
+
58
+ try {
59
+ const response = await fetch(fullURL, fetchOptions);
60
+ const contentType = response.headers.get('content-type');
61
+
62
+ let data;
63
+ if (contentType && contentType.includes('application/json')) {
64
+ data = await response.json();
65
+ } else {
66
+ data = await response.text();
67
+ }
68
+
69
+ if (!response.ok) {
70
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
71
+ }
72
+
73
+ return {
74
+ status: response.status,
75
+ statusText: response.statusText,
76
+ headers: Object.fromEntries(response.headers.entries()),
77
+ data,
78
+ };
79
+ } catch (error) {
80
+ throw new Error(`Request failed: ${error.message}`);
81
+ }
82
+ }
83
+
84
+ /**
85
+ * Make a GET request
86
+ * @param {string} url - Request URL
87
+ * @param {Object} options - Request options
88
+ * @returns {Promise<Object>} Response data
89
+ */
90
+ async get(url, options = {}) {
91
+ return this.request('GET', url, options);
92
+ }
93
+
94
+ /**
95
+ * Make a POST request
96
+ * @param {string} url - Request URL
97
+ * @param {Object} data - Data to send in request body
98
+ * @param {Object} options - Request options
99
+ * @returns {Promise<Object>} Response data
100
+ */
101
+ async post(url, data, options = {}) {
102
+ return this.request('POST', url, { ...options, body: data });
103
+ }
104
+
105
+ /**
106
+ * Make a PUT request
107
+ * @param {string} url - Request URL
108
+ * @param {Object} data - Data to send in request body
109
+ * @param {Object} options - Request options
110
+ * @returns {Promise<Object>} Response data
111
+ */
112
+ async put(url, data, options = {}) {
113
+ return this.request('PUT', url, { ...options, body: data });
114
+ }
115
+
116
+ /**
117
+ * Make a PATCH request
118
+ * @param {string} url - Request URL
119
+ * @param {Object} data - Data to send in request body
120
+ * @param {Object} options - Request options
121
+ * @returns {Promise<Object>} Response data
122
+ */
123
+ async patch(url, data, options = {}) {
124
+ return this.request('PATCH', url, { ...options, body: data });
125
+ }
126
+
127
+ /**
128
+ * Make a DELETE request
129
+ * @param {string} url - Request URL
130
+ * @param {Object} options - Request options
131
+ * @returns {Promise<Object>} Response data
132
+ */
133
+ async delete(url, options = {}) {
134
+ return this.request('DELETE', url, options);
135
+ }
136
+
137
+ /**
138
+ * Set default headers
139
+ * @param {Object} headers - Headers to set as default
140
+ */
141
+ setDefaultHeaders(headers) {
142
+ this.defaultHeaders = {
143
+ ...this.defaultHeaders,
144
+ ...headers,
145
+ };
146
+ }
147
+
148
+ /**
149
+ * Set authorization header
150
+ * @param {string} token - Authorization token
151
+ * @param {string} type - Authorization type (Bearer, Basic, etc.)
152
+ */
153
+ setAuthorization(token, type = 'Bearer') {
154
+ this.setDefaultHeaders({
155
+ Authorization: `${type} ${token}`,
156
+ });
157
+ }
158
+ }
159
+
160
+ module.exports = RestClient;
@@ -0,0 +1,357 @@
1
+ /**
2
+ * StigmergySkillManager - Unified Skill Manager
3
+ *
4
+ * Integrates OpenSkills core functionality + Stigmergy cross-CLI routing
5
+ *
6
+ * License: Apache 2.0
7
+ */
8
+
9
+ import { SkillReader } from './embedded-openskills/SkillReader.js';
10
+ import { SkillInstaller } from './embedded-openskills/SkillInstaller.js';
11
+ import { SkillParser } from './embedded-openskills/SkillParser.js';
12
+ import fs from 'fs/promises';
13
+ import path from 'path';
14
+ import os from 'os';
15
+
16
+ export class StigmergySkillManager {
17
+ constructor(options = {}) {
18
+ this.skillsDir = options.skillsDir || path.join(os.homedir(), '.stigmergy/skills');
19
+
20
+ // Create custom search paths (prioritize skillsDir)
21
+ const customSearchPaths = [
22
+ this.skillsDir, // Stigmergy skills dir (highest priority)
23
+ path.join(process.cwd(), '.agent/skills'), // Project universal
24
+ path.join(os.homedir(), '.agent/skills'), // Global universal
25
+ path.join(process.cwd(), '.claude/skills'), // Project Claude
26
+ path.join(os.homedir(), '.claude/skills'), // Global Claude
27
+ ];
28
+
29
+ this.reader = new SkillReader(customSearchPaths);
30
+ this.installer = new SkillInstaller(this.skillsDir);
31
+ this.parser = new SkillParser();
32
+ }
33
+
34
+ /**
35
+ * Install skills from GitHub repository
36
+ * @param {string} source - GitHub URL or owner/repo
37
+ * @param {Object} options - Installation options
38
+ * @returns {Promise<Array>} List of installed skills
39
+ */
40
+ async install(source, options = {}) {
41
+ console.log(`[INFO] Installing skills from ${source}...`);
42
+
43
+ try {
44
+ const skills = await this.installer.installFromGitHub(source, options);
45
+
46
+ console.log(`\n[OK] Successfully installed ${skills.length} skill(s)`);
47
+
48
+ // Auto-sync to AGENTS.md (if enabled)
49
+ if (options.autoSync !== false) {
50
+ await this.sync();
51
+ }
52
+
53
+ return skills;
54
+ } catch (err) {
55
+ console.error(`[X] Installation failed: ${err.message}`);
56
+ throw err;
57
+ }
58
+ }
59
+
60
+ /**
61
+ * Read skill content (output format compatible with OpenSkills)
62
+ * @param {string} name - Skill name
63
+ * @returns {Promise<Object>} Skill content
64
+ */
65
+ async read(name) {
66
+ try {
67
+ const skill = await this.reader.readSkill(name);
68
+
69
+ // Output format compatible with OpenSkills
70
+ console.log(`Reading: ${skill.name}`);
71
+ console.log(`Base directory: ${skill.baseDir}`);
72
+ console.log('');
73
+ console.log(skill.content);
74
+ console.log('');
75
+ console.log(`Skill read: ${skill.name}`);
76
+
77
+ return skill;
78
+ } catch (err) {
79
+ console.error(`[X] Error reading skill '${name}': ${err.message}`);
80
+ console.error('\nSearched paths:');
81
+ this.reader.searchPaths.forEach(p => console.error(` - ${p}`));
82
+ console.error('\nInstall skills: stigmergy skill install <source>');
83
+ throw err;
84
+ }
85
+ }
86
+
87
+ /**
88
+ * List all installed skills
89
+ * @returns {Promise<Array>} List of skills
90
+ */
91
+ async list() {
92
+ try {
93
+ const skills = await this.reader.listSkills();
94
+
95
+ if (skills.length === 0) {
96
+ console.log('No skills installed');
97
+ console.log('\nInstall skills: stigmergy skill install <source>');
98
+ console.log('Example: stigmergy skill install anthropics/skills');
99
+ return [];
100
+ }
101
+
102
+ console.log(`\nInstalled skills (${skills.length}):\n`);
103
+
104
+ // Group by location for display
105
+ const grouped = this.groupByLocation(skills);
106
+
107
+ for (const [location, locationSkills] of Object.entries(grouped)) {
108
+ console.log(`${this.getLocationIcon(location)} ${location}:`);
109
+ locationSkills.forEach(skill => {
110
+ console.log(` • ${skill.name.padEnd(30)} ${skill.description}`);
111
+ });
112
+ console.log('');
113
+ }
114
+
115
+ return skills;
116
+ } catch (err) {
117
+ console.error(`[X] Error listing skills: ${err.message}`);
118
+ throw err;
119
+ }
120
+ }
121
+
122
+ /**
123
+ * Sync skills to CLI configuration files
124
+ * @returns {Promise<void>}
125
+ */
126
+ async sync() {
127
+ console.log('[INFO] Syncing skills to CLI configuration files...');
128
+
129
+ try {
130
+ const skills = await this.reader.listSkills();
131
+
132
+ if (skills.length === 0) {
133
+ console.log('[INFO] No skills to sync');
134
+ return;
135
+ }
136
+
137
+ // Generate <available_skills> XML
138
+ const skillsXml = this.generateSkillsXml(skills);
139
+
140
+ // All CLI configuration files to update
141
+ const cliFiles = [
142
+ 'AGENTS.md', // Universal config
143
+ 'claude.md', // Claude CLI
144
+ 'qwen.md', // Qwen CLI
145
+ 'gemini.md', // Gemini CLI
146
+ 'iflow.md', // iFlow CLI
147
+ 'qodercli.md', // Qoder CLI
148
+ 'codebuddy.md', // CodeBuddy CLI
149
+ 'copilot.md', // Copilot CLI
150
+ 'codex.md' // Codex CLI
151
+ ];
152
+
153
+ let syncedCount = 0;
154
+ let createdCount = 0;
155
+ let skippedCount = 0;
156
+
157
+ // Iterate through all CLI configuration files
158
+ for (const fileName of cliFiles) {
159
+ const filePath = path.join(process.cwd(), fileName);
160
+
161
+ try {
162
+ const result = await this.syncToFile(filePath, fileName, skillsXml);
163
+ if (result === 'synced') {
164
+ syncedCount++;
165
+ } else if (result === 'created') {
166
+ createdCount++;
167
+ }
168
+ } catch (err) {
169
+ console.log(`[INFO] Skipped ${fileName}: ${err.message}`);
170
+ skippedCount++;
171
+ }
172
+ }
173
+
174
+ // Output sync result summary
175
+ console.log(`\n[OK] Sync completed:`);
176
+ console.log(` - Updated: ${syncedCount} file(s)`);
177
+ if (createdCount > 0) {
178
+ console.log(` - Created: ${createdCount} file(s)`);
179
+ }
180
+ if (skippedCount > 0) {
181
+ console.log(` - Skipped: ${skippedCount} file(s)`);
182
+ }
183
+ console.log(` - Skills: ${skills.length}`);
184
+ } catch (err) {
185
+ console.error(`[X] Sync failed: ${err.message}`);
186
+ throw err;
187
+ }
188
+ }
189
+
190
+ /**
191
+ * Sync skills to a single file
192
+ * @private
193
+ * @param {string} filePath - Full file path
194
+ * @param {string} fileName - File name (for logging)
195
+ * @param {string} skillsXml - Skills XML content
196
+ * @returns {Promise<string>} 'synced' | 'created'
197
+ */
198
+ async syncToFile(filePath, fileName, skillsXml) {
199
+ try {
200
+ let content = await fs.readFile(filePath, 'utf-8');
201
+
202
+ // Replace or insert skills section
203
+ if (content.includes('<!-- SKILLS_START -->')) {
204
+ content = content.replace(
205
+ /<!-- SKILLS_START -->.*?<!-- SKILLS_END -->/s,
206
+ `<!-- SKILLS_START -->\n${skillsXml}\n<!-- SKILLS_END -->`
207
+ );
208
+ } else {
209
+ // Add to end of file
210
+ content += `\n\n<!-- SKILLS_START -->\n${skillsXml}\n<!-- SKILLS_END -->\n`;
211
+ }
212
+
213
+ await fs.writeFile(filePath, content, 'utf-8');
214
+ console.log(` [OK] ${fileName}`);
215
+ return 'synced';
216
+ } catch (err) {
217
+ if (err.code === 'ENOENT') {
218
+ // File does not exist, create new file
219
+ const cliName = fileName.replace('.md', '');
220
+ const content = `# ${cliName.toUpperCase()} Configuration\n\n<!-- SKILLS_START -->\n${skillsXml}\n<!-- SKILLS_END -->\n`;
221
+ await fs.writeFile(filePath, content, 'utf-8');
222
+ console.log(` [OK] ${fileName} (created)`);
223
+ return 'created';
224
+ } else {
225
+ throw err;
226
+ }
227
+ }
228
+ }
229
+
230
+ /**
231
+ * Generate <available_skills> XML
232
+ * @private
233
+ */
234
+ generateSkillsXml(skills) {
235
+ let xml = '<skills_system priority="1">\n\n';
236
+ xml += '## Stigmergy Skills\n\n';
237
+ xml += '<usage>\n';
238
+ xml += 'Load skills using Stigmergy skill manager:\n\n';
239
+ xml += 'Direct call (current CLI):\n';
240
+ xml += ' Bash("stigmergy skill read <skill-name>")\n\n';
241
+ xml += 'Cross-CLI call (specify CLI):\n';
242
+ xml += ' Bash("stigmergy use <cli-name> skill <skill-name>")\n\n';
243
+ xml += 'Smart routing (auto-select best CLI):\n';
244
+ xml += ' Bash("stigmergy call skill <skill-name>")\n\n';
245
+ xml += 'The skill content will load with detailed instructions.\n';
246
+ xml += 'Base directory will be provided for resolving bundled resources.\n';
247
+ xml += '</usage>\n\n';
248
+ xml += '<available_skills>\n\n';
249
+
250
+ for (const skill of skills) {
251
+ xml += '<skill>\n';
252
+ xml += `<name>${skill.name}</name>\n`;
253
+ xml += `<description>${this.escapeXml(skill.description)}</description>\n`;
254
+ xml += `<location>${skill.location}</location>\n`;
255
+ xml += '</skill>\n\n';
256
+ }
257
+
258
+ xml += '</available_skills>\n\n';
259
+ xml += '</skills_system>';
260
+
261
+ return xml;
262
+ }
263
+
264
+ /**
265
+ * Remove skill
266
+ * @param {string} name - Skill name
267
+ * @returns {Promise<void>}
268
+ */
269
+ async remove(name) {
270
+ try {
271
+ await this.installer.uninstallSkill(name);
272
+ console.log(`[OK] Removed skill: ${name}`);
273
+
274
+ // Auto-sync
275
+ await this.sync();
276
+ } catch (err) {
277
+ console.error(`[X] Failed to remove skill: ${err.message}`);
278
+ throw err;
279
+ }
280
+ }
281
+
282
+ /**
283
+ * Validate skill
284
+ * @param {string} pathOrName - Skill path or name
285
+ * @returns {Promise<Object>} Validation result
286
+ */
287
+ async validate(pathOrName) {
288
+ try {
289
+ let content;
290
+
291
+ // Determine if path or name
292
+ if (pathOrName.includes('/') || pathOrName.includes('\\')) {
293
+ // Is a path
294
+ content = await fs.readFile(pathOrName, 'utf-8');
295
+ } else {
296
+ // Is a name
297
+ const skill = await this.reader.readSkill(pathOrName);
298
+ content = skill.content;
299
+ }
300
+
301
+ const validation = this.parser.validateSkill(content);
302
+
303
+ if (validation.valid) {
304
+ console.log('[OK] Skill validation passed');
305
+ } else {
306
+ console.log('[X] Skill validation failed:');
307
+ validation.errors.forEach(err => console.log(` - ${err}`));
308
+ }
309
+
310
+ return validation;
311
+ } catch (err) {
312
+ console.error(`[X] Validation error: ${err.message}`);
313
+ throw err;
314
+ }
315
+ }
316
+
317
+ /**
318
+ * Group by location
319
+ * @private
320
+ */
321
+ groupByLocation(skills) {
322
+ return skills.reduce((groups, skill) => {
323
+ const loc = skill.location || 'unknown';
324
+ if (!groups[loc]) groups[loc] = [];
325
+ groups[loc].push(skill);
326
+ return groups;
327
+ }, {});
328
+ }
329
+
330
+ /**
331
+ * Get location icon
332
+ * @private
333
+ */
334
+ getLocationIcon(location) {
335
+ const icons = {
336
+ 'stigmergy': '[GLOBAL]',
337
+ 'project': '[PROJECT]',
338
+ 'global': '[HOME]',
339
+ 'claude': '[CLAUDE]',
340
+ 'universal': '[UNIVERSAL]'
341
+ };
342
+ return icons[location] || '[INFO]';
343
+ }
344
+
345
+ /**
346
+ * XML escape
347
+ * @private
348
+ */
349
+ escapeXml(str) {
350
+ return str
351
+ .replace(/&/g, '&amp;')
352
+ .replace(/</g, '&lt;')
353
+ .replace(/>/g, '&gt;')
354
+ .replace(/"/g, '&quot;')
355
+ .replace(/'/g, '&apos;');
356
+ }
357
+ }