stigmergy 1.2.12 → 1.3.1-beta
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 +39 -3
- package/STIGMERGY.md +3 -0
- package/config/builtin-skills.json +43 -0
- package/config/enhanced-cli-config.json +438 -0
- package/docs/CLI_TOOLS_AGENT_SKILL_ANALYSIS.md +463 -0
- package/docs/DESIGN_CLI_HELP_ANALYZER_REFACTOR.md +726 -0
- package/docs/ENHANCED_CLI_AGENT_SKILL_CONFIG.md +285 -0
- package/docs/IMPLEMENTATION_CHECKLIST_CLI_HELP_ANALYZER_REFACTOR.md +1268 -0
- package/docs/INSTALLER_ARCHITECTURE.md +257 -0
- package/docs/LESSONS_LEARNED.md +252 -0
- package/docs/SPECS_CLI_HELP_ANALYZER_REFACTOR.md +287 -0
- package/docs/SUDO_PROBLEM_AND_SOLUTION.md +529 -0
- package/docs/correct-skillsio-implementation.md +368 -0
- package/docs/development_guidelines.md +276 -0
- package/docs/independent-resume-implementation.md +198 -0
- package/docs/resumesession-final-implementation.md +195 -0
- package/docs/resumesession-usage.md +87 -0
- package/package.json +19 -9
- package/scripts/analyze-router.js +168 -0
- package/scripts/run-comprehensive-tests.js +230 -0
- package/scripts/run-quick-tests.js +90 -0
- package/scripts/test-runner.js +344 -0
- package/skills/resumesession/INDEPENDENT_SKILL.md +171 -0
- package/skills/resumesession/SKILL.md +127 -0
- package/skills/resumesession/__init__.py +33 -0
- package/skills/resumesession/implementations/simple-resume.js +13 -0
- package/src/adapters/claude/install_claude_integration.js +9 -1
- package/src/adapters/codebuddy/install_codebuddy_integration.js +3 -1
- package/src/adapters/codex/install_codex_integration.js +15 -5
- package/src/adapters/gemini/install_gemini_integration.js +3 -1
- package/src/adapters/qwen/install_qwen_integration.js +3 -1
- package/src/cli/commands/autoinstall.js +65 -0
- package/src/cli/commands/errors.js +190 -0
- package/src/cli/commands/independent-resume.js +395 -0
- package/src/cli/commands/install.js +179 -0
- package/src/cli/commands/permissions.js +108 -0
- package/src/cli/commands/project.js +485 -0
- package/src/cli/commands/scan.js +97 -0
- package/src/cli/commands/simple-resume.js +377 -0
- package/src/cli/commands/skills.js +158 -0
- package/src/cli/commands/status.js +113 -0
- package/src/cli/commands/stigmergy-resume.js +775 -0
- package/src/cli/commands/system.js +301 -0
- package/src/cli/commands/universal-resume.js +394 -0
- package/src/cli/router-beta.js +471 -0
- package/src/cli/utils/environment.js +75 -0
- package/src/cli/utils/formatters.js +47 -0
- package/src/cli/utils/skills_cache.js +92 -0
- package/src/core/cache_cleaner.js +1 -0
- package/src/core/cli_adapters.js +345 -0
- package/src/core/cli_help_analyzer.js +582 -26
- package/src/core/cli_path_detector.js +702 -709
- package/src/core/cli_tools.js +515 -160
- package/src/core/coordination/nodejs/CLIIntegrationManager.js +18 -0
- package/src/core/coordination/nodejs/HookDeploymentManager.js +242 -412
- package/src/core/coordination/nodejs/HookDeploymentManager.refactored.js +323 -0
- package/src/core/coordination/nodejs/generators/CLIAdapterGenerator.js +363 -0
- package/src/core/coordination/nodejs/generators/ResumeSessionGenerator.js +932 -0
- package/src/core/coordination/nodejs/generators/SkillsIntegrationGenerator.js +1395 -0
- package/src/core/coordination/nodejs/generators/index.js +12 -0
- package/src/core/enhanced_cli_installer.js +1208 -608
- package/src/core/enhanced_cli_parameter_handler.js +402 -0
- package/src/core/execution_mode_detector.js +222 -0
- package/src/core/installer.js +151 -106
- package/src/core/local_skill_scanner.js +732 -0
- package/src/core/multilingual/language-pattern-manager.js +1 -1
- package/src/core/skills/BuiltinSkillsDeployer.js +188 -0
- package/src/core/skills/StigmergySkillManager.js +123 -16
- package/src/core/skills/embedded-openskills/SkillParser.js +7 -3
- package/src/core/smart_router.js +291 -2
- package/src/index.js +10 -4
- package/src/utils.js +66 -7
- package/test/cli-integration.test.js +304 -0
- package/test/direct_smart_router_test.js +88 -0
- package/test/enhanced-cli-agent-skill-test.js +485 -0
- package/test/simple_test.js +82 -0
- package/test/smart_router_test_runner.js +123 -0
- package/test/smart_routing_edge_cases.test.js +284 -0
- package/test/smart_routing_simple_verification.js +139 -0
- package/test/smart_routing_verification.test.js +346 -0
- package/test/specific-cli-agent-skill-analysis.js +385 -0
- package/test/unit/smart_router.test.js +295 -0
- package/test/very_simple_test.js +54 -0
- package/src/cli/router.js +0 -1737
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Independent Session Recovery Tool
|
|
4
|
+
* Find and recover the latest session across all CLIs
|
|
5
|
+
* Does NOT depend on Stigmergy installation
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const fs = require('fs');
|
|
9
|
+
const path = require('path');
|
|
10
|
+
const os = require('os');
|
|
11
|
+
|
|
12
|
+
class IndependentSessionRecovery {
|
|
13
|
+
constructor() {
|
|
14
|
+
this.projectPath = process.cwd();
|
|
15
|
+
this.cliPaths = this.getAllCLISessionPaths();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Get all CLI session paths
|
|
19
|
+
getAllCLISessionPaths() {
|
|
20
|
+
const homeDir = os.homedir();
|
|
21
|
+
return {
|
|
22
|
+
claude: path.join(homeDir, '.claude', 'projects'),
|
|
23
|
+
gemini: path.join(homeDir, '.config', 'gemini', 'tmp'),
|
|
24
|
+
qwen: path.join(homeDir, '.qwen', 'projects'),
|
|
25
|
+
iflow: path.join(homeDir, '.iflow', 'projects'),
|
|
26
|
+
codebuddy: path.join(homeDir, '.codebuddy'),
|
|
27
|
+
codex: path.join(homeDir, '.config', 'codex'),
|
|
28
|
+
qodercli: path.join(homeDir, '.qoder', 'projects'),
|
|
29
|
+
kode: path.join(homeDir, '.kode', 'projects')
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Get project directory name (normalized)
|
|
34
|
+
getProjectDirName(cliType) {
|
|
35
|
+
// Windows drive letter format: D:\path -> D--path
|
|
36
|
+
return this.projectPath
|
|
37
|
+
.replace(/^([A-Za-z]):\\/, '$1--')
|
|
38
|
+
.replace(/\\/g, '-');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Find latest session across all CLIs
|
|
42
|
+
findLatestSession() {
|
|
43
|
+
let latestSession = null;
|
|
44
|
+
let latestTime = new Date(0);
|
|
45
|
+
|
|
46
|
+
for (const [cliType, basePath] of Object.entries(this.cliPaths)) {
|
|
47
|
+
if (!fs.existsSync(basePath)) continue;
|
|
48
|
+
|
|
49
|
+
const session = this.findLatestSessionForCLI(cliType, basePath);
|
|
50
|
+
if (session && session.modified > latestTime) {
|
|
51
|
+
latestSession = session;
|
|
52
|
+
latestTime = session.modified;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return latestSession;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Find latest session for a specific CLI
|
|
60
|
+
findLatestSessionForCLI(cliType, basePath) {
|
|
61
|
+
const projectDirName = this.getProjectDirName(cliType);
|
|
62
|
+
|
|
63
|
+
// Different CLIs have different directory structures
|
|
64
|
+
let sessionPath = basePath;
|
|
65
|
+
|
|
66
|
+
if (['claude', 'iflow', 'qodercli', 'kode'].includes(cliType) && basePath.includes('projects')) {
|
|
67
|
+
sessionPath = path.join(basePath, projectDirName);
|
|
68
|
+
} else if (cliType === 'gemini' && basePath.includes('tmp')) {
|
|
69
|
+
// Gemini uses hash directories
|
|
70
|
+
try {
|
|
71
|
+
const hashDirs = fs.readdirSync(basePath);
|
|
72
|
+
for (const hashDir of hashDirs) {
|
|
73
|
+
const hashDirPath = path.join(basePath, hashDir);
|
|
74
|
+
const chatsPath = path.join(hashDirPath, 'chats');
|
|
75
|
+
if (fs.existsSync(chatsPath)) {
|
|
76
|
+
const session = this.findLatestSessionInDir(chatsPath, cliType, hashDir);
|
|
77
|
+
if (session) return session;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return null;
|
|
81
|
+
} catch (error) {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
} else if (cliType === 'qwen' && basePath.includes('projects')) {
|
|
85
|
+
// Qwen uses projects/<projectName>/chats
|
|
86
|
+
const chatsPath = path.join(basePath, projectDirName, 'chats');
|
|
87
|
+
if (fs.existsSync(chatsPath)) {
|
|
88
|
+
return this.findLatestSessionInDir(chatsPath, cliType, projectDirName);
|
|
89
|
+
}
|
|
90
|
+
return null;
|
|
91
|
+
} else if (cliType === 'codebuddy') {
|
|
92
|
+
// CodeBuddy uses projects/<projectName> or root
|
|
93
|
+
const projectsPath = path.join(basePath, 'projects');
|
|
94
|
+
if (fs.existsSync(projectsPath)) {
|
|
95
|
+
const projectPath = path.join(projectsPath, projectDirName);
|
|
96
|
+
if (fs.existsSync(projectPath)) {
|
|
97
|
+
const session = this.findLatestSessionInDir(projectPath, cliType, projectDirName);
|
|
98
|
+
if (session) return session;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return this.findLatestSessionInDir(basePath, cliType, 'root');
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (!fs.existsSync(sessionPath)) return null;
|
|
105
|
+
|
|
106
|
+
return this.findLatestSessionInDir(sessionPath, cliType, projectDirName);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Find latest session in a directory
|
|
110
|
+
findLatestSessionInDir(dirPath, cliType, context) {
|
|
111
|
+
try {
|
|
112
|
+
const files = fs.readdirSync(dirPath);
|
|
113
|
+
|
|
114
|
+
// Filter for session files only
|
|
115
|
+
const sessionFiles = files.filter(file => {
|
|
116
|
+
// CodeBuddy's user-state.json should be skipped
|
|
117
|
+
if (cliType === 'codebuddy' && file === 'user-state.json') {
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
// Codex's slash_commands.json should be skipped
|
|
121
|
+
if (cliType === 'codex' && file === 'slash_commands.json') {
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
return file.endsWith('.jsonl') || file.endsWith('.json') || file.endsWith('.session');
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
if (sessionFiles.length === 0) return null;
|
|
128
|
+
|
|
129
|
+
let latestFile = null;
|
|
130
|
+
let latestTime = new Date(0);
|
|
131
|
+
|
|
132
|
+
for (const file of sessionFiles) {
|
|
133
|
+
const filePath = path.join(dirPath, file);
|
|
134
|
+
try {
|
|
135
|
+
const stats = fs.statSync(filePath);
|
|
136
|
+
if (stats.mtime > latestTime) {
|
|
137
|
+
latestTime = stats.mtime;
|
|
138
|
+
latestFile = file;
|
|
139
|
+
}
|
|
140
|
+
} catch (error) {
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (!latestFile) return null;
|
|
146
|
+
|
|
147
|
+
return {
|
|
148
|
+
cliType,
|
|
149
|
+
file: latestFile,
|
|
150
|
+
path: path.join(dirPath, latestFile),
|
|
151
|
+
modified: latestTime,
|
|
152
|
+
context
|
|
153
|
+
};
|
|
154
|
+
} catch (error) {
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Read and parse full session content
|
|
160
|
+
readFullSession(sessionPath) {
|
|
161
|
+
try {
|
|
162
|
+
const content = fs.readFileSync(sessionPath, 'utf8');
|
|
163
|
+
|
|
164
|
+
if (sessionPath.endsWith('.jsonl')) {
|
|
165
|
+
const lines = content.trim().split('\n').filter(line => line.trim());
|
|
166
|
+
return lines.map(line => {
|
|
167
|
+
try {
|
|
168
|
+
return JSON.parse(line);
|
|
169
|
+
} catch (e) {
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
}).filter(msg => msg !== null);
|
|
173
|
+
} else {
|
|
174
|
+
return JSON.parse(content);
|
|
175
|
+
}
|
|
176
|
+
} catch (error) {
|
|
177
|
+
console.error(`Error reading session: ${error.message}`);
|
|
178
|
+
return null;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Format full session for output
|
|
183
|
+
formatFullSession(session) {
|
|
184
|
+
const messages = this.readFullSession(session.path);
|
|
185
|
+
if (!messages) {
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Handle different message formats
|
|
190
|
+
const messageList = Array.isArray(messages) ? messages :
|
|
191
|
+
(messages.messages && Array.isArray(messages.messages)) ? messages.messages : [];
|
|
192
|
+
|
|
193
|
+
if (messageList.length === 0) {
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const output = [];
|
|
198
|
+
output.push('📋 最新会话恢复');
|
|
199
|
+
output.push('');
|
|
200
|
+
output.push(`🔧 来源: ${session.cliType.toUpperCase()}`);
|
|
201
|
+
output.push(`📅 最后修改: ${session.modified.toLocaleString()}`);
|
|
202
|
+
output.push(`📁 文件: ${session.file}`);
|
|
203
|
+
output.push('');
|
|
204
|
+
output.push('---');
|
|
205
|
+
output.push('');
|
|
206
|
+
output.push('📝 完整对话内容:');
|
|
207
|
+
output.push('');
|
|
208
|
+
|
|
209
|
+
// Extract and format all messages
|
|
210
|
+
messageList.forEach((msg, index) => {
|
|
211
|
+
const role = msg.type || msg.role || 'unknown';
|
|
212
|
+
const prefix = role === 'user' ? '👤 用户' : '🤖 助手';
|
|
213
|
+
const content = this.extractMessageContent(msg);
|
|
214
|
+
|
|
215
|
+
if (content && content.trim()) {
|
|
216
|
+
output.push(`${prefix}:`);
|
|
217
|
+
output.push(content);
|
|
218
|
+
output.push('');
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
return output.join('\n');
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Extract text content from a message
|
|
226
|
+
extractMessageContent(msg) {
|
|
227
|
+
if (msg.message && typeof msg.message === 'object') {
|
|
228
|
+
const content = msg.message.content || msg.message.text || '';
|
|
229
|
+
return this.extractTextFromContent(content);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const content = msg.content || msg.text || '';
|
|
233
|
+
return this.extractTextFromContent(content);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Extract text from content
|
|
237
|
+
extractTextFromContent(content) {
|
|
238
|
+
if (typeof content === 'string') {
|
|
239
|
+
return content;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (Array.isArray(content)) {
|
|
243
|
+
return content
|
|
244
|
+
.map(item => {
|
|
245
|
+
if (typeof item === 'string') return item;
|
|
246
|
+
if (item && typeof item === 'object') {
|
|
247
|
+
return item.text || item.content || '';
|
|
248
|
+
}
|
|
249
|
+
return '';
|
|
250
|
+
})
|
|
251
|
+
.filter(text => text && typeof text === 'string')
|
|
252
|
+
.join(' ');
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (content && typeof content === 'object') {
|
|
256
|
+
return content.text || content.content || '';
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
return '';
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Execute recovery
|
|
263
|
+
execute(options = {}) {
|
|
264
|
+
const {
|
|
265
|
+
fullRecovery = true, // Default: recover full session
|
|
266
|
+
listOnly = false, // List sessions without recovery
|
|
267
|
+
cliFilter = null // Filter by specific CLI
|
|
268
|
+
} = options;
|
|
269
|
+
|
|
270
|
+
if (listOnly) {
|
|
271
|
+
// Advanced mode: list all sessions
|
|
272
|
+
return this.listAllSessions(cliFilter);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Default mode: find and recover latest session
|
|
276
|
+
let session;
|
|
277
|
+
|
|
278
|
+
if (cliFilter) {
|
|
279
|
+
// Filter by specific CLI
|
|
280
|
+
const basePath = this.cliPaths[cliFilter.toLowerCase()];
|
|
281
|
+
if (basePath && fs.existsSync(basePath)) {
|
|
282
|
+
session = this.findLatestSessionForCLI(cliFilter.toLowerCase(), basePath);
|
|
283
|
+
}
|
|
284
|
+
} else {
|
|
285
|
+
// Find latest across all CLIs
|
|
286
|
+
session = this.findLatestSession();
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
if (!session) {
|
|
290
|
+
console.log('📭 未找到任何会话');
|
|
291
|
+
console.log('');
|
|
292
|
+
console.log(`💡 项目路径: ${this.projectPath}`);
|
|
293
|
+
if (cliFilter) {
|
|
294
|
+
console.log(`💡 指定CLI: ${cliFilter}`);
|
|
295
|
+
}
|
|
296
|
+
return 1;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
if (!fullRecovery) {
|
|
300
|
+
// Show summary only
|
|
301
|
+
this.showSessionSummary(session);
|
|
302
|
+
} else {
|
|
303
|
+
// Show full session
|
|
304
|
+
const formatted = this.formatFullSession(session);
|
|
305
|
+
if (formatted) {
|
|
306
|
+
console.log(formatted);
|
|
307
|
+
} else {
|
|
308
|
+
console.log('📭 无法解析会话内容');
|
|
309
|
+
return 1;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
return 0;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// Show session summary (advanced mode)
|
|
317
|
+
showSessionSummary(session) {
|
|
318
|
+
const output = [];
|
|
319
|
+
output.push('📋 会话信息');
|
|
320
|
+
output.push('');
|
|
321
|
+
output.push(`🔧 来源: ${session.cliType.toUpperCase()}`);
|
|
322
|
+
output.push(`📅 最后修改: ${session.modified.toLocaleString()}`);
|
|
323
|
+
output.push(`📁 文件: ${session.file}`);
|
|
324
|
+
output.push(`📂 路径: ${session.path}`);
|
|
325
|
+
if (session.context) {
|
|
326
|
+
output.push(`🔍 上下文: ${session.context}`);
|
|
327
|
+
}
|
|
328
|
+
console.log(output.join('\n'));
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// List all sessions (advanced mode)
|
|
332
|
+
listAllSessions(cliFilter = null) {
|
|
333
|
+
const sessions = [];
|
|
334
|
+
|
|
335
|
+
for (const [cliType, basePath] of Object.entries(this.cliPaths)) {
|
|
336
|
+
if (cliFilter && cliType.toLowerCase() !== cliFilter.toLowerCase()) continue;
|
|
337
|
+
|
|
338
|
+
if (!fs.existsSync(basePath)) continue;
|
|
339
|
+
|
|
340
|
+
const session = this.findLatestSessionForCLI(cliType, basePath);
|
|
341
|
+
if (session) {
|
|
342
|
+
sessions.push(session);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
if (sessions.length === 0) {
|
|
347
|
+
console.log('📭 未找到任何会话');
|
|
348
|
+
return 1;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// Sort by modification time (newest first)
|
|
352
|
+
sessions.sort((a, b) => b.modified - a.modified);
|
|
353
|
+
|
|
354
|
+
const output = [];
|
|
355
|
+
output.push('📋 所有会话列表');
|
|
356
|
+
output.push('');
|
|
357
|
+
output.push(`📊 共找到 ${sessions.length} 个会话`);
|
|
358
|
+
output.push('');
|
|
359
|
+
|
|
360
|
+
sessions.forEach((session, index) => {
|
|
361
|
+
output.push(`${index + 1}. ${session.cliType.toUpperCase()}`);
|
|
362
|
+
output.push(` 📅 ${session.modified.toLocaleString()}`);
|
|
363
|
+
output.push(` 📁 ${session.file}`);
|
|
364
|
+
output.push('');
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
console.log(output.join('\n'));
|
|
368
|
+
return 0;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// Run as CLI command
|
|
373
|
+
if (require.main === module) {
|
|
374
|
+
const recovery = new IndependentSessionRecovery();
|
|
375
|
+
|
|
376
|
+
// Parse command line options
|
|
377
|
+
const options = {};
|
|
378
|
+
const args = process.argv.slice(2);
|
|
379
|
+
|
|
380
|
+
for (let i = 0; i < args.length; i++) {
|
|
381
|
+
const arg = args[i];
|
|
382
|
+
|
|
383
|
+
if (arg === '--list' || arg === '-l') {
|
|
384
|
+
options.listOnly = true;
|
|
385
|
+
} else if (arg === '--summary' || arg === '-s') {
|
|
386
|
+
options.fullRecovery = false;
|
|
387
|
+
} else if (arg === '--cli' && i + 1 < args.length) {
|
|
388
|
+
options.cliFilter = args[++i];
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
process.exit(recovery.execute(options));
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
module.exports = IndependentSessionRecovery;
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Install Command Module
|
|
3
|
+
* Handles CLI tool installation commands
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const StigmergyInstaller = require('../../core/installer');
|
|
7
|
+
const chalk = require('chalk');
|
|
8
|
+
const { ensureSkillsCache } = require('../utils/skills_cache');
|
|
9
|
+
const { handleDeployCommand } = require('./project');
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Handle install command
|
|
13
|
+
* @param {Object} options - Command options
|
|
14
|
+
* @param {string} options.cli - Specific CLI to install
|
|
15
|
+
* @param {boolean} options.verbose - Verbose output
|
|
16
|
+
* @param {boolean} options.force - Force installation
|
|
17
|
+
*/
|
|
18
|
+
async function handleInstallCommand(options = {}) {
|
|
19
|
+
const installer = new StigmergyInstaller();
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
// Initialize or update skills/agents cache
|
|
23
|
+
await ensureSkillsCache({ verbose: options.verbose || process.env.DEBUG === 'true' });
|
|
24
|
+
|
|
25
|
+
console.log(chalk.blue('🚀 Starting CLI tools installation...'));
|
|
26
|
+
|
|
27
|
+
// Handle auto-install mode (non-interactive)
|
|
28
|
+
if (options.nonInteractive) {
|
|
29
|
+
console.log(chalk.blue('[AUTO-INSTALL] Running in non-interactive mode'));
|
|
30
|
+
|
|
31
|
+
// Scan for available and missing tools
|
|
32
|
+
const { missing: missingTools, available: availableTools } = await installer.scanCLI();
|
|
33
|
+
|
|
34
|
+
// Filter to only install tools with autoInstall: true, unless --all is specified
|
|
35
|
+
let toolsToInstall;
|
|
36
|
+
if (options.all) {
|
|
37
|
+
console.log(chalk.blue('[AUTO-INSTALL] Installing ALL CLI tools (--all mode)'));
|
|
38
|
+
toolsToInstall = Object.entries(missingTools);
|
|
39
|
+
} else {
|
|
40
|
+
console.log(chalk.blue('[AUTO-INSTALL] Installing only auto-install tools'));
|
|
41
|
+
toolsToInstall = Object.entries(missingTools)
|
|
42
|
+
.filter(([toolName]) => installer.router.tools[toolName]?.autoInstall === true);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const filteredMissingTools = Object.fromEntries(toolsToInstall);
|
|
46
|
+
|
|
47
|
+
if (Object.keys(filteredMissingTools).length === 0) {
|
|
48
|
+
console.log(chalk.green('✅ All CLI tools are already installed!'));
|
|
49
|
+
return {
|
|
50
|
+
success: true,
|
|
51
|
+
installed: [],
|
|
52
|
+
existing: Object.keys(availableTools)
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Install all missing tools in auto mode
|
|
57
|
+
const selectedTools = Object.keys(filteredMissingTools);
|
|
58
|
+
console.log(chalk.blue(`[AUTO-INSTALL] Installing ${selectedTools.length} tools: ${selectedTools.join(', ')}`));
|
|
59
|
+
|
|
60
|
+
const installResult = await installer.installTools(selectedTools, filteredMissingTools);
|
|
61
|
+
|
|
62
|
+
if (installResult.success) {
|
|
63
|
+
console.log(chalk.green('✅ Auto-install completed successfully!'));
|
|
64
|
+
|
|
65
|
+
// 如果是 --all 模式,自动部署所有工具
|
|
66
|
+
if (options.all) {
|
|
67
|
+
console.log(chalk.blue('\n🚀 Deploying hooks for all installed tools...'));
|
|
68
|
+
try {
|
|
69
|
+
const deployResult = await handleDeployCommand({
|
|
70
|
+
verbose: options.verbose || process.env.DEBUG === 'true',
|
|
71
|
+
force: options.force || false,
|
|
72
|
+
all: true
|
|
73
|
+
});
|
|
74
|
+
if (deployResult.success) {
|
|
75
|
+
console.log(chalk.green('✅ Hooks deployed successfully!'));
|
|
76
|
+
}
|
|
77
|
+
} catch (deployError) {
|
|
78
|
+
console.log(chalk.yellow(`⚠️ Hook deployment warning: ${deployError.message}`));
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
success: true,
|
|
84
|
+
installed: installResult.installed || [],
|
|
85
|
+
failed: installResult.failed || [],
|
|
86
|
+
existing: Object.keys(availableTools)
|
|
87
|
+
};
|
|
88
|
+
} else {
|
|
89
|
+
console.log(chalk.yellow('⚠️ Some tools may not have installed successfully'));
|
|
90
|
+
return {
|
|
91
|
+
success: false,
|
|
92
|
+
installed: installResult.installed || [],
|
|
93
|
+
failed: installResult.failed || [],
|
|
94
|
+
existing: Object.keys(availableTools),
|
|
95
|
+
error: 'Some installations failed'
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Interactive install mode
|
|
101
|
+
const { missing: missingTools, available: availableTools } = await installer.scanCLI();
|
|
102
|
+
|
|
103
|
+
// Filter to only show tools with autoInstall: true
|
|
104
|
+
const toolsToInstall = Object.entries(missingTools)
|
|
105
|
+
.filter(([toolName]) => installer.router.tools[toolName]?.autoInstall === true);
|
|
106
|
+
const filteredMissingTools = Object.fromEntries(toolsToInstall);
|
|
107
|
+
|
|
108
|
+
if (Object.keys(filteredMissingTools).length === 0) {
|
|
109
|
+
console.log(chalk.green('✅ All auto-install CLI tools are already installed!'));
|
|
110
|
+
|
|
111
|
+
if (Object.keys(availableTools).length > 0) {
|
|
112
|
+
console.log(chalk.cyan('\n📦 Available tools:'));
|
|
113
|
+
Object.keys(availableTools).forEach(tool => {
|
|
114
|
+
console.log(` ✅ ${tool}`);
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
success: true,
|
|
120
|
+
installed: [],
|
|
121
|
+
existing: Object.keys(availableTools)
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
console.log(chalk.yellow(`\n⚠️ Found ${Object.keys(filteredMissingTools).length} missing tools:`));
|
|
126
|
+
Object.entries(filteredMissingTools).forEach(([toolName, toolInfo]) => {
|
|
127
|
+
console.log(` - ${toolInfo.name}: ${toolInfo.install}`);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
// For now, install all missing tools
|
|
131
|
+
const selectedTools = Object.keys(filteredMissingTools);
|
|
132
|
+
const installResult = await installer.installTools(selectedTools, filteredMissingTools);
|
|
133
|
+
|
|
134
|
+
if (installResult.success) {
|
|
135
|
+
console.log(chalk.green('✅ Installation completed successfully!'));
|
|
136
|
+
|
|
137
|
+
if (installResult.installed && installResult.installed.length > 0) {
|
|
138
|
+
console.log(chalk.cyan('\n📦 Installed tools:'));
|
|
139
|
+
installResult.installed.forEach(tool => {
|
|
140
|
+
console.log(` ✅ ${tool}`);
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (installResult.failed && installResult.failed.length > 0) {
|
|
145
|
+
console.log(chalk.red('\n❌ Failed tools:'));
|
|
146
|
+
installResult.failed.forEach(tool => {
|
|
147
|
+
console.log(` ❌ ${tool}`);
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return {
|
|
152
|
+
success: true,
|
|
153
|
+
installed: installResult.installed || [],
|
|
154
|
+
failed: installResult.failed || [],
|
|
155
|
+
existing: Object.keys(availableTools)
|
|
156
|
+
};
|
|
157
|
+
} else {
|
|
158
|
+
console.log(chalk.red('❌ Installation failed!'));
|
|
159
|
+
return {
|
|
160
|
+
success: false,
|
|
161
|
+
installed: installResult.installed || [],
|
|
162
|
+
failed: installResult.failed || [],
|
|
163
|
+
existing: Object.keys(availableTools),
|
|
164
|
+
error: 'Installation failed'
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
} catch (error) {
|
|
168
|
+
console.log(chalk.red(`❌ Installation error: ${error.message}`));
|
|
169
|
+
return {
|
|
170
|
+
success: false,
|
|
171
|
+
error: error.message,
|
|
172
|
+
installed: []
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
module.exports = {
|
|
178
|
+
handleInstallCommand
|
|
179
|
+
};
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Permission Management Commands
|
|
3
|
+
* Modular implementation for fix-perms and perm-check commands
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const chalk = require('chalk');
|
|
7
|
+
const DirectoryPermissionManager = require('../../core/directory_permission_manager');
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Handle permission check command
|
|
11
|
+
* @param {Object} options - Command options
|
|
12
|
+
*/
|
|
13
|
+
async function handlePermCheckCommand(options = {}) {
|
|
14
|
+
try {
|
|
15
|
+
console.log(chalk.cyan('[PERM-CHECK] Checking current directory permissions...\n'));
|
|
16
|
+
|
|
17
|
+
const permissionManager = new DirectoryPermissionManager({
|
|
18
|
+
verbose: options.verbose || process.env.DEBUG === 'true'
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
const hasWritePermission = await permissionManager.checkWritePermission();
|
|
22
|
+
|
|
23
|
+
console.log(`📍 Current directory: ${process.cwd()}`);
|
|
24
|
+
console.log(`🔧 Write permission: ${hasWritePermission ? chalk.green('✅ Yes') : chalk.red('❌ No')}`);
|
|
25
|
+
|
|
26
|
+
if (!hasWritePermission) {
|
|
27
|
+
console.log('\n💡 Suggestions:');
|
|
28
|
+
console.log('1. Run: stigmergy fix-perms # Fix permissions automatically');
|
|
29
|
+
console.log('2. Change to user directory: cd ~');
|
|
30
|
+
console.log('3. Create project directory: mkdir ~/stigmergy && cd ~/stigmergy');
|
|
31
|
+
|
|
32
|
+
console.log('\n🔍 System Info:');
|
|
33
|
+
const sysInfo = permissionManager.getSystemInfo();
|
|
34
|
+
console.log(` Platform: ${sysInfo.platform}`);
|
|
35
|
+
console.log(` Shell: ${sysInfo.shell}`);
|
|
36
|
+
console.log(` Home: ${sysInfo.homeDir}`);
|
|
37
|
+
} else {
|
|
38
|
+
console.log(chalk.green('\n✅ Directory permissions are OK!'));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return { success: true, hasWritePermission };
|
|
42
|
+
} catch (error) {
|
|
43
|
+
console.error(chalk.red('[ERROR] Permission check failed:'), error.message);
|
|
44
|
+
return { success: false, error: error.message };
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Handle fix permissions command
|
|
50
|
+
* @param {Object} options - Command options
|
|
51
|
+
*/
|
|
52
|
+
async function handleFixPermsCommand(options = {}) {
|
|
53
|
+
try {
|
|
54
|
+
console.log(chalk.cyan('[FIX-PERMS] Setting up working directory with proper permissions...\n'));
|
|
55
|
+
|
|
56
|
+
// Using DirectoryPermissionManager for permission handling
|
|
57
|
+
// This provides the core permission management functionality
|
|
58
|
+
const permissionManager = new DirectoryPermissionManager({
|
|
59
|
+
verbose: options.verbose || process.env.DEBUG === 'true',
|
|
60
|
+
createStigmergyDir: true
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const hasWritePermission = await permissionManager.checkWritePermission();
|
|
64
|
+
|
|
65
|
+
if (hasWritePermission) {
|
|
66
|
+
console.log(chalk.green('✅ Current directory already has proper permissions!'));
|
|
67
|
+
return { success: true, alreadyFixed: true };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
console.log(chalk.yellow('🔧 Attempting to fix permissions...'));
|
|
71
|
+
|
|
72
|
+
// Try to create a .stigmergy directory in user home as fallback
|
|
73
|
+
const fs = require('fs').promises;
|
|
74
|
+
const path = require('path');
|
|
75
|
+
const os = require('os');
|
|
76
|
+
|
|
77
|
+
const stigmergyDir = path.join(os.homedir(), '.stigmergy');
|
|
78
|
+
|
|
79
|
+
try {
|
|
80
|
+
await fs.mkdir(stigmergyDir, { recursive: true });
|
|
81
|
+
console.log(chalk.green(`✅ Created Stigmergy directory: ${stigmergyDir}`));
|
|
82
|
+
console.log(chalk.yellow('💡 Consider changing to this directory for your projects'));
|
|
83
|
+
|
|
84
|
+
return { success: true, createdDirectory: stigmergyDir };
|
|
85
|
+
} catch (mkdirError) {
|
|
86
|
+
console.log(chalk.red('❌ Could not create directory or fix permissions'));
|
|
87
|
+
console.log(chalk.red(`Error: ${mkdirError.message}`));
|
|
88
|
+
|
|
89
|
+
console.log('\n🔧 Manual fix required:');
|
|
90
|
+
console.log('1. Run in a directory where you have write permissions');
|
|
91
|
+
console.log('2. Try: cd ~ && mkdir stigmergy-workspace && cd stigmergy-workspace');
|
|
92
|
+
|
|
93
|
+
return { success: false, error: mkdirError.message };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
} catch (error) {
|
|
97
|
+
console.error(chalk.red('[ERROR] Permission setup failed:'), error.message);
|
|
98
|
+
if (options.verbose) {
|
|
99
|
+
console.error(error.stack);
|
|
100
|
+
}
|
|
101
|
+
return { success: false, error: error.message };
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
module.exports = {
|
|
106
|
+
handlePermCheckCommand,
|
|
107
|
+
handleFixPermsCommand
|
|
108
|
+
};
|