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.
@@ -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,6 +1,6 @@
1
1
  {
2
2
  "name": "stigmergy",
3
- "version": "1.3.20-beta.0",
3
+ "version": "1.3.22-beta.0",
4
4
  "description": "Stigmergy CLI - Multi-Agents Cross-AI CLI Tools Collaboration System",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -1,191 +1,65 @@
1
- /**
2
- * Auto-install Command
3
- * Modular implementation for automated CLI tool installation (npm postinstall)
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 = {}) {
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
- // 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
14
+ await handleInstallCommand({
39
15
  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
- }
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
- // 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
- });
23
+ // Step 2: Deploy hooks and ResumeSession
24
+ console.log(chalk.blue('\nšŸš€ Step 2/3: Deploying hooks and ResumeSession integration...'));
90
25
 
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
- }
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.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
- }
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
- // 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
- }
42
+ // Step 3: Deploy built-in skills
43
+ console.log(chalk.blue('\nšŸš€ Step 3/3: Deploying built-in skills...'));
142
44
 
143
- if (verificationPassed === verificationChecks.length) {
144
- console.log(chalk.green('\nāœ… All verification checks passed!'));
45
+ try {
46
+ const skillsDeployer = new BuiltinSkillsDeployer();
47
+ const skillsResult = await skillsDeployer.deployAll();
145
48
 
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
- }
49
+ if (skillsResult.success) {
50
+ console.log(chalk.green('āœ“ Built-in skills deployed successfully'));
150
51
  } 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));
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
- 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 };
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
- module.exports = {
190
- handleAutoInstallCommand
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
+ }
@@ -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 (forwards to @stigmergy/resume CLI tool)')
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) => {
@@ -80,11 +80,11 @@ const CLI_TOOLS = {
80
80
  },
81
81
  resumesession: {
82
82
  name: 'ResumeSession CLI',
83
- version: 'resumesession --version',
84
- install: 'npm install -g @stigmergy/resume',
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
- };