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,301 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* System Commands
|
|
3
|
+
* Modular implementation for clean, diagnostic, and other system commands
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const chalk = require('chalk');
|
|
7
|
+
const fs = require('fs').promises;
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const os = require('os');
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Handle diagnostic command
|
|
13
|
+
* @param {Object} options - Command options
|
|
14
|
+
*/
|
|
15
|
+
async function handleDiagnosticCommand(options = {}) {
|
|
16
|
+
try {
|
|
17
|
+
console.log(chalk.cyan('[DIAGNOSTIC] Stigmergy CLI System Diagnostic...\n'));
|
|
18
|
+
|
|
19
|
+
const results = {
|
|
20
|
+
system: {},
|
|
21
|
+
directories: {},
|
|
22
|
+
files: {},
|
|
23
|
+
permissions: {},
|
|
24
|
+
summary: { issues: 0, warnings: 0, ok: 0 }
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
// System information
|
|
28
|
+
console.log(chalk.blue('🖥️ System Information:'));
|
|
29
|
+
const systemInfo = {
|
|
30
|
+
platform: os.platform(),
|
|
31
|
+
arch: os.arch(),
|
|
32
|
+
nodeVersion: process.version,
|
|
33
|
+
memory: Math.round(os.totalmem() / 1024 / 1024) + ' MB',
|
|
34
|
+
freeMemory: Math.round(os.freemem() / 1024 / 1024) + ' MB',
|
|
35
|
+
homeDir: os.homedir(),
|
|
36
|
+
currentDir: process.cwd()
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
results.system = systemInfo;
|
|
40
|
+
Object.entries(systemInfo).forEach(([key, value]) => {
|
|
41
|
+
console.log(` ${key}: ${chalk.green(value)}`);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// Directory checks
|
|
45
|
+
console.log(chalk.blue('\n📁 Directory Structure:'));
|
|
46
|
+
const directories = [
|
|
47
|
+
path.join(os.homedir(), '.stigmergy'),
|
|
48
|
+
path.join(os.homedir(), '.claude'),
|
|
49
|
+
path.join(os.homedir(), '.gemini'),
|
|
50
|
+
path.join(os.homedir(), '.qwen'),
|
|
51
|
+
path.join(process.cwd(), 'node_modules')
|
|
52
|
+
];
|
|
53
|
+
|
|
54
|
+
for (const dir of directories) {
|
|
55
|
+
try {
|
|
56
|
+
const stats = await fs.stat(dir);
|
|
57
|
+
results.directories[dir] = { exists: true, size: stats.size };
|
|
58
|
+
console.log(` ${chalk.green('✅')} ${dir}`);
|
|
59
|
+
results.summary.ok++;
|
|
60
|
+
} catch (error) {
|
|
61
|
+
results.directories[dir] = { exists: false, error: error.code };
|
|
62
|
+
console.log(` ${chalk.yellow('⚠️')} ${dir} (${error.code})`);
|
|
63
|
+
results.summary.warnings++;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Permission checks
|
|
68
|
+
console.log(chalk.blue('\n🔐 Permission Checks:'));
|
|
69
|
+
try {
|
|
70
|
+
await fs.access(process.cwd(), fs.constants.W_OK);
|
|
71
|
+
console.log(` ${chalk.green('✅')} Current directory writable`);
|
|
72
|
+
results.permissions.currentDir = true;
|
|
73
|
+
results.summary.ok++;
|
|
74
|
+
} catch (error) {
|
|
75
|
+
console.log(` ${chalk.red('❌')} Current directory not writable`);
|
|
76
|
+
results.permissions.currentDir = false;
|
|
77
|
+
results.summary.issues++;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
try {
|
|
81
|
+
await fs.access(os.homedir(), fs.constants.W_OK);
|
|
82
|
+
console.log(` ${chalk.green('✅')} Home directory writable`);
|
|
83
|
+
results.permissions.homeDir = true;
|
|
84
|
+
results.summary.ok++;
|
|
85
|
+
} catch (error) {
|
|
86
|
+
console.log(` ${chalk.red('❌')} Home directory not writable`);
|
|
87
|
+
results.permissions.homeDir = false;
|
|
88
|
+
results.summary.issues++;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Summary
|
|
92
|
+
console.log(chalk.blue('\n📊 Diagnostic Summary:'));
|
|
93
|
+
console.log(` Issues: ${chalk.red(results.summary.issues)}`);
|
|
94
|
+
console.log(` Warnings: ${chalk.yellow(results.summary.warnings)}`);
|
|
95
|
+
console.log(` OK: ${chalk.green(results.summary.ok)}`);
|
|
96
|
+
|
|
97
|
+
if (results.summary.issues > 0) {
|
|
98
|
+
console.log(chalk.red('\n❌ Critical issues found - Fix recommended'));
|
|
99
|
+
console.log('Run: stigmergy fix-perms');
|
|
100
|
+
} else if (results.summary.warnings > 0) {
|
|
101
|
+
console.log(chalk.yellow('\n⚠️ Some warnings found - Check recommended'));
|
|
102
|
+
} else {
|
|
103
|
+
console.log(chalk.green('\n✅ System looks healthy!'));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return { success: true, results };
|
|
107
|
+
} catch (error) {
|
|
108
|
+
console.error(chalk.red('[ERROR] Diagnostic failed:'), error.message);
|
|
109
|
+
return { success: false, error: error.message };
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Handle clean command
|
|
115
|
+
* @param {Object} options - Command options
|
|
116
|
+
*/
|
|
117
|
+
async function handleCleanCommand(options = {}) {
|
|
118
|
+
try {
|
|
119
|
+
console.log(chalk.cyan('[CLEAN] Starting intelligent cache cleaning...\n'));
|
|
120
|
+
|
|
121
|
+
const stats = {
|
|
122
|
+
found: 0,
|
|
123
|
+
cleaned: 0,
|
|
124
|
+
skipped: 0,
|
|
125
|
+
totalSize: 0,
|
|
126
|
+
cleanedSize: 0,
|
|
127
|
+
errors: []
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
// Define cleanup targets with priority and safety levels
|
|
131
|
+
const cleanupTargets = [
|
|
132
|
+
// Priority 1: Always safe to clean
|
|
133
|
+
{
|
|
134
|
+
path: path.join(os.tmpdir(), 'stigmergy-*'),
|
|
135
|
+
priority: 1,
|
|
136
|
+
safe: true,
|
|
137
|
+
description: 'Stigmergy temporary files'
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
path: path.join(os.homedir(), '.stigmergy', 'cache'),
|
|
141
|
+
priority: 1,
|
|
142
|
+
safe: true,
|
|
143
|
+
description: 'Stigmergy cache'
|
|
144
|
+
},
|
|
145
|
+
|
|
146
|
+
// Priority 2: Generally safe (user data)
|
|
147
|
+
{
|
|
148
|
+
path: path.join(process.cwd(), 'node_modules', '.cache'),
|
|
149
|
+
priority: 2,
|
|
150
|
+
safe: true,
|
|
151
|
+
description: 'Project cache'
|
|
152
|
+
},
|
|
153
|
+
|
|
154
|
+
// Priority 3: CLI tool caches (may have permission issues)
|
|
155
|
+
{
|
|
156
|
+
path: path.join(os.homedir(), '.claude', 'cache'),
|
|
157
|
+
priority: 3,
|
|
158
|
+
safe: false,
|
|
159
|
+
description: 'Claude CLI cache'
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
path: path.join(os.homedir(), '.gemini', 'cache'),
|
|
163
|
+
priority: 3,
|
|
164
|
+
safe: false,
|
|
165
|
+
description: 'Gemini CLI cache'
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
path: path.join(os.homedir(), '.qwen', 'cache'),
|
|
169
|
+
priority: 3,
|
|
170
|
+
safe: false,
|
|
171
|
+
description: 'Qwen CLI cache'
|
|
172
|
+
}
|
|
173
|
+
];
|
|
174
|
+
|
|
175
|
+
// Sort by priority
|
|
176
|
+
cleanupTargets.sort((a, b) => a.priority - b.priority);
|
|
177
|
+
|
|
178
|
+
console.log(chalk.blue('🔍 Scanning cleanup targets...'));
|
|
179
|
+
|
|
180
|
+
for (const target of cleanupTargets) {
|
|
181
|
+
try {
|
|
182
|
+
const isPattern = target.path.includes('*');
|
|
183
|
+
let targetPaths = [];
|
|
184
|
+
|
|
185
|
+
if (isPattern) {
|
|
186
|
+
// Simple glob pattern handling
|
|
187
|
+
const baseDir = path.dirname(target.path);
|
|
188
|
+
const pattern = path.basename(target.path).replace('*', '');
|
|
189
|
+
try {
|
|
190
|
+
const files = await fs.readdir(baseDir);
|
|
191
|
+
targetPaths = files
|
|
192
|
+
.filter(file => file.includes(pattern))
|
|
193
|
+
.map(file => path.join(baseDir, file));
|
|
194
|
+
} catch (error) {
|
|
195
|
+
// Directory doesn't exist, skip silently
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
} else {
|
|
199
|
+
targetPaths = [target.path];
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
for (const targetPath of targetPaths) {
|
|
203
|
+
try {
|
|
204
|
+
const targetStats = await fs.stat(targetPath);
|
|
205
|
+
stats.found++;
|
|
206
|
+
stats.totalSize += targetStats.size;
|
|
207
|
+
|
|
208
|
+
if (!options.dryRun) {
|
|
209
|
+
// Attempt to clean with better error handling
|
|
210
|
+
try {
|
|
211
|
+
if (targetStats.isDirectory()) {
|
|
212
|
+
await fs.rmdir(targetPath, { recursive: true });
|
|
213
|
+
} else {
|
|
214
|
+
await fs.unlink(targetPath);
|
|
215
|
+
}
|
|
216
|
+
stats.cleaned++;
|
|
217
|
+
stats.cleanedSize += targetStats.size;
|
|
218
|
+
|
|
219
|
+
// Only show successful cleanups in normal mode
|
|
220
|
+
if (!options.quiet) {
|
|
221
|
+
console.log(` ${chalk.green('✅')} Cleaned ${target.description}`);
|
|
222
|
+
}
|
|
223
|
+
} catch (cleanError) {
|
|
224
|
+
// Silent failure for permission issues
|
|
225
|
+
stats.skipped++;
|
|
226
|
+
stats.errors.push({
|
|
227
|
+
path: targetPath,
|
|
228
|
+
error: cleanError.code,
|
|
229
|
+
safe: target.safe
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
} else {
|
|
233
|
+
// Dry run mode
|
|
234
|
+
console.log(` ${chalk.blue('🔍')} Would clean: ${target.description} (${Math.round(targetStats.size / 1024)} KB)`);
|
|
235
|
+
}
|
|
236
|
+
} catch (statError) {
|
|
237
|
+
// File might be locked or in use
|
|
238
|
+
stats.skipped++;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
} catch (error) {
|
|
242
|
+
// Silently skip inaccessible targets
|
|
243
|
+
stats.skipped++;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Show summary with user-friendly output
|
|
248
|
+
console.log(chalk.blue('\n📊 Cleanup Results:'));
|
|
249
|
+
|
|
250
|
+
if (stats.cleaned > 0) {
|
|
251
|
+
console.log(` ${chalk.green('✅')} Cleaned: ${stats.cleaned} items (${Math.round(stats.cleanedSize / 1024)} KB)`);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if (stats.skipped > 0) {
|
|
255
|
+
console.log(` ${chalk.yellow('⚠️')} Skipped: ${stats.skipped} items (in use or permission protected)`);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Show critical errors only (safe targets that failed)
|
|
259
|
+
const criticalErrors = stats.errors.filter(e => e.safe && e.error === 'EPERM');
|
|
260
|
+
if (criticalErrors.length > 0 && options.verbose) {
|
|
261
|
+
console.log(` ${chalk.red('❌')} Permission issues on ${criticalErrors.length} safe targets`);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Final user-friendly message
|
|
265
|
+
if (!options.dryRun) {
|
|
266
|
+
if (stats.cleaned > 0) {
|
|
267
|
+
console.log(chalk.green('\n✅ Cache cleanup completed successfully!'));
|
|
268
|
+
} else {
|
|
269
|
+
console.log(chalk.yellow('\n💡 No cache files were available for cleaning'));
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (stats.skipped > 0 && stats.cleaned === 0) {
|
|
273
|
+
console.log(chalk.gray(' Some files were in use or require admin permissions'));
|
|
274
|
+
}
|
|
275
|
+
} else {
|
|
276
|
+
console.log(chalk.blue('\n💡 Dry run completed. Run without --dry-run to actually clean.'));
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
return {
|
|
280
|
+
success: true,
|
|
281
|
+
stats: {
|
|
282
|
+
cleaned: stats.cleaned,
|
|
283
|
+
skipped: stats.skipped,
|
|
284
|
+
totalSize: Math.round(stats.totalSize / 1024),
|
|
285
|
+
cleanedSize: Math.round(stats.cleanedSize / 1024)
|
|
286
|
+
}
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
} catch (error) {
|
|
290
|
+
// Show only critical errors to user
|
|
291
|
+
if (options.verbose) {
|
|
292
|
+
console.error(chalk.red('[ERROR] Cache cleaning failed:'), error.message);
|
|
293
|
+
}
|
|
294
|
+
return { success: false, error: error.message };
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
module.exports = {
|
|
299
|
+
handleDiagnosticCommand,
|
|
300
|
+
handleCleanCommand
|
|
301
|
+
};
|
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Universal Session Recovery Tool
|
|
4
|
+
* Find and recover the latest session across all CLIs
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const os = require('os');
|
|
10
|
+
|
|
11
|
+
class UniversalSessionRecovery {
|
|
12
|
+
constructor() {
|
|
13
|
+
this.projectPath = process.cwd();
|
|
14
|
+
this.cliPaths = this.getAllCLISessionPaths();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Get all CLI session paths
|
|
18
|
+
getAllCLISessionPaths() {
|
|
19
|
+
const homeDir = os.homedir();
|
|
20
|
+
return {
|
|
21
|
+
claude: path.join(homeDir, '.claude', 'projects'),
|
|
22
|
+
gemini: path.join(homeDir, '.config', 'gemini', 'tmp'),
|
|
23
|
+
qwen: path.join(homeDir, '.qwen', 'projects'),
|
|
24
|
+
iflow: path.join(homeDir, '.iflow', 'projects'),
|
|
25
|
+
codebuddy: path.join(homeDir, '.codebuddy'),
|
|
26
|
+
codex: path.join(homeDir, '.config', 'codex'),
|
|
27
|
+
qodercli: path.join(homeDir, '.qoder', 'projects'),
|
|
28
|
+
kode: path.join(homeDir, '.kode', 'projects')
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Get project directory name (normalized)
|
|
33
|
+
getProjectDirName(cliType) {
|
|
34
|
+
// Windows drive letter format: D:\path -> D--path
|
|
35
|
+
return this.projectPath
|
|
36
|
+
.replace(/^([A-Za-z]):\\/, '$1--')
|
|
37
|
+
.replace(/\\/g, '-');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Find latest session across all CLIs
|
|
41
|
+
findLatestSession() {
|
|
42
|
+
let latestSession = null;
|
|
43
|
+
let latestTime = new Date(0);
|
|
44
|
+
|
|
45
|
+
for (const [cliType, basePath] of Object.entries(this.cliPaths)) {
|
|
46
|
+
if (!fs.existsSync(basePath)) continue;
|
|
47
|
+
|
|
48
|
+
const session = this.findLatestSessionForCLI(cliType, basePath);
|
|
49
|
+
if (session && session.modified > latestTime) {
|
|
50
|
+
latestSession = session;
|
|
51
|
+
latestTime = session.modified;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return latestSession;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Find latest session for a specific CLI
|
|
59
|
+
findLatestSessionForCLI(cliType, basePath) {
|
|
60
|
+
const projectDirName = this.getProjectDirName(cliType);
|
|
61
|
+
|
|
62
|
+
// Different CLIs have different directory structures
|
|
63
|
+
let sessionPath = basePath;
|
|
64
|
+
|
|
65
|
+
if (['claude', 'iflow', 'qodercli', 'kode'].includes(cliType) && basePath.includes('projects')) {
|
|
66
|
+
sessionPath = path.join(basePath, projectDirName);
|
|
67
|
+
} else if (cliType === 'gemini' && basePath.includes('tmp')) {
|
|
68
|
+
// Gemini uses hash directories
|
|
69
|
+
try {
|
|
70
|
+
const hashDirs = fs.readdirSync(basePath);
|
|
71
|
+
for (const hashDir of hashDirs) {
|
|
72
|
+
const hashDirPath = path.join(basePath, hashDir);
|
|
73
|
+
const chatsPath = path.join(hashDirPath, 'chats');
|
|
74
|
+
if (fs.existsSync(chatsPath)) {
|
|
75
|
+
const session = this.findLatestSessionInDir(chatsPath, cliType, hashDir);
|
|
76
|
+
if (session) return session;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return null;
|
|
80
|
+
} catch (error) {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
} else if (cliType === 'qwen' && basePath.includes('projects')) {
|
|
84
|
+
// Qwen uses projects/<projectName>/chats
|
|
85
|
+
const chatsPath = path.join(basePath, projectDirName, 'chats');
|
|
86
|
+
if (fs.existsSync(chatsPath)) {
|
|
87
|
+
return this.findLatestSessionInDir(chatsPath, cliType, projectDirName);
|
|
88
|
+
}
|
|
89
|
+
return null;
|
|
90
|
+
} else if (cliType === 'codebuddy') {
|
|
91
|
+
// CodeBuddy uses projects/<projectName> or root
|
|
92
|
+
const projectsPath = path.join(basePath, 'projects');
|
|
93
|
+
if (fs.existsSync(projectsPath)) {
|
|
94
|
+
const projectPath = path.join(projectsPath, projectDirName);
|
|
95
|
+
if (fs.existsSync(projectPath)) {
|
|
96
|
+
const session = this.findLatestSessionInDir(projectPath, cliType, projectDirName);
|
|
97
|
+
if (session) return session;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return this.findLatestSessionInDir(basePath, cliType, 'root');
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (!fs.existsSync(sessionPath)) return null;
|
|
104
|
+
|
|
105
|
+
return this.findLatestSessionInDir(sessionPath, cliType, projectDirName);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Find latest session in a directory
|
|
109
|
+
findLatestSessionInDir(dirPath, cliType, context) {
|
|
110
|
+
try {
|
|
111
|
+
const files = fs.readdirSync(dirPath);
|
|
112
|
+
|
|
113
|
+
// Filter for session files only
|
|
114
|
+
const sessionFiles = files.filter(file => {
|
|
115
|
+
// CodeBuddy's user-state.json should be skipped
|
|
116
|
+
if (cliType === 'codebuddy' && file === 'user-state.json') {
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
// Codex's slash_commands.json should be skipped
|
|
120
|
+
if (cliType === 'codex' && file === 'slash_commands.json') {
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
return file.endsWith('.jsonl') || file.endsWith('.json') || file.endsWith('.session');
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
if (sessionFiles.length === 0) return null;
|
|
127
|
+
|
|
128
|
+
let latestFile = null;
|
|
129
|
+
let latestTime = new Date(0);
|
|
130
|
+
|
|
131
|
+
for (const file of sessionFiles) {
|
|
132
|
+
const filePath = path.join(dirPath, file);
|
|
133
|
+
try {
|
|
134
|
+
const stats = fs.statSync(filePath);
|
|
135
|
+
if (stats.mtime > latestTime) {
|
|
136
|
+
latestTime = stats.mtime;
|
|
137
|
+
latestFile = file;
|
|
138
|
+
}
|
|
139
|
+
} catch (error) {
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (!latestFile) return null;
|
|
145
|
+
|
|
146
|
+
return {
|
|
147
|
+
cliType,
|
|
148
|
+
file: latestFile,
|
|
149
|
+
path: path.join(dirPath, latestFile),
|
|
150
|
+
modified: latestTime,
|
|
151
|
+
context
|
|
152
|
+
};
|
|
153
|
+
} catch (error) {
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Read and parse full session content
|
|
159
|
+
readFullSession(sessionPath) {
|
|
160
|
+
try {
|
|
161
|
+
const content = fs.readFileSync(sessionPath, 'utf8');
|
|
162
|
+
|
|
163
|
+
if (sessionPath.endsWith('.jsonl')) {
|
|
164
|
+
const lines = content.trim().split('\n').filter(line => line.trim());
|
|
165
|
+
return lines.map(line => {
|
|
166
|
+
try {
|
|
167
|
+
return JSON.parse(line);
|
|
168
|
+
} catch (e) {
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
}).filter(msg => msg !== null);
|
|
172
|
+
} else {
|
|
173
|
+
return JSON.parse(content);
|
|
174
|
+
}
|
|
175
|
+
} catch (error) {
|
|
176
|
+
console.error(`Error reading session: ${error.message}`);
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Format full session for output
|
|
182
|
+
formatFullSession(session) {
|
|
183
|
+
const messages = this.readFullSession(session.path);
|
|
184
|
+
if (!messages) {
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Handle different message formats
|
|
189
|
+
const messageList = Array.isArray(messages) ? messages :
|
|
190
|
+
(messages.messages && Array.isArray(messages.messages)) ? messages.messages : [];
|
|
191
|
+
|
|
192
|
+
if (messageList.length === 0) {
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const output = [];
|
|
197
|
+
output.push('📋 最新会话恢复');
|
|
198
|
+
output.push('');
|
|
199
|
+
output.push(`🔧 来源: ${session.cliType.toUpperCase()}`);
|
|
200
|
+
output.push(`📅 最后修改: ${session.modified.toLocaleString()}`);
|
|
201
|
+
output.push(`📁 文件: ${session.file}`);
|
|
202
|
+
output.push('');
|
|
203
|
+
output.push('---');
|
|
204
|
+
output.push('');
|
|
205
|
+
output.push('📝 完整对话内容:');
|
|
206
|
+
output.push('');
|
|
207
|
+
|
|
208
|
+
// Extract and format all messages
|
|
209
|
+
messageList.forEach((msg, index) => {
|
|
210
|
+
const role = msg.type || msg.role || 'unknown';
|
|
211
|
+
const prefix = role === 'user' ? '👤 用户' : '🤖 助手';
|
|
212
|
+
const content = this.extractMessageContent(msg);
|
|
213
|
+
|
|
214
|
+
if (content && content.trim()) {
|
|
215
|
+
output.push(`${prefix}:`);
|
|
216
|
+
output.push(content);
|
|
217
|
+
output.push('');
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
return output.join('\n');
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Extract text content from a message
|
|
225
|
+
extractMessageContent(msg) {
|
|
226
|
+
if (msg.message && typeof msg.message === 'object') {
|
|
227
|
+
const content = msg.message.content || msg.message.text || '';
|
|
228
|
+
return this.extractTextFromContent(content);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const content = msg.content || msg.text || '';
|
|
232
|
+
return this.extractTextFromContent(content);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Extract text from content
|
|
236
|
+
extractTextFromContent(content) {
|
|
237
|
+
if (typeof content === 'string') {
|
|
238
|
+
return content;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (Array.isArray(content)) {
|
|
242
|
+
return content
|
|
243
|
+
.map(item => {
|
|
244
|
+
if (typeof item === 'string') return item;
|
|
245
|
+
if (item && typeof item === 'object') {
|
|
246
|
+
return item.text || item.content || '';
|
|
247
|
+
}
|
|
248
|
+
return '';
|
|
249
|
+
})
|
|
250
|
+
.filter(text => text && typeof text === 'string')
|
|
251
|
+
.join(' ');
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if (content && typeof content === 'object') {
|
|
255
|
+
return content.text || content.content || '';
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return '';
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Execute recovery
|
|
262
|
+
execute(options = {}) {
|
|
263
|
+
const {
|
|
264
|
+
fullRecovery = true, // Default: recover full session
|
|
265
|
+
listOnly = false, // List sessions without recovery
|
|
266
|
+
cliFilter = null // Filter by specific CLI
|
|
267
|
+
} = options;
|
|
268
|
+
|
|
269
|
+
if (listOnly) {
|
|
270
|
+
// Advanced mode: list all sessions
|
|
271
|
+
return this.listAllSessions(cliFilter);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Default mode: find and recover latest session
|
|
275
|
+
let session;
|
|
276
|
+
|
|
277
|
+
if (cliFilter) {
|
|
278
|
+
// Filter by specific CLI
|
|
279
|
+
const basePath = this.cliPaths[cliFilter.toLowerCase()];
|
|
280
|
+
if (basePath && fs.existsSync(basePath)) {
|
|
281
|
+
session = this.findLatestSessionForCLI(cliFilter.toLowerCase(), basePath);
|
|
282
|
+
}
|
|
283
|
+
} else {
|
|
284
|
+
// Find latest across all CLIs
|
|
285
|
+
session = this.findLatestSession();
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
if (!session) {
|
|
289
|
+
console.log('📭 未找到任何会话');
|
|
290
|
+
console.log('');
|
|
291
|
+
console.log(`💡 项目路径: ${this.projectPath}`);
|
|
292
|
+
if (cliFilter) {
|
|
293
|
+
console.log(`💡 指定CLI: ${cliFilter}`);
|
|
294
|
+
}
|
|
295
|
+
return 1;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
if (!fullRecovery) {
|
|
299
|
+
// Show summary only
|
|
300
|
+
this.showSessionSummary(session);
|
|
301
|
+
} else {
|
|
302
|
+
// Show full session
|
|
303
|
+
const formatted = this.formatFullSession(session);
|
|
304
|
+
if (formatted) {
|
|
305
|
+
console.log(formatted);
|
|
306
|
+
} else {
|
|
307
|
+
console.log('📭 无法解析会话内容');
|
|
308
|
+
return 1;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
return 0;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// Show session summary (advanced mode)
|
|
316
|
+
showSessionSummary(session) {
|
|
317
|
+
const output = [];
|
|
318
|
+
output.push('📋 会话信息');
|
|
319
|
+
output.push('');
|
|
320
|
+
output.push(`🔧 来源: ${session.cliType.toUpperCase()}`);
|
|
321
|
+
output.push(`📅 最后修改: ${session.modified.toLocaleString()}`);
|
|
322
|
+
output.push(`📁 文件: ${session.file}`);
|
|
323
|
+
output.push(`📂 路径: ${session.path}`);
|
|
324
|
+
if (session.context) {
|
|
325
|
+
output.push(`🔍 上下文: ${session.context}`);
|
|
326
|
+
}
|
|
327
|
+
console.log(output.join('\n'));
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// List all sessions (advanced mode)
|
|
331
|
+
listAllSessions(cliFilter = null) {
|
|
332
|
+
const sessions = [];
|
|
333
|
+
|
|
334
|
+
for (const [cliType, basePath] of Object.entries(this.cliPaths)) {
|
|
335
|
+
if (cliFilter && cliType.toLowerCase() !== cliFilter.toLowerCase()) continue;
|
|
336
|
+
|
|
337
|
+
if (!fs.existsSync(basePath)) continue;
|
|
338
|
+
|
|
339
|
+
const session = this.findLatestSessionForCLI(cliType, basePath);
|
|
340
|
+
if (session) {
|
|
341
|
+
sessions.push(session);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
if (sessions.length === 0) {
|
|
346
|
+
console.log('📭 未找到任何会话');
|
|
347
|
+
return 1;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// Sort by modification time (newest first)
|
|
351
|
+
sessions.sort((a, b) => b.modified - a.modified);
|
|
352
|
+
|
|
353
|
+
const output = [];
|
|
354
|
+
output.push('📋 所有会话列表');
|
|
355
|
+
output.push('');
|
|
356
|
+
output.push(`📊 共找到 ${sessions.length} 个会话`);
|
|
357
|
+
output.push('');
|
|
358
|
+
|
|
359
|
+
sessions.forEach((session, index) => {
|
|
360
|
+
output.push(`${index + 1}. ${session.cliType.toUpperCase()}`);
|
|
361
|
+
output.push(` 📅 ${session.modified.toLocaleString()}`);
|
|
362
|
+
output.push(` 📁 ${session.file}`);
|
|
363
|
+
output.push('');
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
console.log(output.join('\n'));
|
|
367
|
+
return 0;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// Run as CLI command
|
|
372
|
+
if (require.main === module) {
|
|
373
|
+
const recovery = new UniversalSessionRecovery();
|
|
374
|
+
|
|
375
|
+
// Parse command line options
|
|
376
|
+
const options = {};
|
|
377
|
+
const args = process.argv.slice(2);
|
|
378
|
+
|
|
379
|
+
for (let i = 0; i < args.length; i++) {
|
|
380
|
+
const arg = args[i];
|
|
381
|
+
|
|
382
|
+
if (arg === '--list' || arg === '-l') {
|
|
383
|
+
options.listOnly = true;
|
|
384
|
+
} else if (arg === '--summary' || arg === '-s') {
|
|
385
|
+
options.fullRecovery = false;
|
|
386
|
+
} else if (arg === '--cli' && i + 1 < args.length) {
|
|
387
|
+
options.cliFilter = args[++i];
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
process.exit(recovery.execute(options));
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
module.exports = UniversalSessionRecovery;
|