stigmergy 1.3.2-beta.0 ā 1.3.2-beta.2
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 +3 -1
- package/scripts/run-comprehensive-tests.js +230 -0
- package/scripts/run-quick-tests.js +90 -0
- package/src/cli/commands/project.js +1 -1
- package/src/cli/router-beta.js +1 -1
- package/src/core/coordination/nodejs/HookDeploymentManager.js +25 -0
- package/src/core/coordination/nodejs/generators/ResumeSessionGenerator.js +6 -4
- package/src/core/enhanced_cli_installer.js +155 -1
- package/src/core/installer.js +35 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "stigmergy",
|
|
3
|
-
"version": "1.3.2-beta.
|
|
3
|
+
"version": "1.3.2-beta.2",
|
|
4
4
|
"description": "Stigmergy CLI - Multi-Agents Cross-AI CLI Tools Collaboration System",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -48,6 +48,8 @@
|
|
|
48
48
|
"path-fixer": "node path-fixer.js",
|
|
49
49
|
"emergency-clean": "node emergency-cleanup.js",
|
|
50
50
|
"test:conflict-prevention": "node test/conflict-prevention-test.js",
|
|
51
|
+
"test:comprehensive": "node scripts/run-comprehensive-tests.js",
|
|
52
|
+
"test:quick": "node scripts/run-quick-tests.js",
|
|
51
53
|
"post-deployment-config": "node scripts/post-deployment-config.js",
|
|
52
54
|
"preinstall": "node scripts/preinstall-check.js",
|
|
53
55
|
"postinstall": "node src/index.js auto-install",
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Comprehensive Test Runner
|
|
5
|
+
* Runs all tests and generates detailed coverage report
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const { execSync } = require('child_process');
|
|
9
|
+
const fs = require('fs');
|
|
10
|
+
const path = require('path');
|
|
11
|
+
|
|
12
|
+
console.log('šÆ COMPREHENSIVE TEST SUITE');
|
|
13
|
+
console.log('='.repeat(70));
|
|
14
|
+
|
|
15
|
+
// ANSI color codes
|
|
16
|
+
const colors = {
|
|
17
|
+
reset: '\x1b[0m',
|
|
18
|
+
green: '\x1b[32m',
|
|
19
|
+
red: '\x1b[31m',
|
|
20
|
+
yellow: '\x1b[33m',
|
|
21
|
+
blue: '\x1b[34m',
|
|
22
|
+
cyan: '\x1b[36m'
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
function log(message, color = 'reset') {
|
|
26
|
+
console.log(`${colors[color]}${message}${colors.reset}`);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function runCommand(command, description) {
|
|
30
|
+
log(`\nš¦ ${description}...`, 'cyan');
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
const output = execSync(command, {
|
|
34
|
+
stdio: 'inherit',
|
|
35
|
+
cwd: process.cwd()
|
|
36
|
+
});
|
|
37
|
+
log(`ā
${description} completed`, 'green');
|
|
38
|
+
return true;
|
|
39
|
+
} catch (error) {
|
|
40
|
+
log(`ā ${description} failed`, 'red');
|
|
41
|
+
log(`Error: ${error.message}`, 'red');
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function ensureDirectories() {
|
|
47
|
+
const dirs = [
|
|
48
|
+
'tests/comprehensive/cli',
|
|
49
|
+
'tests/comprehensive/cli/commands',
|
|
50
|
+
'tests/comprehensive/core',
|
|
51
|
+
'tests/comprehensive/coordination',
|
|
52
|
+
'tests/comprehensive/adapters',
|
|
53
|
+
'tests/comprehensive/utils',
|
|
54
|
+
'coverage'
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
dirs.forEach(dir => {
|
|
58
|
+
const fullPath = path.join(process.cwd(), dir);
|
|
59
|
+
if (!fs.existsSync(fullPath)) {
|
|
60
|
+
fs.mkdirSync(fullPath, { recursive: true });
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function countTestFiles() {
|
|
66
|
+
const testDirs = [
|
|
67
|
+
'tests/unit',
|
|
68
|
+
'tests/integration',
|
|
69
|
+
'tests/e2e',
|
|
70
|
+
'tests/comprehensive',
|
|
71
|
+
'tests/regression'
|
|
72
|
+
];
|
|
73
|
+
|
|
74
|
+
let totalTests = 0;
|
|
75
|
+
testDirs.forEach(dir => {
|
|
76
|
+
const testPath = path.join(process.cwd(), dir);
|
|
77
|
+
if (fs.existsSync(testPath)) {
|
|
78
|
+
const files = fs.readdirSync(testPath)
|
|
79
|
+
.filter(f => f.endsWith('.test.js'));
|
|
80
|
+
totalTests += files.length;
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
return totalTests;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function countSourceFiles() {
|
|
88
|
+
const srcPath = path.join(process.cwd(), 'src');
|
|
89
|
+
|
|
90
|
+
function countDir(dir) {
|
|
91
|
+
let count = 0;
|
|
92
|
+
const items = fs.readdirSync(dir, { withFileTypes: true });
|
|
93
|
+
|
|
94
|
+
items.forEach(item => {
|
|
95
|
+
if (item.isDirectory()) {
|
|
96
|
+
count += countDir(path.join(dir, item.name));
|
|
97
|
+
} else if (item.name.endsWith('.js')) {
|
|
98
|
+
count++;
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
return count;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return countDir(srcPath);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function printSummary() {
|
|
109
|
+
log('\n' + '='.repeat(70), 'cyan');
|
|
110
|
+
log('š TEST SUITE SUMMARY', 'cyan');
|
|
111
|
+
log('='.repeat(70), 'cyan');
|
|
112
|
+
|
|
113
|
+
const sourceFiles = countSourceFiles();
|
|
114
|
+
const testFiles = countTestFiles();
|
|
115
|
+
const coverage = ((testFiles / sourceFiles) * 100).toFixed(1);
|
|
116
|
+
|
|
117
|
+
log(`\nš Source Files: ${sourceFiles}`);
|
|
118
|
+
log(`š§Ŗ Test Files: ${testFiles}`);
|
|
119
|
+
log(`š Test Coverage: ${coverage}%`);
|
|
120
|
+
|
|
121
|
+
log('\n' + '='.repeat(70), 'cyan');
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function readCoverageReport() {
|
|
125
|
+
const coveragePath = path.join(process.cwd(), 'coverage', 'coverage-summary.json');
|
|
126
|
+
|
|
127
|
+
if (!fs.existsSync(coveragePath)) {
|
|
128
|
+
log('\nā ļø Coverage report not found', 'yellow');
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const coverage = JSON.parse(fs.readFileSync(coveragePath, 'utf8'));
|
|
133
|
+
|
|
134
|
+
log('\n' + '='.repeat(70), 'cyan');
|
|
135
|
+
log('š DETAILED COVERAGE REPORT', 'cyan');
|
|
136
|
+
log('='.repeat(70), 'cyan');
|
|
137
|
+
|
|
138
|
+
const total = coverage.total;
|
|
139
|
+
log(`\nš Overall Coverage:`, 'cyan');
|
|
140
|
+
log(` Statements: ${total.statements.pct}% (${total.statements.covered}/${total.statements.total})`);
|
|
141
|
+
log(` Branches: ${total.branches.pct}% (${total.branches.covered}/${total.branches.total})`);
|
|
142
|
+
log(` Functions: ${total.functions.pct}% (${total.functions.covered}/${total.functions.total})`);
|
|
143
|
+
log(` Lines: ${total.lines.pct}% (${total.lines.covered}/${total.lines.total})`);
|
|
144
|
+
|
|
145
|
+
// Find files with low coverage
|
|
146
|
+
log('\nš Files with Low Coverage (< 80%):', 'yellow');
|
|
147
|
+
let lowCoverageFound = false;
|
|
148
|
+
|
|
149
|
+
Object.entries(coverage).forEach(([file, data]) => {
|
|
150
|
+
if (file !== 'total' && data.statements.pct < 80) {
|
|
151
|
+
lowCoverageFound = true;
|
|
152
|
+
const relativePath = file.replace(process.cwd(), '');
|
|
153
|
+
log(` ${relativePath}: ${data.statements.pct}%`, 'red');
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
if (!lowCoverageFound) {
|
|
158
|
+
log(' ā
All files have good coverage!', 'green');
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
log('\n' + '='.repeat(70), 'cyan');
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
async function main() {
|
|
165
|
+
const startTime = Date.now();
|
|
166
|
+
|
|
167
|
+
try {
|
|
168
|
+
// Ensure test directories exist
|
|
169
|
+
ensureDirectories();
|
|
170
|
+
|
|
171
|
+
// Print initial summary
|
|
172
|
+
log('\nš Test Environment:', 'blue');
|
|
173
|
+
log(` Node.js: ${process.version}`);
|
|
174
|
+
log(` Platform: ${process.platform}`);
|
|
175
|
+
log(` CWD: ${process.cwd()}`);
|
|
176
|
+
|
|
177
|
+
// Run pre-test cleanup
|
|
178
|
+
log('\nš§¹ Cleaning test environment...', 'blue');
|
|
179
|
+
try {
|
|
180
|
+
execSync('npm run clean 2>/dev/null || true', { stdio: 'ignore' });
|
|
181
|
+
} catch (e) {
|
|
182
|
+
// Ignore cleanup errors
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Run tests by category
|
|
186
|
+
const results = {
|
|
187
|
+
unit: runCommand('npm run test:unit -- --silent', 'Unit Tests'),
|
|
188
|
+
integration: runCommand('npm run test:integration -- --silent', 'Integration Tests'),
|
|
189
|
+
comprehensive: runCommand('jest tests/comprehensive --coverage --silent', 'Comprehensive Tests')
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
// Generate coverage report
|
|
193
|
+
if (results.comprehensive) {
|
|
194
|
+
readCoverageReport();
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Calculate duration
|
|
198
|
+
const duration = ((Date.now() - startTime) / 1000).toFixed(2);
|
|
199
|
+
|
|
200
|
+
// Print final summary
|
|
201
|
+
printSummary();
|
|
202
|
+
|
|
203
|
+
log(`\nā±ļø Total Duration: ${duration}s`, 'blue');
|
|
204
|
+
|
|
205
|
+
// Determine success
|
|
206
|
+
const allPassed = Object.values(results).every(r => r === true);
|
|
207
|
+
|
|
208
|
+
if (allPassed) {
|
|
209
|
+
log('\nā
ALL TESTS PASSED!', 'green');
|
|
210
|
+
log('\nš Great job! Your test suite is comprehensive.', 'green');
|
|
211
|
+
process.exit(0);
|
|
212
|
+
} else {
|
|
213
|
+
log('\nā SOME TESTS FAILED', 'red');
|
|
214
|
+
log('\nš” Run with --verbose to see detailed error messages', 'yellow');
|
|
215
|
+
process.exit(1);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
} catch (error) {
|
|
219
|
+
log(`\nš„ Fatal Error: ${error.message}`, 'red');
|
|
220
|
+
console.error(error);
|
|
221
|
+
process.exit(1);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Run if executed directly
|
|
226
|
+
if (require.main === module) {
|
|
227
|
+
main();
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
module.exports = { runCommand, readCoverageReport, printSummary };
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Quick Test Runner
|
|
5
|
+
* Runs only the comprehensive tests without legacy tests
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const { execSync } = require('child_process');
|
|
9
|
+
const fs = require('fs');
|
|
10
|
+
const path = require('path');
|
|
11
|
+
|
|
12
|
+
console.log('šÆ QUICK COMPREHENSIVE TEST SUITE');
|
|
13
|
+
console.log('='.repeat(70));
|
|
14
|
+
|
|
15
|
+
// ANSI colors
|
|
16
|
+
const colors = {
|
|
17
|
+
reset: '\x1b[0m',
|
|
18
|
+
green: '\x1b[32m',
|
|
19
|
+
red: '\x1b[31m',
|
|
20
|
+
yellow: '\x1b[33m',
|
|
21
|
+
blue: '\x1b[34m',
|
|
22
|
+
cyan: '\x1b[36m'
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
function log(message, color = 'reset') {
|
|
26
|
+
console.log(`${colors[color]}${message}${colors.reset}`);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function runCommand(command, description) {
|
|
30
|
+
log(`\nš¦ ${description}...`, 'cyan');
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
const output = execSync(command, {
|
|
34
|
+
stdio: 'inherit',
|
|
35
|
+
cwd: process.cwd()
|
|
36
|
+
});
|
|
37
|
+
log(`ā
${description} completed`, 'green');
|
|
38
|
+
return true;
|
|
39
|
+
} catch (error) {
|
|
40
|
+
log(`ā ${description} failed`, 'red');
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function main() {
|
|
46
|
+
const startTime = Date.now();
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
log('\nš Running comprehensive tests only...', 'blue');
|
|
50
|
+
|
|
51
|
+
// Ensure directories exist
|
|
52
|
+
const dirs = ['tests/comprehensive/cli/commands', 'tests/comprehensive/core', 'tests/comprehensive/coordination'];
|
|
53
|
+
dirs.forEach(dir => {
|
|
54
|
+
const fullPath = path.join(process.cwd(), dir);
|
|
55
|
+
if (!fs.existsSync(fullPath)) {
|
|
56
|
+
fs.mkdirSync(fullPath, { recursive: true });
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// Run comprehensive tests only
|
|
61
|
+
const result = runCommand(
|
|
62
|
+
'jest tests/comprehensive --coverage',
|
|
63
|
+
'Comprehensive Tests'
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
// Calculate duration
|
|
67
|
+
const duration = ((Date.now() - startTime) / 1000).toFixed(2);
|
|
68
|
+
|
|
69
|
+
log(`\nā±ļø Duration: ${duration}s`, 'blue');
|
|
70
|
+
|
|
71
|
+
if (result) {
|
|
72
|
+
log('\nā
ALL TESTS PASSED!', 'green');
|
|
73
|
+
process.exit(0);
|
|
74
|
+
} else {
|
|
75
|
+
log('\nā SOME TESTS FAILED', 'red');
|
|
76
|
+
log('\nš” Check the output above for details', 'yellow');
|
|
77
|
+
process.exit(1);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
} catch (error) {
|
|
81
|
+
log(`\nš„ Fatal Error: ${error.message}`, 'red');
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (require.main === module) {
|
|
87
|
+
main();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
module.exports = { runCommand };
|
package/src/cli/router-beta.js
CHANGED
|
@@ -76,6 +76,11 @@ class HookDeploymentManager {
|
|
|
76
76
|
async deployNodeJsHooks(cliName, hookDir, options) {
|
|
77
77
|
console.log(`[HOOK_DEPLOYMENT] Deploying Node.js hooks for ${cliName}...`);
|
|
78
78
|
|
|
79
|
+
// Ensure hook directory exists
|
|
80
|
+
if (!fs.existsSync(hookDir)) {
|
|
81
|
+
fs.mkdirSync(hookDir, { recursive: true });
|
|
82
|
+
}
|
|
83
|
+
|
|
79
84
|
// Deploy ResumeSession extension
|
|
80
85
|
await this.deployResumeSessionExtension(cliName, hookDir);
|
|
81
86
|
|
|
@@ -92,6 +97,11 @@ class HookDeploymentManager {
|
|
|
92
97
|
async deployResumeSessionExtension(cliName, hookDir) {
|
|
93
98
|
console.log(`[HOOK_DEPLOYMENT] Deploying ResumeSession extension for ${cliName}...`);
|
|
94
99
|
|
|
100
|
+
// Ensure hook directory exists
|
|
101
|
+
if (!fs.existsSync(hookDir)) {
|
|
102
|
+
fs.mkdirSync(hookDir, { recursive: true });
|
|
103
|
+
}
|
|
104
|
+
|
|
95
105
|
try {
|
|
96
106
|
const extensionContent = this.resumeSessionGenerator.generateForCLI(cliName);
|
|
97
107
|
const fileName = this.resumeSessionGenerator.getFileName(cliName);
|
|
@@ -120,6 +130,11 @@ class HookDeploymentManager {
|
|
|
120
130
|
async deploySkillsIntegration(cliName, hookDir) {
|
|
121
131
|
console.log(`[HOOK_DEPLOYMENT] Deploying skills integration for ${cliName}...`);
|
|
122
132
|
|
|
133
|
+
// Ensure hook directory exists
|
|
134
|
+
if (!fs.existsSync(hookDir)) {
|
|
135
|
+
fs.mkdirSync(hookDir, { recursive: true });
|
|
136
|
+
}
|
|
137
|
+
|
|
123
138
|
try {
|
|
124
139
|
const skillsResult = this.skillsIntegrationGenerator.generateForCLI(cliName);
|
|
125
140
|
const skillsPath = path.join(hookDir, skillsResult.fileName);
|
|
@@ -158,6 +173,11 @@ class HookDeploymentManager {
|
|
|
158
173
|
async deployCLIAdapter(cliName, hookDir) {
|
|
159
174
|
console.log(`[HOOK_DEPLOYMENT] Deploying CLI adapter for ${cliName}...`);
|
|
160
175
|
|
|
176
|
+
// Ensure hook directory exists
|
|
177
|
+
if (!fs.existsSync(hookDir)) {
|
|
178
|
+
fs.mkdirSync(hookDir, { recursive: true });
|
|
179
|
+
}
|
|
180
|
+
|
|
161
181
|
try {
|
|
162
182
|
const adapterContent = this.cliAdapterGenerator.generateForCLI(cliName);
|
|
163
183
|
const fileName = this.cliAdapterGenerator.getFileName(cliName);
|
|
@@ -184,6 +204,11 @@ class HookDeploymentManager {
|
|
|
184
204
|
async createBasicConfiguration(cliName, hookDir) {
|
|
185
205
|
console.log(`[HOOK_DEPLOYMENT] Creating basic configuration for ${cliName}...`);
|
|
186
206
|
|
|
207
|
+
// Ensure hook directory exists
|
|
208
|
+
if (!fs.existsSync(hookDir)) {
|
|
209
|
+
fs.mkdirSync(hookDir, { recursive: true });
|
|
210
|
+
}
|
|
211
|
+
|
|
187
212
|
try {
|
|
188
213
|
// Create main configuration file
|
|
189
214
|
const config = {
|
|
@@ -332,12 +332,14 @@ class SessionFilter {
|
|
|
332
332
|
switch (timeRange) {
|
|
333
333
|
case 'today':
|
|
334
334
|
return sessionDate.toDateString() === now.toDateString();
|
|
335
|
-
case 'week':
|
|
335
|
+
case 'week': {
|
|
336
336
|
const weekAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);
|
|
337
337
|
return sessionDate >= weekAgo;
|
|
338
|
-
|
|
338
|
+
}
|
|
339
|
+
case 'month': {
|
|
339
340
|
const monthAgo = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000);
|
|
340
341
|
return sessionDate >= monthAgo;
|
|
342
|
+
}
|
|
341
343
|
default:
|
|
342
344
|
return true;
|
|
343
345
|
}
|
|
@@ -565,11 +567,11 @@ function buildQuery(input) {
|
|
|
565
567
|
|
|
566
568
|
// CLI-specific registration logic
|
|
567
569
|
|
|
568
|
-
${this.generateCLIRegistrationCode(cliName, commandName)}
|
|
570
|
+
${this.generateCLIRegistrationCode(cliName, commandName, projectPath)}
|
|
569
571
|
`;
|
|
570
572
|
}
|
|
571
573
|
|
|
572
|
-
generateCLIRegistrationCode(cliName, commandName) {
|
|
574
|
+
generateCLIRegistrationCode(cliName, commandName, projectPath) {
|
|
573
575
|
switch (cliName.toLowerCase()) {
|
|
574
576
|
case 'claude':
|
|
575
577
|
case 'codebuddy':
|
|
@@ -24,13 +24,18 @@ class EnhancedCLIInstaller {
|
|
|
24
24
|
...options
|
|
25
25
|
};
|
|
26
26
|
|
|
27
|
+
// Expose commonly used options as properties for easier access
|
|
28
|
+
this.verbose = this.options.verbose;
|
|
29
|
+
this.force = options.force || false;
|
|
30
|
+
this.homeDir = options.homeDir || require('os').homedir();
|
|
27
31
|
this.results = {
|
|
28
32
|
permissionSetup: null,
|
|
29
33
|
installations: {},
|
|
30
34
|
failedInstallations: [],
|
|
31
35
|
npmConfigured: false,
|
|
32
36
|
workingDirectory: null,
|
|
33
|
-
permissionMode: 'standard' // 'standard', 'elevated', 'failed'
|
|
37
|
+
permissionMode: 'standard', // 'standard', 'elevated', 'failed'
|
|
38
|
+
errors: []
|
|
34
39
|
};
|
|
35
40
|
|
|
36
41
|
this.cliTools = require('./cli_tools').CLI_TOOLS;
|
|
@@ -794,6 +799,155 @@ class EnhancedCLIInstaller {
|
|
|
794
799
|
getResults() {
|
|
795
800
|
return this.results;
|
|
796
801
|
}
|
|
802
|
+
|
|
803
|
+
/**
|
|
804
|
+
* Detect permission availability for the current platform
|
|
805
|
+
*/
|
|
806
|
+
detectPermissionAvailability() {
|
|
807
|
+
const platform = process.platform;
|
|
808
|
+
|
|
809
|
+
if (platform === 'win32') {
|
|
810
|
+
// Windows: Check if running as administrator
|
|
811
|
+
try {
|
|
812
|
+
const { execSync } = require('child_process');
|
|
813
|
+
const result = execSync('net session', { encoding: 'utf8' });
|
|
814
|
+
return result.includes('Administrator');
|
|
815
|
+
} catch {
|
|
816
|
+
return false;
|
|
817
|
+
}
|
|
818
|
+
} else {
|
|
819
|
+
// Unix-like systems: Check available privilege escalation tools
|
|
820
|
+
const privilegeEscalationTools = ['sudo', 'doas', 'run0', 'pkexec'];
|
|
821
|
+
for (const tool of privilegeEscalationTools) {
|
|
822
|
+
try {
|
|
823
|
+
const result = spawnSync(tool, ['--version'], {
|
|
824
|
+
stdio: 'pipe',
|
|
825
|
+
timeout: 5000
|
|
826
|
+
});
|
|
827
|
+
if (result.status !== null || result.error?.code !== 'ENOENT') {
|
|
828
|
+
return true;
|
|
829
|
+
}
|
|
830
|
+
} catch {
|
|
831
|
+
continue;
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
return false;
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
/**
|
|
839
|
+
* Detect available privilege escalation tools
|
|
840
|
+
*/
|
|
841
|
+
detectPrivilegeTools() {
|
|
842
|
+
const platform = process.platform;
|
|
843
|
+
const availableTools = [];
|
|
844
|
+
|
|
845
|
+
if (platform === 'win32') {
|
|
846
|
+
// Windows: Check for admin privileges
|
|
847
|
+
try {
|
|
848
|
+
const { execSync } = require('child_process');
|
|
849
|
+
const result = execSync('net session', { encoding: 'utf8' });
|
|
850
|
+
if (result.includes('Administrator')) {
|
|
851
|
+
availableTools.push('admin');
|
|
852
|
+
}
|
|
853
|
+
} catch {
|
|
854
|
+
// Not running as admin
|
|
855
|
+
}
|
|
856
|
+
} else {
|
|
857
|
+
// Unix-like systems: Check privilege escalation tools
|
|
858
|
+
const tools = [
|
|
859
|
+
{ name: 'sudo', checkCmd: ['-n', 'true'] },
|
|
860
|
+
{ name: 'doas', checkCmd: ['-n', 'true'] },
|
|
861
|
+
{ name: 'run0', checkCmd: ['-n', 'true'] },
|
|
862
|
+
{ name: 'pkexec', checkCmd: ['--help'] }
|
|
863
|
+
];
|
|
864
|
+
|
|
865
|
+
for (const tool of tools) {
|
|
866
|
+
try {
|
|
867
|
+
const result = spawnSync(tool.name, tool.checkCmd, {
|
|
868
|
+
stdio: 'pipe',
|
|
869
|
+
timeout: 5000
|
|
870
|
+
});
|
|
871
|
+
if (result.status !== null || result.error?.code !== 'ENOENT') {
|
|
872
|
+
availableTools.push(tool.name);
|
|
873
|
+
}
|
|
874
|
+
} catch {
|
|
875
|
+
continue;
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
return availableTools;
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
/**
|
|
884
|
+
* Upgrade a single tool
|
|
885
|
+
*/
|
|
886
|
+
async upgradeTool(toolName, toolInfo) {
|
|
887
|
+
this.log('info', `Upgrading ${toolName}...`);
|
|
888
|
+
|
|
889
|
+
if (!toolInfo || !toolInfo.install) {
|
|
890
|
+
this.log('warn', `Tool ${toolName} has no install command, skipping upgrade...`);
|
|
891
|
+
this.results.errors.push({
|
|
892
|
+
tool: toolName,
|
|
893
|
+
error: 'No install command specified',
|
|
894
|
+
timestamp: new Date().toISOString()
|
|
895
|
+
});
|
|
896
|
+
return false;
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
const upgradeCommand = `npm upgrade -g ${toolName}`;
|
|
900
|
+
|
|
901
|
+
this.results.installations[toolName] = {
|
|
902
|
+
startTime: Date.now(),
|
|
903
|
+
...this.results.installations[toolName]
|
|
904
|
+
};
|
|
905
|
+
|
|
906
|
+
try {
|
|
907
|
+
const success = await this.installTool(toolName, {
|
|
908
|
+
...toolInfo,
|
|
909
|
+
install: upgradeCommand,
|
|
910
|
+
name: `${toolInfo.name} (Upgrade)`
|
|
911
|
+
});
|
|
912
|
+
|
|
913
|
+
return success;
|
|
914
|
+
} catch (error) {
|
|
915
|
+
this.log('error', `Failed to upgrade ${toolName}: ${error.message}`);
|
|
916
|
+
this.results.errors.push({
|
|
917
|
+
tool: toolName,
|
|
918
|
+
error: error.message,
|
|
919
|
+
timestamp: new Date().toISOString()
|
|
920
|
+
});
|
|
921
|
+
return false;
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
/**
|
|
926
|
+
* Get installation summary
|
|
927
|
+
*/
|
|
928
|
+
getInstallationSummary() {
|
|
929
|
+
const installations = this.results.installations || {};
|
|
930
|
+
const errors = this.results.errors || [];
|
|
931
|
+
|
|
932
|
+
const total = Object.keys(installations).length;
|
|
933
|
+
const successful = Object.values(installations).filter(i => i.success !== false).length;
|
|
934
|
+
const failed = total - successful;
|
|
935
|
+
|
|
936
|
+
return {
|
|
937
|
+
total,
|
|
938
|
+
successful,
|
|
939
|
+
failed,
|
|
940
|
+
permissionMode: this.permissionMode,
|
|
941
|
+
npmConfigured: this.results.npmConfigured,
|
|
942
|
+
workingDirectory: this.results.workingDirectory,
|
|
943
|
+
errors: errors.map(e => ({
|
|
944
|
+
tool: e.tool,
|
|
945
|
+
error: e.error,
|
|
946
|
+
timestamp: e.timestamp
|
|
947
|
+
})),
|
|
948
|
+
details: installations
|
|
949
|
+
};
|
|
950
|
+
}
|
|
797
951
|
}
|
|
798
952
|
|
|
799
953
|
module.exports = EnhancedCLIInstaller;
|
package/src/core/installer.js
CHANGED
|
@@ -211,6 +211,12 @@ class StigmergyInstaller extends EnhancedCLIInstaller {
|
|
|
211
211
|
const missing = {};
|
|
212
212
|
|
|
213
213
|
for (const [toolName, toolInfo] of Object.entries(this.router.tools)) {
|
|
214
|
+
// Skip internal functions without install command
|
|
215
|
+
if (!toolInfo.install) {
|
|
216
|
+
console.log(`[DEBUG] Tool ${toolName} has no version/install info, skipping check`);
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
|
|
214
220
|
try {
|
|
215
221
|
console.log(`[SCAN] Checking ${toolInfo.name}...`);
|
|
216
222
|
const isAvailable = await this.checkCLI(toolName);
|
|
@@ -335,6 +341,35 @@ class StigmergyInstaller extends EnhancedCLIInstaller {
|
|
|
335
341
|
return result;
|
|
336
342
|
}
|
|
337
343
|
|
|
344
|
+
/**
|
|
345
|
+
* Generate hook for a specific CLI tool
|
|
346
|
+
* @param {string} toolName - Name of the CLI tool
|
|
347
|
+
* @param {Object} toolInfo - Tool information object
|
|
348
|
+
*/
|
|
349
|
+
async generateToolHook(toolName, toolInfo) {
|
|
350
|
+
console.log(`[HOOK] Generating hook for ${toolName}...`);
|
|
351
|
+
|
|
352
|
+
const HookDeploymentManager = require('./coordination/nodejs/HookDeploymentManager');
|
|
353
|
+
const hookManager = new HookDeploymentManager();
|
|
354
|
+
|
|
355
|
+
await hookManager.initialize();
|
|
356
|
+
|
|
357
|
+
try {
|
|
358
|
+
const deploySuccess = await hookManager.deployHooksForCLI(toolName);
|
|
359
|
+
|
|
360
|
+
if (deploySuccess) {
|
|
361
|
+
console.log(`[OK] Hook generated successfully for ${toolName}`);
|
|
362
|
+
return true;
|
|
363
|
+
} else {
|
|
364
|
+
console.log(`[WARN] Hook generation failed for ${toolName}`);
|
|
365
|
+
return false;
|
|
366
|
+
}
|
|
367
|
+
} catch (error) {
|
|
368
|
+
console.error(`[ERROR] Hook generation error for ${toolName}:`, error.message);
|
|
369
|
+
throw error;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
338
373
|
/**
|
|
339
374
|
* Deploy hooks for available CLI tools
|
|
340
375
|
*/
|