stigmergy 1.3.19-beta.0 ā 1.3.21-beta.0
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/config/builtin-skills.json +43 -0
- package/package.json +1 -1
- package/src/cli/commands/autoinstall.js +4 -191
- package/src/cli/commands/install.js +30 -4
- package/src/cli/commands/project.js +12 -5
- package/src/cli/commands/stigmergy-resume.js +708 -0
- package/src/cli/router-beta.js +9 -2
- package/src/core/cli_tools.js +3 -3
- package/src/core/skills/BuiltinSkillsDeployer.js +154 -0
- package/src/cli/commands/resume.js +0 -134
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "1.0.0",
|
|
3
|
+
"description": "Stigmergy built-in skills configuration",
|
|
4
|
+
"skills": [
|
|
5
|
+
{
|
|
6
|
+
"name": "resumesession",
|
|
7
|
+
"displayName": "ResumeSession",
|
|
8
|
+
"description": "Cross-CLI session recovery and history management",
|
|
9
|
+
"version": "1.0.0",
|
|
10
|
+
"author": "stigmergy",
|
|
11
|
+
"type": "builtin",
|
|
12
|
+
"category": "session-management",
|
|
13
|
+
"entryPoint": "src/cli/commands/stigmergy-resume.js",
|
|
14
|
+
"agentskills": {
|
|
15
|
+
"name": "resumesession",
|
|
16
|
+
"description": "Cross-CLI session recovery and history management",
|
|
17
|
+
"version": "1.0.0",
|
|
18
|
+
"author": "stigmergy"
|
|
19
|
+
},
|
|
20
|
+
"deployment": {
|
|
21
|
+
"autoDeploy": true,
|
|
22
|
+
"targetCLIs": ["claude", "codex", "iflow", "qwen", "qodercli", "codebuddy"],
|
|
23
|
+
"files": [
|
|
24
|
+
{
|
|
25
|
+
"source": ".claude/skills/resumesession/SKILL.md",
|
|
26
|
+
"destination": "skills/resumesession/SKILL.md"
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"source": ".claude/skills/resumesession/__init__.py",
|
|
30
|
+
"destination": "skills/resumesession/__init__.py"
|
|
31
|
+
}
|
|
32
|
+
]
|
|
33
|
+
},
|
|
34
|
+
"commands": {
|
|
35
|
+
"stigmergy": {
|
|
36
|
+
"name": "resume",
|
|
37
|
+
"description": "Resume session - Cross-CLI session recovery and history management",
|
|
38
|
+
"handler": "src/cli/commands/stigmergy-resume.js"
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
]
|
|
43
|
+
}
|
package/package.json
CHANGED
|
@@ -1,191 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
const chalk = require('chalk');
|
|
7
|
-
const { handleInstallCommand } = require('./install');
|
|
8
|
-
const { handleDeployCommand } = require('./project');
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Handle auto-install command - Automated installation for npm postinstall
|
|
12
|
-
* @param {Object} options - Command options
|
|
13
|
-
*/
|
|
14
|
-
async function handleAutoInstallCommand(options = {}) {
|
|
15
|
-
try {
|
|
16
|
-
// Detect npm environment for better output visibility
|
|
17
|
-
const isNpmPostinstall = process.env.npm_lifecycle_event === 'postinstall';
|
|
18
|
-
|
|
19
|
-
// Use stderr for critical messages in npm environment (more likely to be shown)
|
|
20
|
-
const criticalLog = isNpmPostinstall ? console.error : console.log;
|
|
21
|
-
|
|
22
|
-
criticalLog(chalk.cyan('š STIGMERGY CLI AUTO-INSTALL STARTING'));
|
|
23
|
-
criticalLog('='.repeat(60));
|
|
24
|
-
criticalLog('Installing CLI tools and deploying hooks (including ResumeSession)...');
|
|
25
|
-
criticalLog('='.repeat(60));
|
|
26
|
-
|
|
27
|
-
console.log(chalk.blue('[AUTO-INSTALL] Stigmergy CLI automated setup'));
|
|
28
|
-
console.log('='.repeat(60));
|
|
29
|
-
|
|
30
|
-
// Check if we're in npm postinstall environment
|
|
31
|
-
if (isNpmPostinstall) {
|
|
32
|
-
console.log(chalk.yellow('š¦ Detected npm postinstall environment'));
|
|
33
|
-
console.log(chalk.gray('Setting up CLI integrations and hooks automatically...'));
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// Auto-install options - non-interactive mode
|
|
37
|
-
const autoInstallOptions = {
|
|
38
|
-
cli: 'all', // Install all available CLI tools
|
|
39
|
-
verbose: options.verbose || process.env.DEBUG === 'true',
|
|
40
|
-
force: options.force || false,
|
|
41
|
-
nonInteractive: true, // Critical for automated installation
|
|
42
|
-
autoDetect: true, // Auto-detect available tools
|
|
43
|
-
skipPermissionCheck: process.env.STIGMERGY_SKIP_PERMISSION_CHECK === 'true'
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
console.log(chalk.blue('š Scanning for available CLI tools...'));
|
|
47
|
-
|
|
48
|
-
// Step 1: Install CLI tools
|
|
49
|
-
console.log(chalk.blue('š ļø Step 1/2: Installing CLI tools...'));
|
|
50
|
-
|
|
51
|
-
const installResult = await handleInstallCommand(autoInstallOptions);
|
|
52
|
-
|
|
53
|
-
if (installResult.success) {
|
|
54
|
-
console.log(chalk.green('\nā
CLI tools installed successfully!'));
|
|
55
|
-
|
|
56
|
-
if (isNpmPostinstall) {
|
|
57
|
-
criticalLog(chalk.green('ā
CLI TOOLS INSTALLED'));
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// Show summary of what was installed
|
|
61
|
-
if (installResult.installed && installResult.installed.length > 0) {
|
|
62
|
-
console.log(chalk.blue('\nš Installation Summary:'));
|
|
63
|
-
installResult.installed.forEach(tool => {
|
|
64
|
-
console.log(` ${chalk.green('ā
')} ${tool}`);
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
if (installResult.failed && installResult.failed.length > 0) {
|
|
69
|
-
console.log(chalk.blue('\nā Failed Tools:'));
|
|
70
|
-
installResult.failed.forEach(tool => {
|
|
71
|
-
console.log(` ${chalk.red('ā')} ${tool} (installation failed)`);
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
if (installResult.existing && installResult.existing.length > 0) {
|
|
76
|
-
console.log(chalk.blue('\nš§ Already Available:'));
|
|
77
|
-
installResult.existing.forEach(tool => {
|
|
78
|
-
console.log(` ${chalk.yellow('ā ļø')} ${tool} (already installed)`);
|
|
79
|
-
});
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// Step 2: Deploy hooks and ResumeSession
|
|
83
|
-
console.log(chalk.blue('\nš Step 2/2: Deploying hooks and ResumeSession integration...'));
|
|
84
|
-
|
|
85
|
-
try {
|
|
86
|
-
const deployResult = await handleDeployCommand({
|
|
87
|
-
verbose: options.verbose || process.env.DEBUG === 'true',
|
|
88
|
-
force: options.force || false
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
if (deployResult.success) {
|
|
92
|
-
console.log(chalk.green('\nā
Hooks and ResumeSession deployed successfully!'));
|
|
93
|
-
|
|
94
|
-
if (isNpmPostinstall) {
|
|
95
|
-
criticalLog(chalk.green('ā
HOOKS AND RESUMESESSION DEPLOYED'));
|
|
96
|
-
criticalLog(chalk.green('ā
/stigmergy-resume command is now available in all CLIs'));
|
|
97
|
-
}
|
|
98
|
-
} else {
|
|
99
|
-
console.log(chalk.yellow('\nā ļø Hook deployment encountered issues'));
|
|
100
|
-
console.log(chalk.yellow('Run: stigmergy deploy --verbose for details'));
|
|
101
|
-
}
|
|
102
|
-
} catch (deployError) {
|
|
103
|
-
console.log(chalk.red(`\nā Hook deployment failed: ${deployError.message}`));
|
|
104
|
-
console.log(chalk.yellow('Run: stigmergy deploy manually to complete setup'));
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
} else {
|
|
108
|
-
console.log(chalk.red('\nā Auto-install encountered issues'));
|
|
109
|
-
|
|
110
|
-
if (isNpmPostinstall) {
|
|
111
|
-
criticalLog(chalk.red('ā STIGMERGY CLI SETUP INCOMPLETE'));
|
|
112
|
-
criticalLog(chalk.yellow('Run: stigmergy install && stigmergy deploy to complete setup manually'));
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
if (installResult.error) {
|
|
116
|
-
console.log(chalk.red(`Error: ${installResult.error}`));
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// Setup verification
|
|
121
|
-
console.log(chalk.blue('\nš Verifying installation...'));
|
|
122
|
-
|
|
123
|
-
// Quick verification of core functionality
|
|
124
|
-
const verificationChecks = [
|
|
125
|
-
{ name: 'Core modules accessible', check: () => require('../utils/formatters') && require('../utils/environment') },
|
|
126
|
-
{ name: 'Error handler available', check: () => require('../../core/error_handler') },
|
|
127
|
-
{ name: 'Smart router available', check: () => require('../../core/smart_router') },
|
|
128
|
-
{ name: 'Hook deployment manager', check: () => require('../../core/coordination/nodejs/HookDeploymentManager') },
|
|
129
|
-
{ name: 'ResumeSession generator', check: () => require('../../core/coordination/nodejs/generators/ResumeSessionGenerator') }
|
|
130
|
-
];
|
|
131
|
-
|
|
132
|
-
let verificationPassed = 0;
|
|
133
|
-
for (const check of verificationChecks) {
|
|
134
|
-
try {
|
|
135
|
-
check.check();
|
|
136
|
-
console.log(` ${chalk.green('ā
')} ${check.name}`);
|
|
137
|
-
verificationPassed++;
|
|
138
|
-
} catch (error) {
|
|
139
|
-
console.log(` ${chalk.red('ā')} ${check.name}`);
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
if (verificationPassed === verificationChecks.length) {
|
|
144
|
-
console.log(chalk.green('\nā
All verification checks passed!'));
|
|
145
|
-
|
|
146
|
-
if (isNpmPostinstall) {
|
|
147
|
-
criticalLog(chalk.green('š STIGMERGY CLI IS READY TO USE!'));
|
|
148
|
-
criticalLog(chalk.green('šÆ All CLI tools configured with ResumeSession support'));
|
|
149
|
-
}
|
|
150
|
-
} else {
|
|
151
|
-
console.log(chalk.yellow('\nā ļø Some verification checks failed'));
|
|
152
|
-
console.log(chalk.yellow('Run: stigmergy diagnostic for full system check'));
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
// Final summary for npm environment
|
|
156
|
-
if (isNpmPostinstall) {
|
|
157
|
-
criticalLog('='.repeat(60));
|
|
158
|
-
criticalLog(chalk.cyan('šÆ STIGMERGY CLI AUTO-INSTALL FINISHED'));
|
|
159
|
-
criticalLog('='.repeat(60));
|
|
160
|
-
criticalLog(chalk.green('ā
All CLI tools installed and configured'));
|
|
161
|
-
criticalLog(chalk.green('ā
Hooks deployed for all CLIs'));
|
|
162
|
-
criticalLog(chalk.green('ā
ResumeSession integration enabled'));
|
|
163
|
-
criticalLog(chalk.cyan('š Usage: /stigmergy-resume in any CLI'));
|
|
164
|
-
criticalLog('='.repeat(60));
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
return {
|
|
168
|
-
success: installResult.success,
|
|
169
|
-
verificationPassed,
|
|
170
|
-
installed: installResult.installed || [],
|
|
171
|
-
existing: installResult.existing || []
|
|
172
|
-
};
|
|
173
|
-
|
|
174
|
-
} catch (error) {
|
|
175
|
-
const isNpmPostinstall = process.env.npm_lifecycle_event === 'postinstall';
|
|
176
|
-
const criticalLog = isNpmPostinstall ? console.error : console.log;
|
|
177
|
-
|
|
178
|
-
console.error(chalk.red('[ERROR] Auto-install failed:'), error.message);
|
|
179
|
-
|
|
180
|
-
if (isNpmPostinstall) {
|
|
181
|
-
criticalLog(chalk.red('š„ AUTO-INSTALL FAILED'));
|
|
182
|
-
criticalLog(chalk.yellow('Run: stigmergy install --verbose && stigmergy deploy --verbose'));
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
return { success: false, error: error.message };
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
module.exports = {
|
|
190
|
-
handleAutoInstallCommand
|
|
191
|
-
};
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { handleInstallCommand } from './install.js';
|
|
3
|
+
import { handleDeployCommand } from './project.js';
|
|
4
|
+
import BuiltinSkillsDeployer from '../core/skills/BuiltinSkillsDeployer.js';
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
const StigmergyInstaller = require('../../core/installer');
|
|
7
7
|
const chalk = require('chalk');
|
|
8
8
|
const { ensureSkillsCache } = require('../utils/skills_cache');
|
|
9
|
+
const { handleDeployCommand } = require('./project');
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* Handle install command
|
|
@@ -30,14 +31,21 @@ async function handleInstallCommand(options = {}) {
|
|
|
30
31
|
// Scan for available and missing tools
|
|
31
32
|
const { missing: missingTools, available: availableTools } = await installer.scanCLI();
|
|
32
33
|
|
|
33
|
-
// Filter to only install tools with autoInstall: true
|
|
34
|
-
|
|
35
|
-
|
|
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
|
+
}
|
|
36
44
|
|
|
37
45
|
const filteredMissingTools = Object.fromEntries(toolsToInstall);
|
|
38
46
|
|
|
39
47
|
if (Object.keys(filteredMissingTools).length === 0) {
|
|
40
|
-
console.log(chalk.green('ā
All
|
|
48
|
+
console.log(chalk.green('ā
All CLI tools are already installed!'));
|
|
41
49
|
return {
|
|
42
50
|
success: true,
|
|
43
51
|
installed: [],
|
|
@@ -53,6 +61,24 @@ async function handleInstallCommand(options = {}) {
|
|
|
53
61
|
|
|
54
62
|
if (installResult.success) {
|
|
55
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
|
+
|
|
56
82
|
return {
|
|
57
83
|
success: true,
|
|
58
84
|
installed: installResult.installed || [],
|
|
@@ -104,13 +104,20 @@ async function handleDeployCommand(options = {}) {
|
|
|
104
104
|
const installer = new StigmergyInstaller({ verbose: options.verbose });
|
|
105
105
|
const { available: deployedTools } = await installer.scanCLI();
|
|
106
106
|
|
|
107
|
-
//
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
107
|
+
// å¦ę options.all äøŗ trueļ¼éØē½²ęęå·„å
·ļ¼å¦ååŖéØē½² autoInstall: true ēå·„å
·
|
|
108
|
+
let filteredDeployedTools;
|
|
109
|
+
if (options.all) {
|
|
110
|
+
console.log(chalk.blue('[INFO] Deploying hooks for ALL available tools (--all mode)'));
|
|
111
|
+
filteredDeployedTools = deployedTools;
|
|
112
|
+
} else {
|
|
113
|
+
console.log(chalk.blue('[INFO] Deploying hooks for auto-install tools only'));
|
|
114
|
+
const toolsToDeploy = Object.entries(deployedTools)
|
|
115
|
+
.filter(([toolName]) => installer.router.tools[toolName]?.autoInstall === true);
|
|
116
|
+
filteredDeployedTools = Object.fromEntries(toolsToDeploy);
|
|
117
|
+
}
|
|
111
118
|
|
|
112
119
|
if (Object.keys(filteredDeployedTools).length === 0) {
|
|
113
|
-
console.log(chalk.yellow('[INFO] No
|
|
120
|
+
console.log(chalk.yellow('[INFO] No CLI tools found for deployment'));
|
|
114
121
|
console.log(chalk.blue('š” Run: stigmergy install to install CLI tools first'));
|
|
115
122
|
return { success: true, deployed: 0 };
|
|
116
123
|
}
|
|
@@ -0,0 +1,708 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* ResumeSession Command
|
|
4
|
+
* Cross-CLI session recovery and history management
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const os = require('os');
|
|
10
|
+
|
|
11
|
+
class ResumeSessionCommand {
|
|
12
|
+
constructor() {
|
|
13
|
+
this.projectPath = process.cwd();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Get all CLI session paths (with dynamic detection)
|
|
17
|
+
getAllCLISessionPaths() {
|
|
18
|
+
const homeDir = os.homedir();
|
|
19
|
+
const paths = {};
|
|
20
|
+
|
|
21
|
+
// Detect Claude CLI session paths
|
|
22
|
+
paths.claude = this.detectCLISessionPaths([
|
|
23
|
+
path.join(homeDir, '.claude', 'projects'),
|
|
24
|
+
path.join(homeDir, '.claude', 'sessions'),
|
|
25
|
+
path.join(homeDir, '.claude', 'history')
|
|
26
|
+
]);
|
|
27
|
+
|
|
28
|
+
// Detect Gemini CLI session paths
|
|
29
|
+
paths.gemini = this.detectCLISessionPaths([
|
|
30
|
+
path.join(homeDir, '.config', 'gemini', 'tmp'),
|
|
31
|
+
path.join(homeDir, '.gemini', 'sessions'),
|
|
32
|
+
path.join(homeDir, '.gemini', 'history')
|
|
33
|
+
]);
|
|
34
|
+
|
|
35
|
+
// Detect Qwen CLI session paths
|
|
36
|
+
paths.qwen = this.detectCLISessionPaths([
|
|
37
|
+
path.join(homeDir, '.qwen', 'projects'),
|
|
38
|
+
path.join(homeDir, '.qwen', 'sessions'),
|
|
39
|
+
path.join(homeDir, '.qwen', 'history')
|
|
40
|
+
]);
|
|
41
|
+
|
|
42
|
+
// Detect IFlow CLI session paths
|
|
43
|
+
paths.iflow = this.detectCLISessionPaths([
|
|
44
|
+
path.join(homeDir, '.iflow', 'projects'),
|
|
45
|
+
path.join(homeDir, '.iflow', 'sessions'),
|
|
46
|
+
path.join(homeDir, '.iflow', 'history')
|
|
47
|
+
]);
|
|
48
|
+
|
|
49
|
+
// Detect QoderCLI session paths
|
|
50
|
+
paths.qodercli = this.detectCLISessionPaths([
|
|
51
|
+
path.join(homeDir, '.qoder', 'projects'),
|
|
52
|
+
path.join(homeDir, '.qoder', 'sessions')
|
|
53
|
+
]);
|
|
54
|
+
|
|
55
|
+
// Detect CodeBuddy session paths
|
|
56
|
+
paths.codebuddy = this.detectCLISessionPaths([
|
|
57
|
+
path.join(homeDir, '.codebuddy'),
|
|
58
|
+
path.join(homeDir, '.codebuddy', 'sessions'),
|
|
59
|
+
path.join(homeDir, '.codebuddy', 'history')
|
|
60
|
+
]);
|
|
61
|
+
|
|
62
|
+
// Detect Codex session paths
|
|
63
|
+
paths.codex = this.detectCLISessionPaths([
|
|
64
|
+
path.join(homeDir, '.config', 'codex'),
|
|
65
|
+
path.join(homeDir, '.codex', 'sessions'),
|
|
66
|
+
path.join(homeDir, '.codex', 'history')
|
|
67
|
+
]);
|
|
68
|
+
|
|
69
|
+
// Detect Kode session paths
|
|
70
|
+
paths.kode = this.detectCLISessionPaths([
|
|
71
|
+
path.join(homeDir, '.kode', 'projects'),
|
|
72
|
+
path.join(homeDir, '.kode', 'sessions')
|
|
73
|
+
]);
|
|
74
|
+
|
|
75
|
+
return paths;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Detect which session paths actually exist and contain session files
|
|
79
|
+
detectCLISessionPaths(candidatePaths) {
|
|
80
|
+
const validPaths = [];
|
|
81
|
+
|
|
82
|
+
for (const candidatePath of candidatePaths) {
|
|
83
|
+
if (!fs.existsSync(candidatePath)) {
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Check if this directory contains session files
|
|
88
|
+
const hasSessionFiles = this.hasSessionFiles(candidatePath);
|
|
89
|
+
if (hasSessionFiles) {
|
|
90
|
+
validPaths.push(candidatePath);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return validPaths.length > 0 ? validPaths : candidatePaths.filter(p => fs.existsSync(p));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Check if a directory contains session files
|
|
98
|
+
hasSessionFiles(dirPath) {
|
|
99
|
+
try {
|
|
100
|
+
const files = fs.readdirSync(dirPath);
|
|
101
|
+
return files.some(file =>
|
|
102
|
+
file.endsWith('.jsonl') ||
|
|
103
|
+
file.endsWith('.json') ||
|
|
104
|
+
file.endsWith('.session')
|
|
105
|
+
);
|
|
106
|
+
} catch (error) {
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Scan sessions for a specific CLI
|
|
112
|
+
scanSessions(cliType, sessionsPath, projectPath) {
|
|
113
|
+
const sessions = [];
|
|
114
|
+
if (!sessionsPath || !projectPath) return sessions;
|
|
115
|
+
|
|
116
|
+
try {
|
|
117
|
+
if (!fs.existsSync(sessionsPath)) return sessions;
|
|
118
|
+
|
|
119
|
+
// For IFlow, Claude, QoderCLI, Kode: scan projects subdirectories (one level)
|
|
120
|
+
if ((cliType === 'iflow' || cliType === 'claude' || cliType === 'qodercli' || cliType === 'kode') && sessionsPath.includes('projects')) {
|
|
121
|
+
const subdirs = fs.readdirSync(sessionsPath);
|
|
122
|
+
for (const subdir of subdirs) {
|
|
123
|
+
const subdirPath = path.join(sessionsPath, subdir);
|
|
124
|
+
try {
|
|
125
|
+
const stat = fs.statSync(subdirPath);
|
|
126
|
+
if (stat.isDirectory()) {
|
|
127
|
+
sessions.push(...this.scanSessionFiles(cliType, subdirPath, projectPath));
|
|
128
|
+
}
|
|
129
|
+
} catch (error) {
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return sessions;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// For Gemini: scan tmp/<hash>/chats subdirectories (multiple levels)
|
|
137
|
+
if (cliType === 'gemini' && sessionsPath.includes('tmp')) {
|
|
138
|
+
const hashDirs = fs.readdirSync(sessionsPath);
|
|
139
|
+
for (const hashDir of hashDirs) {
|
|
140
|
+
const hashDirPath = path.join(sessionsPath, hashDir);
|
|
141
|
+
try {
|
|
142
|
+
const stat = fs.statSync(hashDirPath);
|
|
143
|
+
if (stat.isDirectory()) {
|
|
144
|
+
const chatsPath = path.join(hashDirPath, 'chats');
|
|
145
|
+
if (fs.existsSync(chatsPath)) {
|
|
146
|
+
sessions.push(...this.scanSessionFiles(cliType, chatsPath, projectPath));
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
} catch (error) {
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return sessions;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// For Qwen: scan projects/<projectName>/chats subdirectories (two levels)
|
|
157
|
+
if (cliType === 'qwen' && sessionsPath.includes('projects')) {
|
|
158
|
+
const projectDirs = fs.readdirSync(sessionsPath);
|
|
159
|
+
for (const projectDir of projectDirs) {
|
|
160
|
+
const projectDirPath = path.join(sessionsPath, projectDir);
|
|
161
|
+
try {
|
|
162
|
+
const stat = fs.statSync(projectDirPath);
|
|
163
|
+
if (stat.isDirectory()) {
|
|
164
|
+
const chatsPath = path.join(projectDirPath, 'chats');
|
|
165
|
+
if (fs.existsSync(chatsPath)) {
|
|
166
|
+
sessions.push(...this.scanSessionFiles(cliType, chatsPath, projectPath));
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
} catch (error) {
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return sessions;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// For CodeBuddy: scan both projects subdirectories and root history.jsonl
|
|
177
|
+
if (cliType === 'codebuddy') {
|
|
178
|
+
const projectsPath = path.join(sessionsPath, 'projects');
|
|
179
|
+
if (fs.existsSync(projectsPath)) {
|
|
180
|
+
const projectDirs = fs.readdirSync(projectsPath);
|
|
181
|
+
for (const projectDir of projectDirs) {
|
|
182
|
+
const projectDirPath = path.join(projectsPath, projectDir);
|
|
183
|
+
try {
|
|
184
|
+
const stat = fs.statSync(projectDirPath);
|
|
185
|
+
if (stat.isDirectory()) {
|
|
186
|
+
sessions.push(...this.scanSessionFiles(cliType, projectDirPath, projectPath));
|
|
187
|
+
}
|
|
188
|
+
} catch (error) {
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
sessions.push(...this.scanSessionFiles(cliType, sessionsPath, projectPath));
|
|
194
|
+
return sessions;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return this.scanSessionFiles(cliType, sessionsPath, projectPath);
|
|
198
|
+
} catch (error) {
|
|
199
|
+
console.warn(`Warning: Could not scan ${cliType} sessions:`, error.message);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return sessions;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Scan session files in a directory
|
|
206
|
+
scanSessionFiles(cliType, sessionsPath, projectPath) {
|
|
207
|
+
const sessions = [];
|
|
208
|
+
try {
|
|
209
|
+
const files = fs.readdirSync(sessionsPath);
|
|
210
|
+
for (const file of files) {
|
|
211
|
+
if (file.endsWith('.json') || file.endsWith('.session') || file.endsWith('.jsonl')) {
|
|
212
|
+
try {
|
|
213
|
+
const filePath = path.join(sessionsPath, file);
|
|
214
|
+
let sessionData;
|
|
215
|
+
|
|
216
|
+
if (file.endsWith('.jsonl')) {
|
|
217
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
218
|
+
const lines = content.trim().split('\n').filter(line => line.trim());
|
|
219
|
+
const messages = lines.map(line => JSON.parse(line));
|
|
220
|
+
|
|
221
|
+
if (messages.length === 0) continue;
|
|
222
|
+
sessionData = this.parseJSONLSession(messages, file);
|
|
223
|
+
} else {
|
|
224
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
225
|
+
sessionData = JSON.parse(content);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (this.isProjectSession(sessionData, projectPath)) {
|
|
229
|
+
sessions.push({
|
|
230
|
+
cliType,
|
|
231
|
+
sessionId: sessionData.id || sessionData.sessionId || file.replace(/\.(json|session|jsonl)$/, ''),
|
|
232
|
+
title: sessionData.title || sessionData.topic || 'Untitled',
|
|
233
|
+
content: this.extractContent(sessionData),
|
|
234
|
+
updatedAt: new Date(sessionData.updatedAt || sessionData.timestamp || fs.statSync(filePath).mtime),
|
|
235
|
+
messageCount: sessionData.messageCount || this.countMessages(sessionData),
|
|
236
|
+
projectPath
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
} catch (error) {
|
|
240
|
+
console.warn(`Warning: Could not parse ${file}:`, error.message);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
} catch (error) {
|
|
245
|
+
console.warn('Warning: Could not scan files:', error.message);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
return sessions;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Parse JSONL session format
|
|
252
|
+
parseJSONLSession(messages, filename) {
|
|
253
|
+
const firstMsg = messages[0];
|
|
254
|
+
const lastMsg = messages[messages.length - 1];
|
|
255
|
+
const userMessages = messages.filter(m => m.type === 'user' || m.role === 'user');
|
|
256
|
+
|
|
257
|
+
let title = 'Untitled Session';
|
|
258
|
+
if (userMessages.length > 0) {
|
|
259
|
+
const firstUserMsg = userMessages[0];
|
|
260
|
+
let content = firstUserMsg.message?.content || firstUserMsg.content || '';
|
|
261
|
+
if (typeof content === 'object') {
|
|
262
|
+
content = JSON.stringify(content);
|
|
263
|
+
}
|
|
264
|
+
if (typeof content === 'string' && content.trim()) {
|
|
265
|
+
title = content.substring(0, 100) || title;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const contentParts = messages
|
|
270
|
+
.map(m => {
|
|
271
|
+
if (m.message && typeof m.message === 'object') {
|
|
272
|
+
return m.message.content || m.message.text || '';
|
|
273
|
+
}
|
|
274
|
+
return m.content || m.text || '';
|
|
275
|
+
})
|
|
276
|
+
.filter(text => text && typeof text === 'string' && text.trim());
|
|
277
|
+
|
|
278
|
+
return {
|
|
279
|
+
sessionId: firstMsg.sessionId || filename.replace('.jsonl', ''),
|
|
280
|
+
title: title,
|
|
281
|
+
content: contentParts.join(' '),
|
|
282
|
+
timestamp: lastMsg.timestamp || new Date().toISOString(),
|
|
283
|
+
projectPath: firstMsg.cwd || firstMsg.workingDirectory,
|
|
284
|
+
messageCount: messages.filter(m => m.type === 'user' || m.type === 'assistant' || m.role === 'user' || m.role === 'assistant').length,
|
|
285
|
+
messages: messages
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// Scan all CLI sessions
|
|
290
|
+
scanAllCLISessions(projectPath) {
|
|
291
|
+
const allSessions = [];
|
|
292
|
+
const cliPathsMap = this.getAllCLISessionPaths();
|
|
293
|
+
|
|
294
|
+
for (const [cliType, sessionsPaths] of Object.entries(cliPathsMap)) {
|
|
295
|
+
for (const sessionsPath of sessionsPaths) {
|
|
296
|
+
const sessions = this.scanSessions(cliType, sessionsPath, projectPath);
|
|
297
|
+
allSessions.push(...sessions);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
return allSessions;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Check if session belongs to current project
|
|
305
|
+
isProjectSession(session, projectPath) {
|
|
306
|
+
const sessionProject = session.projectPath || session.workingDirectory || session.cwd;
|
|
307
|
+
if (!sessionProject) return true;
|
|
308
|
+
|
|
309
|
+
return sessionProject === projectPath ||
|
|
310
|
+
sessionProject.startsWith(projectPath) ||
|
|
311
|
+
projectPath.startsWith(sessionProject);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Extract content from session data
|
|
315
|
+
extractContent(sessionData) {
|
|
316
|
+
if (sessionData.content && typeof sessionData.content === 'string') {
|
|
317
|
+
return sessionData.content;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
if (sessionData.messages && Array.isArray(sessionData.messages)) {
|
|
321
|
+
return sessionData.messages
|
|
322
|
+
.map(msg => {
|
|
323
|
+
if (msg.message && typeof msg.message === 'object') {
|
|
324
|
+
const content = msg.message.content || msg.message.text || '';
|
|
325
|
+
return this.extractTextFromContent(content);
|
|
326
|
+
}
|
|
327
|
+
const content = msg.content || msg.text || '';
|
|
328
|
+
return this.extractTextFromContent(content);
|
|
329
|
+
})
|
|
330
|
+
.filter(text => text && typeof text === 'string' && text.trim())
|
|
331
|
+
.join(' ');
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
if (Array.isArray(sessionData)) {
|
|
335
|
+
return sessionData
|
|
336
|
+
.map(item => {
|
|
337
|
+
if (item.message && typeof item.message === 'object') {
|
|
338
|
+
const content = item.message.content || item.message.text || '';
|
|
339
|
+
return this.extractTextFromContent(content);
|
|
340
|
+
}
|
|
341
|
+
const content = item.content || item.text || '';
|
|
342
|
+
return this.extractTextFromContent(content);
|
|
343
|
+
})
|
|
344
|
+
.filter(text => text && typeof text === 'string' && text.trim())
|
|
345
|
+
.join(' ');
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
return '';
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// Extract text from content
|
|
352
|
+
extractTextFromContent(content) {
|
|
353
|
+
if (typeof content === 'string') {
|
|
354
|
+
return content;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
if (Array.isArray(content)) {
|
|
358
|
+
return content
|
|
359
|
+
.map(item => {
|
|
360
|
+
if (typeof item === 'string') return item;
|
|
361
|
+
if (item && typeof item === 'object') {
|
|
362
|
+
return item.text || item.content || '';
|
|
363
|
+
}
|
|
364
|
+
return '';
|
|
365
|
+
})
|
|
366
|
+
.filter(text => text && typeof text === 'string')
|
|
367
|
+
.join(' ');
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
if (content && typeof content === 'object') {
|
|
371
|
+
return content.text || content.content || JSON.stringify(content);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
return '';
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// Count messages in session
|
|
378
|
+
countMessages(sessionData) {
|
|
379
|
+
if (sessionData.messages) {
|
|
380
|
+
return Array.isArray(sessionData.messages) ? sessionData.messages.length : 0;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
if (Array.isArray(sessionData)) {
|
|
384
|
+
return sessionData.length;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
return 0;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// Filter sessions by CLI
|
|
391
|
+
filterByCLI(sessions, cliType) {
|
|
392
|
+
if (!cliType) return sessions;
|
|
393
|
+
return sessions.filter(session => session.cliType === cliType);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// Filter sessions by search term
|
|
397
|
+
filterBySearch(sessions, searchTerm) {
|
|
398
|
+
if (!searchTerm) return sessions;
|
|
399
|
+
|
|
400
|
+
const lowerSearch = searchTerm.toLowerCase();
|
|
401
|
+
return sessions.filter(session =>
|
|
402
|
+
session.title.toLowerCase().includes(lowerSearch) ||
|
|
403
|
+
session.content.toLowerCase().includes(lowerSearch)
|
|
404
|
+
);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// Filter sessions by date range
|
|
408
|
+
filterByDateRange(sessions, timeRange = 'all') {
|
|
409
|
+
if (timeRange === 'all') return sessions;
|
|
410
|
+
|
|
411
|
+
const now = new Date();
|
|
412
|
+
return sessions.filter(session => {
|
|
413
|
+
const sessionDate = new Date(session.updatedAt);
|
|
414
|
+
|
|
415
|
+
switch (timeRange) {
|
|
416
|
+
case 'today':
|
|
417
|
+
return sessionDate.toDateString() === now.toDateString();
|
|
418
|
+
case 'week':
|
|
419
|
+
const weekAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);
|
|
420
|
+
return sessionDate >= weekAgo;
|
|
421
|
+
case 'month':
|
|
422
|
+
const monthAgo = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000);
|
|
423
|
+
return sessionDate >= monthAgo;
|
|
424
|
+
default:
|
|
425
|
+
return true;
|
|
426
|
+
}
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// Sort sessions by date
|
|
431
|
+
sortByDate(sessions) {
|
|
432
|
+
return [...sessions].sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime());
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// Apply all filters
|
|
436
|
+
applyFilters(sessions, options, projectPath) {
|
|
437
|
+
let filteredSessions = [...sessions];
|
|
438
|
+
|
|
439
|
+
filteredSessions = filteredSessions.filter(session => this.isProjectSession(session, projectPath));
|
|
440
|
+
|
|
441
|
+
if (options.cli) {
|
|
442
|
+
filteredSessions = this.filterByCLI(filteredSessions, options.cli);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
if (options.search) {
|
|
446
|
+
filteredSessions = this.filterBySearch(filteredSessions, options.search);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
if (options.timeRange) {
|
|
450
|
+
filteredSessions = this.filterByDateRange(filteredSessions, options.timeRange);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
filteredSessions = this.sortByDate(filteredSessions);
|
|
454
|
+
|
|
455
|
+
if (options.limit && options.limit > 0) {
|
|
456
|
+
filteredSessions = filteredSessions.slice(0, options.limit);
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
return filteredSessions;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// Format sessions as summary
|
|
463
|
+
formatSummary(sessions) {
|
|
464
|
+
if (sessions.length === 0) {
|
|
465
|
+
return `š å½å锹ē®ęę åå²ä¼čÆ\n\nš” **ę示:** å°čÆ: stigmergy resume --search <å
³é®čÆ>`;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
let response = `š **锹ē®åå²ä¼čÆ**\n\nš å
±ę¾å° ${sessions.length} äøŖä¼čÆ\n\n`;
|
|
469
|
+
|
|
470
|
+
const byCLI = {};
|
|
471
|
+
sessions.forEach(session => {
|
|
472
|
+
if (!byCLI[session.cliType]) byCLI[session.cliType] = [];
|
|
473
|
+
byCLI[session.cliType].push(session);
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
Object.entries(byCLI).forEach(([cli, cliSessions]) => {
|
|
477
|
+
const icon = this.getCLIIcon(cli);
|
|
478
|
+
response += `${icon} **${cli.toUpperCase()}** (${cliSessions.length}äøŖ)\n`;
|
|
479
|
+
|
|
480
|
+
cliSessions.slice(0, 3).forEach((session, i) => {
|
|
481
|
+
const date = this.formatDate(session.updatedAt);
|
|
482
|
+
const title = session.title.substring(0, 50);
|
|
483
|
+
response += ` ${i + 1}. ${title}...\n`;
|
|
484
|
+
response += ` š
${date} ⢠š¬ ${session.messageCount}ę”ę¶ęÆ\n`;
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
if (cliSessions.length > 3) {
|
|
488
|
+
response += ` ... čæę ${cliSessions.length - 3} äøŖä¼čÆ\n`;
|
|
489
|
+
}
|
|
490
|
+
response += '\n';
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
response += `š” **使ēØę¹ę³:**\n`;
|
|
494
|
+
response += `⢠'stigmergy resume --cli <å·„å
·>' - ę„ēē¹å®CLI\n`;
|
|
495
|
+
response += `⢠'stigmergy resume --search <å
³é®čÆ>' - ęē“¢å
容\n`;
|
|
496
|
+
response += `⢠'stigmergy resume --format timeline' - ę¶é“ēŗæč§å¾`;
|
|
497
|
+
|
|
498
|
+
return response;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// Format sessions as timeline
|
|
502
|
+
formatTimeline(sessions) {
|
|
503
|
+
if (sessions.length === 0) {
|
|
504
|
+
return 'š ęę ä¼čÆę¶é“ēŗæć';
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
let response = `ā° **ę¶é“ēŗæč§å¾**\n\n`;
|
|
508
|
+
|
|
509
|
+
sessions.forEach((session, index) => {
|
|
510
|
+
const date = this.formatDate(session.updatedAt);
|
|
511
|
+
const cliIcon = this.getCLIIcon(session.cliType);
|
|
512
|
+
|
|
513
|
+
response += `${index + 1}. ${cliIcon} ${session.title}\n`;
|
|
514
|
+
response += ` š
${date} ⢠š¬ ${session.messageCount}ę”ę¶ęÆ\n`;
|
|
515
|
+
response += ` š ${session.cliType}:${session.sessionId}\n\n`;
|
|
516
|
+
});
|
|
517
|
+
|
|
518
|
+
return response;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
// Format sessions as detailed
|
|
522
|
+
formatDetailed(sessions) {
|
|
523
|
+
if (sessions.length === 0) {
|
|
524
|
+
return 'š ęę 详ē»ä¼čÆäæ”ęÆć';
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
let response = `š **详ē»č§å¾**\n\n`;
|
|
528
|
+
|
|
529
|
+
sessions.forEach((session, index) => {
|
|
530
|
+
const cliIcon = this.getCLIIcon(session.cliType);
|
|
531
|
+
const date = session.updatedAt.toLocaleString();
|
|
532
|
+
|
|
533
|
+
response += `${index + 1}. ${cliIcon} **${session.title}**\n`;
|
|
534
|
+
response += ` š
${date}\n`;
|
|
535
|
+
response += ` š§ CLI: ${session.cliType}\n`;
|
|
536
|
+
response += ` š¬ ę¶ęÆę°: ${session.messageCount}\n`;
|
|
537
|
+
response += ` š ä¼čÆID: '${session.sessionId}'\n\n`;
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
return response;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
// Format session context
|
|
544
|
+
formatContext(session) {
|
|
545
|
+
if (!session) {
|
|
546
|
+
return `š ęę åÆę¢å¤ēäøäøęć`;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
let response = `š **äøäøęę¢å¤**\n\n`;
|
|
550
|
+
response += `š
ä¼čÆę¶é“: ${session.updatedAt.toLocaleString()}\n`;
|
|
551
|
+
response += `š§ ę„ęŗCLI: ${session.cliType}\n`;
|
|
552
|
+
response += `š¬ ę¶ęÆę°: ${session.messageCount}\n`;
|
|
553
|
+
response += `š ä¼čÆID: ${session.sessionId}\n\n`;
|
|
554
|
+
response += `---\n\n`;
|
|
555
|
+
response += `**äøę¬”讨论å
容:**\n`;
|
|
556
|
+
response += session.content.substring(0, 500);
|
|
557
|
+
if (session.content.length > 500) {
|
|
558
|
+
response += `...`;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
return response;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
// Get CLI icon
|
|
565
|
+
getCLIIcon(cliType) {
|
|
566
|
+
const icons = {
|
|
567
|
+
'claude': 'š¢',
|
|
568
|
+
'gemini': 'šµ',
|
|
569
|
+
'qwen': 'š”',
|
|
570
|
+
'iflow': 'š“',
|
|
571
|
+
'codebuddy': 'š£',
|
|
572
|
+
'codex': 'šŖ',
|
|
573
|
+
'qodercli': 'š ',
|
|
574
|
+
'kode': 'ā”'
|
|
575
|
+
};
|
|
576
|
+
return icons[cliType] || 'š¹';
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
// Format date
|
|
580
|
+
formatDate(date) {
|
|
581
|
+
const now = new Date();
|
|
582
|
+
const diff = now.getTime() - date.getTime();
|
|
583
|
+
const days = Math.floor(diff / (24 * 60 * 60 * 1000));
|
|
584
|
+
|
|
585
|
+
if (days === 0) {
|
|
586
|
+
return date.toLocaleTimeString();
|
|
587
|
+
} else if (days === 1) {
|
|
588
|
+
return 'ęØå¤©';
|
|
589
|
+
} else if (days < 7) {
|
|
590
|
+
return `${days}天å`;
|
|
591
|
+
} else if (days < 30) {
|
|
592
|
+
return `${Math.floor(days / 7)}åØå`;
|
|
593
|
+
} else {
|
|
594
|
+
return `${Math.floor(days / 30)}äøŖęå`;
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
// Parse command options
|
|
599
|
+
parseOptions(args) {
|
|
600
|
+
const options = {
|
|
601
|
+
limit: 10,
|
|
602
|
+
format: 'summary',
|
|
603
|
+
timeRange: 'all',
|
|
604
|
+
cli: null,
|
|
605
|
+
search: null
|
|
606
|
+
};
|
|
607
|
+
|
|
608
|
+
for (let i = 0; i < args.length; i++) {
|
|
609
|
+
const arg = args[i];
|
|
610
|
+
|
|
611
|
+
if (arg === '--cli' && i + 1 < args.length) {
|
|
612
|
+
options.cli = args[++i];
|
|
613
|
+
} else if (arg === '--search' && i + 1 < args.length) {
|
|
614
|
+
options.search = args[++i];
|
|
615
|
+
} else if (arg === '--limit' && i + 1 < args.length) {
|
|
616
|
+
options.limit = parseInt(args[++i]);
|
|
617
|
+
} else if (arg === '--format' && i + 1 < args.length) {
|
|
618
|
+
const format = args[++i]?.toLowerCase();
|
|
619
|
+
if (['summary', 'timeline', 'detailed', 'context'].includes(format)) {
|
|
620
|
+
options.format = format;
|
|
621
|
+
}
|
|
622
|
+
} else if (arg === '--today') {
|
|
623
|
+
options.timeRange = 'today';
|
|
624
|
+
} else if (arg === '--week') {
|
|
625
|
+
options.timeRange = 'week';
|
|
626
|
+
} else if (arg === '--month') {
|
|
627
|
+
options.timeRange = 'month';
|
|
628
|
+
} else if (!arg.startsWith('--') && !options.search) {
|
|
629
|
+
options.search = arg;
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
return options;
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
// Execute resume command
|
|
637
|
+
execute(args) {
|
|
638
|
+
try {
|
|
639
|
+
const options = this.parseOptions(args);
|
|
640
|
+
const allSessions = this.scanAllCLISessions(this.projectPath);
|
|
641
|
+
const filteredSessions = this.applyFilters(allSessions, options, this.projectPath);
|
|
642
|
+
|
|
643
|
+
let response;
|
|
644
|
+
switch (options.format) {
|
|
645
|
+
case 'timeline':
|
|
646
|
+
response = this.formatTimeline(filteredSessions);
|
|
647
|
+
break;
|
|
648
|
+
case 'detailed':
|
|
649
|
+
response = this.formatDetailed(filteredSessions);
|
|
650
|
+
break;
|
|
651
|
+
case 'context':
|
|
652
|
+
response = this.formatContext(filteredSessions[0] || null);
|
|
653
|
+
break;
|
|
654
|
+
case 'summary':
|
|
655
|
+
default:
|
|
656
|
+
response = this.formatSummary(filteredSessions);
|
|
657
|
+
break;
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
console.log(response);
|
|
661
|
+
return 0;
|
|
662
|
+
} catch (error) {
|
|
663
|
+
console.error(`ā åå²ę„询失蓄: ${error.message}`);
|
|
664
|
+
return 1;
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
// Export for use as module
|
|
670
|
+
module.exports = ResumeSessionCommand;
|
|
671
|
+
|
|
672
|
+
// Export handler function for router
|
|
673
|
+
module.exports.handleResumeCommand = async function(args, options) {
|
|
674
|
+
const command = new ResumeSessionCommand();
|
|
675
|
+
return command.execute(args);
|
|
676
|
+
};
|
|
677
|
+
|
|
678
|
+
// Export help function
|
|
679
|
+
module.exports.printResumeHelp = function() {
|
|
680
|
+
console.log(`
|
|
681
|
+
ResumeSession - Cross-CLI session recovery and history management
|
|
682
|
+
|
|
683
|
+
Usage: stigmergy resume [options]
|
|
684
|
+
|
|
685
|
+
Options:
|
|
686
|
+
--cli <name> Filter by CLI tool (claude, gemini, qwen, etc.)
|
|
687
|
+
--search <term> Search sessions by content
|
|
688
|
+
--limit <num> Maximum sessions to show (default: 10)
|
|
689
|
+
--format <type> Output format: summary, timeline, detailed, context
|
|
690
|
+
--today Show today's sessions only
|
|
691
|
+
--week Show sessions from last 7 days
|
|
692
|
+
--month Show sessions from last 30 days
|
|
693
|
+
|
|
694
|
+
Examples:
|
|
695
|
+
stigmergy resume
|
|
696
|
+
stigmergy resume --cli claude
|
|
697
|
+
stigmergy resume --search react
|
|
698
|
+
stigmergy resume --format timeline
|
|
699
|
+
stigmergy resume --today
|
|
700
|
+
`);
|
|
701
|
+
};
|
|
702
|
+
|
|
703
|
+
// Run as CLI command
|
|
704
|
+
if (require.main === module) {
|
|
705
|
+
const command = new ResumeSessionCommand();
|
|
706
|
+
const args = process.argv.slice(2);
|
|
707
|
+
process.exit(command.execute(args));
|
|
708
|
+
}
|
package/src/cli/router-beta.js
CHANGED
|
@@ -35,7 +35,7 @@ const { handleDiagnosticCommand, handleCleanCommand } = require('./commands/syst
|
|
|
35
35
|
const { handleSkillMainCommand, printSkillsHelp } = require('./commands/skills');
|
|
36
36
|
const { handleErrorsCommand } = require('./commands/errors');
|
|
37
37
|
const { handleAutoInstallCommand } = require('./commands/autoinstall');
|
|
38
|
-
const { handleResumeCommand, printResumeHelp } = require('./commands/resume');
|
|
38
|
+
const { handleResumeCommand, printResumeHelp } = require('./commands/stigmergy-resume');
|
|
39
39
|
const { getCLIPath } = require('../core/cli_tools');
|
|
40
40
|
const {
|
|
41
41
|
handleUpgradeCommand,
|
|
@@ -123,11 +123,18 @@ async function main() {
|
|
|
123
123
|
program
|
|
124
124
|
.command('install')
|
|
125
125
|
.alias('inst')
|
|
126
|
+
.alias('a')
|
|
126
127
|
.description('Install CLI tools')
|
|
127
128
|
.option('-c, --cli <cli>', 'Install specific CLI tool')
|
|
128
129
|
.option('-v, --verbose', 'Verbose output')
|
|
129
130
|
.option('-f, --force', 'Force installation')
|
|
131
|
+
.option('--all', 'Install all CLI tools (ignore autoInstall filter)')
|
|
130
132
|
.action(async (options) => {
|
|
133
|
+
// ę£ęµęÆå¦éčæ 'a' å«åč°ēØļ¼čŖåØč®¾ē½® --all é锹
|
|
134
|
+
const commandName = process.argv[2];
|
|
135
|
+
if (commandName === 'a') {
|
|
136
|
+
options.all = true;
|
|
137
|
+
}
|
|
131
138
|
await handleInstallCommand(options);
|
|
132
139
|
});
|
|
133
140
|
|
|
@@ -323,7 +330,7 @@ async function main() {
|
|
|
323
330
|
// Resume session command
|
|
324
331
|
program
|
|
325
332
|
.command('resume')
|
|
326
|
-
.description('Resume session
|
|
333
|
+
.description('Resume session - Cross-CLI session recovery and history management')
|
|
327
334
|
.argument('[args...]', 'Arguments to pass to resumesession')
|
|
328
335
|
.option('-v, --verbose', 'Verbose output')
|
|
329
336
|
.action(async (args, options) => {
|
package/src/core/cli_tools.js
CHANGED
|
@@ -80,11 +80,11 @@ const CLI_TOOLS = {
|
|
|
80
80
|
},
|
|
81
81
|
resumesession: {
|
|
82
82
|
name: 'ResumeSession CLI',
|
|
83
|
-
version: '
|
|
84
|
-
install: '
|
|
83
|
+
version: 'stigmergy resume --version',
|
|
84
|
+
install: 'Built-in with stigmergy CLI',
|
|
85
85
|
hooksDir: path.join(os.homedir(), '.resumesession', 'hooks'),
|
|
86
86
|
config: path.join(os.homedir(), '.resumesession', 'config.json'),
|
|
87
|
-
autoInstall: false, //
|
|
87
|
+
autoInstall: false, // å
ē½®åč½ļ¼ę éåē¬å®č£
|
|
88
88
|
},
|
|
89
89
|
opencode: {
|
|
90
90
|
name: 'OpenCode AI CLI',
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BuiltinSkillsDeployer - Deploy built-in skills to CLI tools
|
|
3
|
+
* Automatically deploys stigmergy built-in skills during installation
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import fs from 'fs';
|
|
7
|
+
import path from 'path';
|
|
8
|
+
import os from 'os';
|
|
9
|
+
|
|
10
|
+
class BuiltinSkillsDeployer {
|
|
11
|
+
constructor() {
|
|
12
|
+
this.configPath = path.join(process.cwd(), 'config', 'builtin-skills.json');
|
|
13
|
+
this.skillsBaseDir = process.cwd();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Load built-in skills configuration
|
|
18
|
+
*/
|
|
19
|
+
loadConfig() {
|
|
20
|
+
try {
|
|
21
|
+
if (!fs.existsSync(this.configPath)) {
|
|
22
|
+
console.warn('[BUILTIN_SKILLS] No built-in skills configuration found');
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const content = fs.readFileSync(this.configPath, 'utf8');
|
|
27
|
+
return JSON.parse(content);
|
|
28
|
+
} catch (error) {
|
|
29
|
+
console.error('[BUILTIN_SKILLS] Failed to load configuration:', error.message);
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Deploy built-in skills to all CLI tools
|
|
36
|
+
*/
|
|
37
|
+
async deployAll() {
|
|
38
|
+
const config = this.loadConfig();
|
|
39
|
+
if (!config) {
|
|
40
|
+
return { success: false, message: 'No built-in skills configuration found' };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
console.log(`[BUILTIN_SKILLS] Found ${config.skills.length} built-in skill(s)`);
|
|
44
|
+
|
|
45
|
+
const results = [];
|
|
46
|
+
for (const skill of config.skills) {
|
|
47
|
+
if (skill.deployment && skill.deployment.autoDeploy) {
|
|
48
|
+
const result = await this.deploySkill(skill);
|
|
49
|
+
results.push(result);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const successCount = results.filter(r => r.success).length;
|
|
54
|
+
console.log(`[BUILTIN_SKILLS] Deployed ${successCount}/${results.length} skill(s)`);
|
|
55
|
+
|
|
56
|
+
return { success: true, results };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Deploy a single built-in skill to target CLIs
|
|
61
|
+
*/
|
|
62
|
+
async deploySkill(skill) {
|
|
63
|
+
const targetCLIs = skill.deployment.targetCLIs || [];
|
|
64
|
+
const results = [];
|
|
65
|
+
|
|
66
|
+
for (const cliName of targetCLIs) {
|
|
67
|
+
const result = await this.deployToCLI(skill, cliName);
|
|
68
|
+
results.push(result);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const successCount = results.filter(r => r.success).length;
|
|
72
|
+
return {
|
|
73
|
+
success: successCount === results.length,
|
|
74
|
+
skillName: skill.name,
|
|
75
|
+
targetCLIs: targetCLIs,
|
|
76
|
+
deployedCount: successCount,
|
|
77
|
+
results
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Deploy skill to a specific CLI
|
|
83
|
+
*/
|
|
84
|
+
async deployToCLI(skill, cliName) {
|
|
85
|
+
try {
|
|
86
|
+
const cliHomeDir = path.join(os.homedir(), `.${cliName}`);
|
|
87
|
+
const cliSkillsDir = path.join(cliHomeDir, 'skills', skill.name);
|
|
88
|
+
|
|
89
|
+
// Create skills directory
|
|
90
|
+
if (!fs.existsSync(cliSkillsDir)) {
|
|
91
|
+
fs.mkdirSync(cliSkillsDir, { recursive: true });
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Copy skill files
|
|
95
|
+
const files = skill.deployment.files || [];
|
|
96
|
+
for (const file of files) {
|
|
97
|
+
const sourcePath = path.join(this.skillsBaseDir, file.source);
|
|
98
|
+
const destPath = path.join(cliSkillsDir, path.basename(file.destination));
|
|
99
|
+
|
|
100
|
+
if (fs.existsSync(sourcePath)) {
|
|
101
|
+
const content = fs.readFileSync(sourcePath, 'utf8');
|
|
102
|
+
fs.writeFileSync(destPath, content);
|
|
103
|
+
console.log(`[BUILTIN_SKILLS] Deployed ${file.source} to ${cliName}`);
|
|
104
|
+
} else {
|
|
105
|
+
console.warn(`[BUILTIN_SKILLS] Source file not found: ${sourcePath}`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return { success: true, cliName, skillName: skill.name };
|
|
110
|
+
} catch (error) {
|
|
111
|
+
console.error(`[BUILTIN_SKILLS] Failed to deploy ${skill.name} to ${cliName}:`, error.message);
|
|
112
|
+
return { success: false, cliName, skillName: skill.name, error: error.message };
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Check if a skill is deployed to a CLI
|
|
118
|
+
*/
|
|
119
|
+
isDeployed(skillName, cliName) {
|
|
120
|
+
const cliSkillsDir = path.join(os.homedir(), `.${cliName}`, 'skills', skillName);
|
|
121
|
+
return fs.existsSync(cliSkillsDir);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Get deployment status for all built-in skills
|
|
126
|
+
*/
|
|
127
|
+
getDeploymentStatus() {
|
|
128
|
+
const config = this.loadConfig();
|
|
129
|
+
if (!config) {
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const status = [];
|
|
134
|
+
for (const skill of config.skills) {
|
|
135
|
+
const skillStatus = {
|
|
136
|
+
name: skill.name,
|
|
137
|
+
displayName: skill.displayName,
|
|
138
|
+
version: skill.version,
|
|
139
|
+
deployment: {}
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
const targetCLIs = skill.deployment.targetCLIs || [];
|
|
143
|
+
for (const cliName of targetCLIs) {
|
|
144
|
+
skillStatus.deployment[cliName] = this.isDeployed(skill.name, cliName);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
status.push(skillStatus);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return status;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export default BuiltinSkillsDeployer;
|
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Resume Session Commands
|
|
3
|
-
* Modular implementation for resume command
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
const chalk = require('chalk');
|
|
7
|
-
const { spawnSync } = require('child_process');
|
|
8
|
-
const { getCLIPath } = require('../../core/cli_tools');
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Handle resume session command - Forward to @stigmergy/resume CLI tool
|
|
12
|
-
* @param {Array} args - Arguments to pass to resumesession
|
|
13
|
-
* @param {Object} options - Command options
|
|
14
|
-
*/
|
|
15
|
-
async function handleResumeCommand(args = [], options = {}) {
|
|
16
|
-
try {
|
|
17
|
-
console.log(chalk.blue('[RESUME] Checking for ResumeSession CLI tool...'));
|
|
18
|
-
|
|
19
|
-
// Check if resumesession command is available
|
|
20
|
-
const resumesessionPath = await getCLIPath('resumesession');
|
|
21
|
-
|
|
22
|
-
if (resumesessionPath) {
|
|
23
|
-
console.log(chalk.green(`[RESUME] Found ResumeSession at: ${resumesessionPath}`));
|
|
24
|
-
|
|
25
|
-
// Forward all arguments to the resumesession CLI tool
|
|
26
|
-
const resumeArgs = args; // Pass all arguments through
|
|
27
|
-
console.log(chalk.blue(`[RESUME] Forwarding to resumesession: ${resumeArgs.join(' ') || '(no args)'}`));
|
|
28
|
-
|
|
29
|
-
// Execute resumesession with the provided arguments
|
|
30
|
-
const result = spawnSync(resumesessionPath, resumeArgs, {
|
|
31
|
-
stdio: 'inherit',
|
|
32
|
-
shell: true,
|
|
33
|
-
cwd: process.cwd(),
|
|
34
|
-
env: { ...process.env, FORCE_COLOR: '1' } // Ensure colors are preserved
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
if (result.status !== 0) {
|
|
38
|
-
console.log(chalk.yellow(`[WARN] ResumeSession exited with code ${result.status}`));
|
|
39
|
-
return { success: false, exitCode: result.status, forwarded: true };
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
return { success: true, exitCode: result.status, forwarded: true };
|
|
43
|
-
|
|
44
|
-
} else {
|
|
45
|
-
// ResumeSession CLI tool not found
|
|
46
|
-
console.log(chalk.red('[ERROR] ResumeSession CLI tool not found'));
|
|
47
|
-
console.log(chalk.yellow('[INFO] ResumeSession is an optional component for session recovery'));
|
|
48
|
-
|
|
49
|
-
console.log(chalk.blue('\nš¦ To install ResumeSession:'));
|
|
50
|
-
console.log(' npm install -g resumesession');
|
|
51
|
-
console.log('');
|
|
52
|
-
console.log(chalk.blue('š§ ResumeSession provides:'));
|
|
53
|
-
console.log(' ⢠Cross-CLI session history');
|
|
54
|
-
console.log(' ⢠Memory sharing between CLI tools');
|
|
55
|
-
console.log(' ⢠Session recovery and continuation');
|
|
56
|
-
console.log(' ⢠Centralized configuration management');
|
|
57
|
-
console.log('');
|
|
58
|
-
console.log(chalk.blue('š Usage examples:'));
|
|
59
|
-
console.log(' stigmergy resume list # Show session history');
|
|
60
|
-
console.log(' stigmergy resume continue <session-id> # Continue a session');
|
|
61
|
-
console.log(' stigmergy resume export # Export session data');
|
|
62
|
-
console.log(' stigmergy resume --help # Show all options');
|
|
63
|
-
|
|
64
|
-
return { success: false, error: 'ResumeSession CLI tool not found', forwarded: false };
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
} catch (error) {
|
|
68
|
-
console.error(chalk.red('[ERROR] Resume command failed:'), error.message);
|
|
69
|
-
|
|
70
|
-
if (options.verbose) {
|
|
71
|
-
console.error(chalk.red('Stack trace:'), error.stack);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
return { success: false, error: error.message, forwarded: false };
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Handle resumesession command alias
|
|
80
|
-
* @param {Array} args - Arguments
|
|
81
|
-
* @param {Object} options - Options
|
|
82
|
-
*/
|
|
83
|
-
async function handleResumeSessionCommand(args = [], options = {}) {
|
|
84
|
-
return await handleResumeCommand(args, options);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Handle sg-resume command alias
|
|
89
|
-
* @param {Array} args - Arguments
|
|
90
|
-
* @param {Object} options - Options
|
|
91
|
-
*/
|
|
92
|
-
async function handleSgResumeCommand(args = [], options = {}) {
|
|
93
|
-
return await handleResumeCommand(args, options);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* Print resume help information
|
|
98
|
-
*/
|
|
99
|
-
function printResumeHelp() {
|
|
100
|
-
console.log(chalk.cyan(`
|
|
101
|
-
š Stigmergy Resume Session System
|
|
102
|
-
|
|
103
|
-
š ResumeSession forwards to the resumesession CLI tool for session management.
|
|
104
|
-
|
|
105
|
-
š ļø Available Commands:
|
|
106
|
-
stigmergy resume [args] Forward to resumesession CLI
|
|
107
|
-
|
|
108
|
-
š¦ Requirements:
|
|
109
|
-
resumesession CLI tool must be installed separately.
|
|
110
|
-
|
|
111
|
-
š¾ Installation:
|
|
112
|
-
npm install -g resumesession
|
|
113
|
-
|
|
114
|
-
š Common Usage:
|
|
115
|
-
stigmergy resume list # Show available sessions
|
|
116
|
-
stigmergy resume continue <id> # Continue a specific session
|
|
117
|
-
stigmergy resume export # Export session data
|
|
118
|
-
stigmergy resume clean # Clean old sessions
|
|
119
|
-
|
|
120
|
-
š More Information:
|
|
121
|
-
ResumeSession provides cross-CLI memory sharing and session recovery
|
|
122
|
-
capabilities, allowing you to maintain context across different AI CLI tools.
|
|
123
|
-
|
|
124
|
-
If ResumeSession is not installed, you can still use all other Stigmergy
|
|
125
|
-
CLI features normally.
|
|
126
|
-
`));
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
module.exports = {
|
|
130
|
-
handleResumeCommand,
|
|
131
|
-
handleResumeSessionCommand,
|
|
132
|
-
handleSgResumeCommand,
|
|
133
|
-
printResumeHelp
|
|
134
|
-
};
|