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.
- package/README.md +69 -20
- package/STIGMERGY.md +26 -7
- package/docs/MULTI_USER_WIKI_COLLABORATION_SYSTEM.md +523 -0
- package/docs/PROMPT_BASED_SKILLS_SYSTEM_DESIGN.md +458 -0
- package/docs/SKILL_IMPLEMENTATION_CONSTRAINTS_AND_ALIGNMENT.md +423 -0
- package/docs/TECHNICAL_FEASIBILITY_ANALYSIS.md +308 -0
- package/examples/multilingual-hook-demo.js +125 -0
- package/package.json +30 -19
- package/scripts/dependency-analyzer.js +101 -0
- package/scripts/generate-cli-docs.js +64 -0
- package/scripts/postuninstall.js +46 -0
- package/scripts/preuninstall.js +85 -0
- package/scripts/run-layered-tests.js +3 -3
- package/src/adapters/claude/install_claude_integration.js +37 -37
- package/src/adapters/codebuddy/install_codebuddy_integration.js +66 -63
- package/src/adapters/codex/install_codex_integration.js +54 -55
- package/src/adapters/copilot/install_copilot_integration.js +46 -46
- package/src/adapters/gemini/install_gemini_integration.js +68 -68
- package/src/adapters/iflow/install_iflow_integration.js +77 -77
- package/src/adapters/qoder/install_qoder_integration.js +76 -76
- package/src/adapters/qwen/install_qwen_integration.js +23 -23
- package/src/cli/router.js +713 -163
- package/src/commands/skill-bridge.js +39 -0
- package/src/commands/skill-handler.js +150 -0
- package/src/commands/skill.js +127 -0
- package/src/core/cache_cleaner.js +767 -767
- package/src/core/cli_help_analyzer.js +680 -680
- package/src/core/cli_parameter_handler.js +132 -132
- package/src/core/cli_path_detector.js +573 -0
- package/src/core/cli_tools.js +160 -89
- package/src/core/coordination/index.js +16 -16
- package/src/core/coordination/nodejs/AdapterManager.js +130 -102
- package/src/core/coordination/nodejs/CLCommunication.js +132 -132
- package/src/core/coordination/nodejs/CLIIntegrationManager.js +272 -272
- package/src/core/coordination/nodejs/HealthChecker.js +76 -76
- package/src/core/coordination/nodejs/HookDeploymentManager.js +463 -274
- package/src/core/coordination/nodejs/StatisticsCollector.js +71 -71
- package/src/core/coordination/nodejs/index.js +90 -90
- package/src/core/coordination/nodejs/utils/Logger.js +29 -29
- package/src/core/directory_permission_manager.js +568 -0
- package/src/core/enhanced_cli_installer.js +609 -0
- package/src/core/error_handler.js +406 -406
- package/src/core/installer.js +263 -119
- package/src/core/memory_manager.js +83 -83
- package/src/core/multilingual/language-pattern-manager.js +200 -0
- package/src/core/persistent_shell_configurator.js +468 -0
- package/src/core/rest_client.js +160 -160
- package/src/core/skills/StigmergySkillManager.js +357 -0
- package/src/core/skills/__tests__/SkillInstaller.test.js +275 -0
- package/src/core/skills/__tests__/SkillParser.test.js +202 -0
- package/src/core/skills/__tests__/SkillReader.test.js +189 -0
- package/src/core/skills/cli-command-test.js +201 -0
- package/src/core/skills/comprehensive-e2e-test.js +473 -0
- package/src/core/skills/e2e-test.js +267 -0
- package/src/core/skills/embedded-openskills/SkillInstaller.js +438 -0
- package/src/core/skills/embedded-openskills/SkillParser.js +123 -0
- package/src/core/skills/embedded-openskills/SkillReader.js +143 -0
- package/src/core/skills/integration-test.js +248 -0
- package/src/core/skills/package.json +6 -0
- package/src/core/skills/regression-test.js +285 -0
- package/src/core/skills/run-all-tests.js +129 -0
- package/src/core/skills/sync-test.js +210 -0
- package/src/core/skills/test-runner.js +242 -0
- package/src/core/smart_router.js +261 -249
- package/src/core/upgrade_manager.js +48 -20
- package/src/index.js +30 -30
- package/src/test/cli-availability-checker.js +194 -194
- package/src/test/test-environment.js +289 -289
- package/src/utils/helpers.js +18 -35
- package/src/utils.js +921 -921
- package/src/weatherProcessor.js +228 -228
- package/test/multilingual/hook-deployment.test.js +91 -0
- package/test/multilingual/language-pattern-manager.test.js +140 -0
- package/test/multilingual/system-test.js +85 -0
- package/src/auth.js +0 -173
- package/src/auth_command.js +0 -208
- package/src/calculator.js +0 -313
- package/src/core/enhanced_installer.js +0 -479
- package/src/core/enhanced_uninstaller.js +0 -638
- package/src/data_encryption.js +0 -143
- package/src/data_structures.js +0 -440
- package/src/deploy.js +0 -55
package/src/core/rest_client.js
CHANGED
|
@@ -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, '&')
|
|
352
|
+
.replace(/</g, '<')
|
|
353
|
+
.replace(/>/g, '>')
|
|
354
|
+
.replace(/"/g, '"')
|
|
355
|
+
.replace(/'/g, ''');
|
|
356
|
+
}
|
|
357
|
+
}
|