stigmergy 1.3.20-beta.0 ā 1.3.22-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 +50 -176
- package/src/cli/commands/stigmergy-resume.js +708 -0
- package/src/cli/router-beta.js +2 -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,65 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
*/
|
|
14
|
-
async function handleAutoInstallCommand(options = {}) {
|
|
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';
|
|
5
|
+
|
|
6
|
+
export async function handleAutoInstallCommand(options) {
|
|
7
|
+
console.log(chalk.blue('š Stigmergy Auto-Installation'));
|
|
8
|
+
console.log(chalk.gray('=====================================\n'));
|
|
9
|
+
|
|
10
|
+
// Step 1: Install CLI tools
|
|
11
|
+
console.log(chalk.blue('š¦ Step 1/3: Installing CLI tools...'));
|
|
12
|
+
|
|
15
13
|
try {
|
|
16
|
-
|
|
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
|
|
14
|
+
await handleInstallCommand({
|
|
39
15
|
verbose: options.verbose || process.env.DEBUG === 'true',
|
|
40
|
-
force: options.force || false
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
-
}
|
|
16
|
+
force: options.force || false
|
|
17
|
+
});
|
|
18
|
+
} catch (error) {
|
|
19
|
+
console.error(chalk.red('ā CLI tools installation failed:'), error.message);
|
|
20
|
+
// Continue with next step even if CLI tools installation fails
|
|
21
|
+
}
|
|
81
22
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
try {
|
|
86
|
-
const deployResult = await handleDeployCommand({
|
|
87
|
-
verbose: options.verbose || process.env.DEBUG === 'true',
|
|
88
|
-
force: options.force || false
|
|
89
|
-
});
|
|
23
|
+
// Step 2: Deploy hooks and ResumeSession
|
|
24
|
+
console.log(chalk.blue('\nš Step 2/3: Deploying hooks and ResumeSession integration...'));
|
|
90
25
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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
|
-
}
|
|
26
|
+
try {
|
|
27
|
+
const deployResult = await handleDeployCommand({
|
|
28
|
+
verbose: options.verbose || process.env.DEBUG === 'true',
|
|
29
|
+
force: options.force || false
|
|
30
|
+
});
|
|
106
31
|
|
|
32
|
+
if (deployResult.success) {
|
|
33
|
+
console.log(chalk.green('ā Hooks deployed successfully'));
|
|
107
34
|
} else {
|
|
108
|
-
console.
|
|
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
|
-
}
|
|
35
|
+
console.warn(chalk.yellow('ā Hooks deployment completed with warnings'));
|
|
118
36
|
}
|
|
37
|
+
} catch (error) {
|
|
38
|
+
console.error(chalk.red('ā Hooks deployment failed:'), error.message);
|
|
39
|
+
// Continue with next step even if hooks deployment fails
|
|
40
|
+
}
|
|
119
41
|
|
|
120
|
-
|
|
121
|
-
|
|
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
|
-
}
|
|
42
|
+
// Step 3: Deploy built-in skills
|
|
43
|
+
console.log(chalk.blue('\nš Step 3/3: Deploying built-in skills...'));
|
|
142
44
|
|
|
143
|
-
|
|
144
|
-
|
|
45
|
+
try {
|
|
46
|
+
const skillsDeployer = new BuiltinSkillsDeployer();
|
|
47
|
+
const skillsResult = await skillsDeployer.deployAll();
|
|
145
48
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
criticalLog(chalk.green('šÆ All CLI tools configured with ResumeSession support'));
|
|
149
|
-
}
|
|
49
|
+
if (skillsResult.success) {
|
|
50
|
+
console.log(chalk.green('ā Built-in skills deployed successfully'));
|
|
150
51
|
} else {
|
|
151
|
-
console.
|
|
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));
|
|
52
|
+
console.warn(chalk.yellow('ā Built-in skills deployment completed with warnings'));
|
|
165
53
|
}
|
|
166
|
-
|
|
167
|
-
return {
|
|
168
|
-
success: installResult.success,
|
|
169
|
-
verificationPassed,
|
|
170
|
-
installed: installResult.installed || [],
|
|
171
|
-
existing: installResult.existing || []
|
|
172
|
-
};
|
|
173
|
-
|
|
174
54
|
} catch (error) {
|
|
175
|
-
|
|
176
|
-
|
|
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 };
|
|
55
|
+
console.error(chalk.red('ā Built-in skills deployment failed:'), error.message);
|
|
56
|
+
// Continue even if skills deployment fails
|
|
186
57
|
}
|
|
187
|
-
}
|
|
188
58
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
59
|
+
console.log(chalk.green('\nš Auto-installation completed successfully!'));
|
|
60
|
+
console.log(chalk.cyan('\nNext steps:'));
|
|
61
|
+
console.log(' ⢠Run: stigmergy --help');
|
|
62
|
+
console.log(' ⢠Try: stigmergy claude "Hello World"');
|
|
63
|
+
console.log(' ⢠Check: stigmergy status');
|
|
64
|
+
console.log(' ⢠Resume sessions: stigmergy resume --limit 10');
|
|
65
|
+
}
|
|
@@ -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,
|
|
@@ -330,7 +330,7 @@ async function main() {
|
|
|
330
330
|
// Resume session command
|
|
331
331
|
program
|
|
332
332
|
.command('resume')
|
|
333
|
-
.description('Resume session
|
|
333
|
+
.description('Resume session - Cross-CLI session recovery and history management')
|
|
334
334
|
.argument('[args...]', 'Arguments to pass to resumesession')
|
|
335
335
|
.option('-v, --verbose', 'Verbose output')
|
|
336
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
|
-
};
|