pulse-coder-cli 0.0.1-alpha.1
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/.pulse-coder/agents/code-reviewer.md +36 -0
- package/.pulse-coder/agents/doc-generator.md +37 -0
- package/.pulse-coder/agents/test-writer.md +38 -0
- package/.pulse-coder/mcp.json +8 -0
- package/.pulse-coder/skills/branch-naming/SKILL.md +427 -0
- package/.pulse-coder/skills/code-review/SKILL.md +56 -0
- package/.pulse-coder/skills/deep-research/SKILL.md +124 -0
- package/.pulse-coder/skills/git-workflow/SKILL.md +93 -0
- package/.pulse-coder/skills/mr-generator/README.md +96 -0
- package/.pulse-coder/skills/mr-generator/SKILL.md +113 -0
- package/.pulse-coder/skills/mr-generator/mr-generate.sh +342 -0
- package/.pulse-coder/skills/refactor/SKILL.md +63 -0
- package/README.md +155 -0
- package/dist/index.js +599 -0
- package/dist/index.js.map +1 -0
- package/package.json +25 -0
- package/src/index.ts +321 -0
- package/src/input-manager.ts +113 -0
- package/src/session-commands.ts +142 -0
- package/src/session.ts +171 -0
- package/tsconfig.json +9 -0
- package/tsup.config.ts +12 -0
package/package.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "pulse-coder-cli",
|
|
3
|
+
"version": "0.0.1-alpha.1",
|
|
4
|
+
"description": "CLI interface for Pulse Coder",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"pulse-coder": "./dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsup",
|
|
12
|
+
"dev": "tsup --watch",
|
|
13
|
+
"test": "vitest",
|
|
14
|
+
"start": "node dist/index.js"
|
|
15
|
+
},
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"@pulse-coder/engine": "workspace:*"
|
|
18
|
+
},
|
|
19
|
+
"devDependencies": {
|
|
20
|
+
"typescript": "^5.0.0",
|
|
21
|
+
"tsup": "^8.0.0",
|
|
22
|
+
"vitest": "^1.0.0",
|
|
23
|
+
"@types/node": "^25.0.10"
|
|
24
|
+
}
|
|
25
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
import { PulseAgent } from '@pulse-coder/engine';
|
|
2
|
+
import * as readline from 'readline';
|
|
3
|
+
import type { Context } from '@pulse-coder/engine';
|
|
4
|
+
import { SessionCommands } from './session-commands.js';
|
|
5
|
+
import { InputManager } from './input-manager.js';
|
|
6
|
+
|
|
7
|
+
class CoderCLI {
|
|
8
|
+
private agent: PulseAgent;
|
|
9
|
+
private context: Context;
|
|
10
|
+
private sessionCommands: SessionCommands;
|
|
11
|
+
private inputManager: InputManager;
|
|
12
|
+
|
|
13
|
+
constructor() {
|
|
14
|
+
// šÆ ē°åØå¼ęčŖåØå
å«å
ē½®ęä»¶ļ¼ę éę¾å¼é
ē½®ļ¼
|
|
15
|
+
this.agent = new PulseAgent({
|
|
16
|
+
enginePlugins: {
|
|
17
|
+
// åŖé
ē½®ę©å±ęä»¶ē®å½ļ¼å
ē½®ęä»¶ä¼čŖåØå č½½
|
|
18
|
+
dirs: ['.pulse-coder/engine-plugins', '.coder/engine-plugins', '~/.pulse-coder/engine-plugins', '~/.coder/engine-plugins'],
|
|
19
|
+
scan: true
|
|
20
|
+
},
|
|
21
|
+
userConfigPlugins: {
|
|
22
|
+
dirs: ['.pulse-coder/config', '.coder/config', '~/.pulse-coder/config', '~/.coder/config'],
|
|
23
|
+
scan: true
|
|
24
|
+
}
|
|
25
|
+
// 注ęļ¼äøåéč¦ plugins: [...] é
ē½®
|
|
26
|
+
});
|
|
27
|
+
this.context = { messages: [] };
|
|
28
|
+
this.sessionCommands = new SessionCommands();
|
|
29
|
+
this.inputManager = new InputManager();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
private async handleCommand(command: string, args: string[]): Promise<void> {
|
|
33
|
+
try {
|
|
34
|
+
switch (command.toLowerCase()) {
|
|
35
|
+
case 'help':
|
|
36
|
+
console.log('\nš Available commands:');
|
|
37
|
+
console.log('/help - Show this help message');
|
|
38
|
+
console.log('/new [title] - Create a new session');
|
|
39
|
+
console.log('/resume <id> - Resume a saved session');
|
|
40
|
+
console.log('/sessions - List all saved sessions');
|
|
41
|
+
console.log('/search <query> - Search in saved sessions');
|
|
42
|
+
console.log('/rename <id> <new-title> - Rename a session');
|
|
43
|
+
console.log('/delete <id> - Delete a session');
|
|
44
|
+
console.log('/clear - Clear current conversation');
|
|
45
|
+
console.log('/status - Show current session status');
|
|
46
|
+
console.log('/save - Save current session explicitly');
|
|
47
|
+
console.log('/exit - Exit the application');
|
|
48
|
+
break;
|
|
49
|
+
|
|
50
|
+
case 'new':
|
|
51
|
+
const newTitle = args.join(' ') || undefined;
|
|
52
|
+
await this.sessionCommands.createSession(newTitle);
|
|
53
|
+
this.context.messages = [];
|
|
54
|
+
break;
|
|
55
|
+
|
|
56
|
+
case 'resume':
|
|
57
|
+
if (args.length === 0) {
|
|
58
|
+
console.log('\nā Please provide a session ID');
|
|
59
|
+
console.log('Usage: /resume <session-id>');
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
const sessionId = args[0];
|
|
63
|
+
const success = await this.sessionCommands.resumeSession(sessionId);
|
|
64
|
+
if (success) {
|
|
65
|
+
await this.sessionCommands.loadContext(this.context);
|
|
66
|
+
}
|
|
67
|
+
break;
|
|
68
|
+
|
|
69
|
+
case 'sessions':
|
|
70
|
+
await this.sessionCommands.listSessions();
|
|
71
|
+
break;
|
|
72
|
+
|
|
73
|
+
case 'search':
|
|
74
|
+
if (args.length === 0) {
|
|
75
|
+
console.log('\nā Please provide a search query');
|
|
76
|
+
console.log('Usage: /search <query>');
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
const query = args.join(' ');
|
|
80
|
+
await this.sessionCommands.searchSessions(query);
|
|
81
|
+
break;
|
|
82
|
+
|
|
83
|
+
case 'rename':
|
|
84
|
+
if (args.length < 2) {
|
|
85
|
+
console.log('\nā Please provide session ID and new title');
|
|
86
|
+
console.log('Usage: /rename <session-id> <new-title>');
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
const renameId = args[0];
|
|
90
|
+
const newName = args.slice(1).join(' ');
|
|
91
|
+
await this.sessionCommands.renameSession(renameId, newName);
|
|
92
|
+
break;
|
|
93
|
+
|
|
94
|
+
case 'delete':
|
|
95
|
+
if (args.length === 0) {
|
|
96
|
+
console.log('\nā Please provide a session ID');
|
|
97
|
+
console.log('Usage: /delete <session-id>');
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
100
|
+
const deleteId = args[0];
|
|
101
|
+
await this.sessionCommands.deleteSession(deleteId);
|
|
102
|
+
break;
|
|
103
|
+
|
|
104
|
+
case 'clear':
|
|
105
|
+
this.context.messages = [];
|
|
106
|
+
console.log('\nš§¹ Current conversation cleared!');
|
|
107
|
+
break;
|
|
108
|
+
|
|
109
|
+
case 'status':
|
|
110
|
+
const currentId = this.sessionCommands.getCurrentSessionId();
|
|
111
|
+
console.log(`\nš Session Status:`);
|
|
112
|
+
console.log(`Current Session: ${currentId || 'None (new session)'}`);
|
|
113
|
+
console.log(`Messages: ${this.context.messages.length}`);
|
|
114
|
+
if (currentId) {
|
|
115
|
+
console.log(`To save this session, use: /save`);
|
|
116
|
+
}
|
|
117
|
+
break;
|
|
118
|
+
|
|
119
|
+
case 'save':
|
|
120
|
+
if (this.sessionCommands.getCurrentSessionId()) {
|
|
121
|
+
await this.sessionCommands.saveContext(this.context);
|
|
122
|
+
console.log('\nš¾ Current session saved!');
|
|
123
|
+
} else {
|
|
124
|
+
console.log('\nā No active session. Create one with /new');
|
|
125
|
+
}
|
|
126
|
+
break;
|
|
127
|
+
|
|
128
|
+
case 'exit':
|
|
129
|
+
console.log('š¾ Saving current session...');
|
|
130
|
+
await this.sessionCommands.saveContext(this.context);
|
|
131
|
+
console.log('Goodbye!');
|
|
132
|
+
process.exit(0);
|
|
133
|
+
break;
|
|
134
|
+
|
|
135
|
+
default:
|
|
136
|
+
console.log(`\nā ļø Unknown command: ${command}`);
|
|
137
|
+
console.log('Type /help to see available commands');
|
|
138
|
+
}
|
|
139
|
+
} catch (error) {
|
|
140
|
+
console.error('\nā Error executing command:', error);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async start() {
|
|
145
|
+
console.log('š Pulse Coder CLI is running...');
|
|
146
|
+
console.log('Type your messages and press Enter. Type "exit" to quit.');
|
|
147
|
+
console.log('Commands starting with "/" will trigger command mode.\n');
|
|
148
|
+
|
|
149
|
+
await this.sessionCommands.initialize();
|
|
150
|
+
await this.agent.initialize();
|
|
151
|
+
|
|
152
|
+
// ę¾ē¤ŗęä»¶ē¶ę
|
|
153
|
+
const pluginStatus = this.agent.getPluginStatus();
|
|
154
|
+
console.log(`ā
Built-in plugins loaded: ${pluginStatus.enginePlugins.length} plugins`);
|
|
155
|
+
|
|
156
|
+
// Auto-create a new session
|
|
157
|
+
await this.sessionCommands.createSession();
|
|
158
|
+
|
|
159
|
+
const rl = readline.createInterface({
|
|
160
|
+
input: process.stdin,
|
|
161
|
+
output: process.stdout,
|
|
162
|
+
prompt: '> '
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
let currentAbortController: AbortController | null = null;
|
|
166
|
+
let isProcessing = false;
|
|
167
|
+
|
|
168
|
+
// Handle SIGINT gracefully
|
|
169
|
+
process.on('SIGINT', () => {
|
|
170
|
+
if (isProcessing && currentAbortController && !currentAbortController.signal.aborted) {
|
|
171
|
+
currentAbortController.abort();
|
|
172
|
+
console.log('\n[Abort] Request cancelled.');
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Cancel any pending clarification request
|
|
177
|
+
if (this.inputManager.hasPendingRequest()) {
|
|
178
|
+
this.inputManager.cancel('User interrupted with Ctrl+C');
|
|
179
|
+
console.log('\n[Abort] Clarification cancelled.');
|
|
180
|
+
rl.prompt();
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
console.log('\nš¾ Saving current session...');
|
|
185
|
+
this.sessionCommands.saveContext(this.context).then(() => {
|
|
186
|
+
console.log('š Goodbye!');
|
|
187
|
+
process.exit(0);
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
// Main input handler
|
|
192
|
+
const handleInput = async (input: string) => {
|
|
193
|
+
const trimmedInput = input.trim();
|
|
194
|
+
|
|
195
|
+
// Handle clarification requests first
|
|
196
|
+
if (this.inputManager.handleUserInput(trimmedInput)) {
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (trimmedInput.toLowerCase() === 'exit') {
|
|
201
|
+
console.log('š¾ Saving current session...');
|
|
202
|
+
await this.sessionCommands.saveContext(this.context);
|
|
203
|
+
console.log('š Goodbye!');
|
|
204
|
+
rl.close();
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (!trimmedInput) {
|
|
209
|
+
rl.prompt();
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Handle commands
|
|
214
|
+
if (trimmedInput.startsWith('/')) {
|
|
215
|
+
const commandLine = trimmedInput.substring(1);
|
|
216
|
+
const parts = commandLine.split(/\s+/).filter(part => part.length > 0);
|
|
217
|
+
|
|
218
|
+
if (parts.length === 0) {
|
|
219
|
+
console.log('\nā ļø Please provide a command after "/"');
|
|
220
|
+
rl.prompt();
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const command = parts[0];
|
|
225
|
+
const args = parts.slice(1);
|
|
226
|
+
|
|
227
|
+
await this.handleCommand(command, args);
|
|
228
|
+
rl.prompt();
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Regular message processing
|
|
233
|
+
this.context.messages.push({
|
|
234
|
+
role: 'user',
|
|
235
|
+
content: trimmedInput,
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
console.log('\nš Processing...\n');
|
|
239
|
+
|
|
240
|
+
const ac = new AbortController();
|
|
241
|
+
currentAbortController = ac;
|
|
242
|
+
isProcessing = true;
|
|
243
|
+
|
|
244
|
+
let sawText = false;
|
|
245
|
+
|
|
246
|
+
try {
|
|
247
|
+
const result = await this.agent.run(this.context, {
|
|
248
|
+
abortSignal: ac.signal,
|
|
249
|
+
onText: (delta) => {
|
|
250
|
+
sawText = true;
|
|
251
|
+
process.stdout.write(delta);
|
|
252
|
+
},
|
|
253
|
+
onToolCall: (toolCall) => {
|
|
254
|
+
const input = 'input' in toolCall ? toolCall.input : undefined;
|
|
255
|
+
const inputText = input === undefined ? '' : `(${JSON.stringify(input)})`;
|
|
256
|
+
process.stdout.write(`\nš§ ${toolCall.toolName}${inputText}\n`);
|
|
257
|
+
},
|
|
258
|
+
onToolResult: (toolResult) => {
|
|
259
|
+
process.stdout.write(`\nā
${toolResult.toolName}\n`);
|
|
260
|
+
},
|
|
261
|
+
onStepFinish: (step) => {
|
|
262
|
+
process.stdout.write(`\nš Step finished: ${step.finishReason}\n`);
|
|
263
|
+
},
|
|
264
|
+
onClarificationRequest: async (request) => {
|
|
265
|
+
return await this.inputManager.requestInput(request);
|
|
266
|
+
},
|
|
267
|
+
onCompacted: (newMessages) => {
|
|
268
|
+
this.context.messages = newMessages;
|
|
269
|
+
},
|
|
270
|
+
onResponse: (messages) => {
|
|
271
|
+
this.context.messages.push(...messages);
|
|
272
|
+
},
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
if (result) {
|
|
276
|
+
if (!sawText) {
|
|
277
|
+
console.log(result);
|
|
278
|
+
} else {
|
|
279
|
+
console.log();
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
this.context.messages.push({
|
|
283
|
+
role: 'assistant',
|
|
284
|
+
content: result,
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
await this.sessionCommands.saveContext(this.context);
|
|
288
|
+
}
|
|
289
|
+
} catch (error) {
|
|
290
|
+
if (error.name === 'AbortError') {
|
|
291
|
+
console.log('\n[Abort] Operation cancelled.');
|
|
292
|
+
} else {
|
|
293
|
+
console.error('\nā Error:', error.message);
|
|
294
|
+
}
|
|
295
|
+
} finally {
|
|
296
|
+
isProcessing = false;
|
|
297
|
+
currentAbortController = null;
|
|
298
|
+
rl.prompt();
|
|
299
|
+
}
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
// Start the CLI
|
|
303
|
+
rl.prompt();
|
|
304
|
+
rl.on('line', handleInput);
|
|
305
|
+
|
|
306
|
+
// Handle terminal close
|
|
307
|
+
rl.on('close', async () => {
|
|
308
|
+
console.log('\nš¾ Saving current session...');
|
|
309
|
+
await this.sessionCommands.saveContext(this.context);
|
|
310
|
+
console.log('š Goodbye!');
|
|
311
|
+
process.exit(0);
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// Always start the CLI when executed directly
|
|
317
|
+
const cli = new CoderCLI();
|
|
318
|
+
cli.start().catch(error => {
|
|
319
|
+
console.error('Failed to start CLI:', error);
|
|
320
|
+
process.exit(1);
|
|
321
|
+
});
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import type { ClarificationRequest } from '@pulse-coder/engine';
|
|
2
|
+
|
|
3
|
+
interface PendingRequest {
|
|
4
|
+
request: ClarificationRequest;
|
|
5
|
+
resolve: (answer: string) => void;
|
|
6
|
+
reject: (error: Error) => void;
|
|
7
|
+
timeoutId?: NodeJS.Timeout;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* InputManager handles asynchronous user input for clarification requests.
|
|
12
|
+
* It manages pending clarification requests and coordinates with the readline interface.
|
|
13
|
+
*/
|
|
14
|
+
export class InputManager {
|
|
15
|
+
private pendingRequest: PendingRequest | null = null;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Request user input for a clarification
|
|
19
|
+
* @param request The clarification request details
|
|
20
|
+
* @returns Promise that resolves with the user's answer
|
|
21
|
+
*/
|
|
22
|
+
async requestInput(request: ClarificationRequest): Promise<string> {
|
|
23
|
+
return new Promise<string>((resolve, reject) => {
|
|
24
|
+
// If there's already a pending request, reject this one
|
|
25
|
+
if (this.pendingRequest) {
|
|
26
|
+
reject(new Error('Another clarification request is already pending'));
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Display the question to the user
|
|
31
|
+
console.log(`\nā ${request.question}`);
|
|
32
|
+
if (request.context) {
|
|
33
|
+
console.log(` ${request.context}`);
|
|
34
|
+
}
|
|
35
|
+
if (request.defaultAnswer) {
|
|
36
|
+
console.log(` (Default: ${request.defaultAnswer})`);
|
|
37
|
+
}
|
|
38
|
+
console.log(''); // Empty line for spacing
|
|
39
|
+
|
|
40
|
+
// Set up timeout if specified
|
|
41
|
+
let timeoutId: NodeJS.Timeout | undefined;
|
|
42
|
+
if (request.timeout > 0) {
|
|
43
|
+
timeoutId = setTimeout(() => {
|
|
44
|
+
if (this.pendingRequest?.request.id === request.id) {
|
|
45
|
+
const error = new Error(`Clarification request timed out after ${request.timeout}ms`);
|
|
46
|
+
this.pendingRequest.reject(error);
|
|
47
|
+
this.pendingRequest = null;
|
|
48
|
+
}
|
|
49
|
+
}, request.timeout);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Store the pending request
|
|
53
|
+
this.pendingRequest = {
|
|
54
|
+
request,
|
|
55
|
+
resolve,
|
|
56
|
+
reject,
|
|
57
|
+
timeoutId
|
|
58
|
+
};
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Handle user input - checks if there's a pending clarification request
|
|
64
|
+
* @param input The user's input
|
|
65
|
+
* @returns true if input was consumed by a pending clarification, false otherwise
|
|
66
|
+
*/
|
|
67
|
+
handleUserInput(input: string): boolean {
|
|
68
|
+
if (!this.pendingRequest) {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Clear the timeout if it exists
|
|
73
|
+
if (this.pendingRequest.timeoutId) {
|
|
74
|
+
clearTimeout(this.pendingRequest.timeoutId);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Resolve the pending request with the user's input
|
|
78
|
+
const trimmedInput = input.trim();
|
|
79
|
+
this.pendingRequest.resolve(trimmedInput);
|
|
80
|
+
this.pendingRequest = null;
|
|
81
|
+
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Cancel any pending clarification request
|
|
87
|
+
* @param reason Optional cancellation reason
|
|
88
|
+
*/
|
|
89
|
+
cancel(reason?: string): void {
|
|
90
|
+
if (this.pendingRequest) {
|
|
91
|
+
if (this.pendingRequest.timeoutId) {
|
|
92
|
+
clearTimeout(this.pendingRequest.timeoutId);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
this.pendingRequest.reject(new Error(reason || 'Clarification request cancelled'));
|
|
96
|
+
this.pendingRequest = null;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Check if there's a pending clarification request
|
|
102
|
+
*/
|
|
103
|
+
hasPendingRequest(): boolean {
|
|
104
|
+
return this.pendingRequest !== null;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Get the current pending request (for debugging/testing)
|
|
109
|
+
*/
|
|
110
|
+
getPendingRequest(): ClarificationRequest | null {
|
|
111
|
+
return this.pendingRequest?.request || null;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { SessionManager } from './session.js';
|
|
2
|
+
import type { Context } from '@pulse-coder/engine';
|
|
3
|
+
|
|
4
|
+
export class SessionCommands {
|
|
5
|
+
private sessionManager: SessionManager;
|
|
6
|
+
private currentSessionId: string | null = null;
|
|
7
|
+
|
|
8
|
+
constructor() {
|
|
9
|
+
this.sessionManager = new SessionManager();
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
async initialize(): Promise<void> {
|
|
13
|
+
await this.sessionManager.initialize();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
getCurrentSessionId(): string | null {
|
|
17
|
+
return this.currentSessionId;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async createSession(title?: string): Promise<string> {
|
|
21
|
+
const session = await this.sessionManager.createSession(title);
|
|
22
|
+
this.currentSessionId = session.id;
|
|
23
|
+
console.log(`\nā
New session created: ${session.title} (ID: ${session.id})`);
|
|
24
|
+
return session.id;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async resumeSession(id: string): Promise<boolean> {
|
|
28
|
+
const session = await this.sessionManager.loadSession(id);
|
|
29
|
+
if (!session) {
|
|
30
|
+
console.log(`\nā Session not found: ${id}`);
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
this.currentSessionId = session.id;
|
|
35
|
+
console.log(`\nā
Resumed session: ${session.title} (ID: ${session.id})`);
|
|
36
|
+
console.log(`š Loaded ${session.messages.length} messages`);
|
|
37
|
+
|
|
38
|
+
// Show last few messages as context
|
|
39
|
+
const recentMessages = session.messages.slice(-5);
|
|
40
|
+
if (recentMessages.length > 0) {
|
|
41
|
+
console.log('\nš¬ Recent conversation:');
|
|
42
|
+
recentMessages.forEach((msg, index) => {
|
|
43
|
+
const role = msg.role === 'user' ? 'š¤ You' : 'š¤ Assistant';
|
|
44
|
+
const contentStr = typeof msg.content === 'string' ? msg.content : JSON.stringify(msg.content);
|
|
45
|
+
const preview = contentStr.substring(0, 100) + (contentStr.length > 100 ? '...' : '');
|
|
46
|
+
console.log(`${index + 1}. ${role}: ${preview}`);
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async listSessions(): Promise<void> {
|
|
54
|
+
const sessions = await this.sessionManager.listSessions();
|
|
55
|
+
|
|
56
|
+
if (sessions.length === 0) {
|
|
57
|
+
console.log('\nš No saved sessions found.');
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
console.log('\nš Saved sessions:');
|
|
62
|
+
console.log('='.repeat(80));
|
|
63
|
+
|
|
64
|
+
sessions.forEach((session, index) => {
|
|
65
|
+
const isActive = session.id === this.currentSessionId ? 'ā
' : ' ';
|
|
66
|
+
const date = new Date(session.updatedAt).toLocaleString();
|
|
67
|
+
console.log(`${index + 1}. ${isActive} ${session.title}`);
|
|
68
|
+
console.log(` ID: ${session.id}`);
|
|
69
|
+
console.log(` Messages: ${session.messageCount} | Updated: ${date}`);
|
|
70
|
+
console.log(` Preview: ${session.preview}`);
|
|
71
|
+
console.log();
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async saveContext(context: Context): Promise<void> {
|
|
76
|
+
if (!this.currentSessionId) return;
|
|
77
|
+
|
|
78
|
+
const session = await this.sessionManager.loadSession(this.currentSessionId);
|
|
79
|
+
if (!session) return;
|
|
80
|
+
|
|
81
|
+
// Sync messages from context
|
|
82
|
+
session.messages = context.messages.map(msg => ({
|
|
83
|
+
role: msg.role as 'user' | 'assistant' | 'system',
|
|
84
|
+
content: typeof msg.content === 'string' ? msg.content : JSON.stringify(msg.content),
|
|
85
|
+
timestamp: Date.now(),
|
|
86
|
+
}));
|
|
87
|
+
|
|
88
|
+
await this.sessionManager.saveSession(session);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async loadContext(context: Context): Promise<void> {
|
|
92
|
+
if (!this.currentSessionId) return;
|
|
93
|
+
|
|
94
|
+
const session = await this.sessionManager.loadSession(this.currentSessionId);
|
|
95
|
+
if (!session) return;
|
|
96
|
+
|
|
97
|
+
// Load messages into context
|
|
98
|
+
context.messages = session.messages.map(msg => ({
|
|
99
|
+
role: msg.role,
|
|
100
|
+
content: msg.content,
|
|
101
|
+
}));
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async searchSessions(query: string): Promise<void> {
|
|
105
|
+
const sessions = await this.sessionManager.searchSessions(query);
|
|
106
|
+
|
|
107
|
+
if (sessions.length === 0) {
|
|
108
|
+
console.log(`\nš No sessions found matching "${query}"`);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
console.log(`\nš Search results for "${query}":`);
|
|
113
|
+
sessions.forEach((session, index) => {
|
|
114
|
+
console.log(`${index + 1}. ${session.title} (${session.id}) - ${session.messageCount} messages`);
|
|
115
|
+
console.log(` Updated: ${new Date(session.updatedAt).toLocaleString()}`);
|
|
116
|
+
console.log(` Preview: ${session.preview}`);
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async deleteSession(id: string): Promise<boolean> {
|
|
121
|
+
const success = await this.sessionManager.deleteSession(id);
|
|
122
|
+
if (success) {
|
|
123
|
+
console.log(`\nšļø Session ${id} deleted`);
|
|
124
|
+
if (this.currentSessionId === id) {
|
|
125
|
+
this.currentSessionId = null;
|
|
126
|
+
}
|
|
127
|
+
} else {
|
|
128
|
+
console.log(`\nā Failed to delete session ${id}`);
|
|
129
|
+
}
|
|
130
|
+
return success;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async renameSession(id: string, newTitle: string): Promise<boolean> {
|
|
134
|
+
const success = await this.sessionManager.updateSessionTitle(id, newTitle);
|
|
135
|
+
if (success) {
|
|
136
|
+
console.log(`\nā
Session ${id} renamed to "${newTitle}"`);
|
|
137
|
+
} else {
|
|
138
|
+
console.log(`\nā Failed to rename session ${id}`);
|
|
139
|
+
}
|
|
140
|
+
return success;
|
|
141
|
+
}
|
|
142
|
+
}
|