happy-coder 0.1.7 → 0.1.10
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/dist/index.cjs +950 -938
- package/dist/index.mjs +880 -868
- package/dist/lib.cjs +32 -0
- package/dist/lib.d.cts +527 -0
- package/dist/lib.d.mts +527 -0
- package/dist/lib.mjs +14 -0
- package/dist/types-B2JzqUiU.cjs +831 -0
- package/dist/types-DnQGY77F.mjs +818 -0
- package/package.json +25 -10
- package/dist/auth/auth.d.ts +0 -38
- package/dist/auth/auth.js +0 -76
- package/dist/auth/auth.test.d.ts +0 -7
- package/dist/auth/auth.test.js +0 -96
- package/dist/auth/crypto.d.ts +0 -25
- package/dist/auth/crypto.js +0 -36
- package/dist/claude/claude.d.ts +0 -54
- package/dist/claude/claude.js +0 -170
- package/dist/claude/claude.test.d.ts +0 -7
- package/dist/claude/claude.test.js +0 -130
- package/dist/claude/types.d.ts +0 -37
- package/dist/claude/types.js +0 -7
- package/dist/commands/start.d.ts +0 -38
- package/dist/commands/start.js +0 -161
- package/dist/commands/start.test.d.ts +0 -7
- package/dist/commands/start.test.js +0 -307
- package/dist/handlers/message-handler.d.ts +0 -65
- package/dist/handlers/message-handler.js +0 -187
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -1
- package/dist/session/service.d.ts +0 -27
- package/dist/session/service.js +0 -93
- package/dist/session/service.test.d.ts +0 -7
- package/dist/session/service.test.js +0 -71
- package/dist/session/types.d.ts +0 -44
- package/dist/session/types.js +0 -4
- package/dist/socket/client.d.ts +0 -50
- package/dist/socket/client.js +0 -136
- package/dist/socket/client.test.d.ts +0 -7
- package/dist/socket/client.test.js +0 -74
- package/dist/socket/types.d.ts +0 -80
- package/dist/socket/types.js +0 -12
- package/dist/utils/config.d.ts +0 -22
- package/dist/utils/config.js +0 -23
- package/dist/utils/logger.d.ts +0 -26
- package/dist/utils/logger.js +0 -60
- package/dist/utils/paths.d.ts +0 -18
- package/dist/utils/paths.js +0 -24
- package/dist/utils/qrcode.d.ts +0 -19
- package/dist/utils/qrcode.js +0 -37
- package/dist/utils/qrcode.test.d.ts +0 -7
- package/dist/utils/qrcode.test.js +0 -14
|
@@ -1,307 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* End-to-end integration test for handy-cli bin/dev.js
|
|
3
|
-
*
|
|
4
|
-
* This test demonstrates how to properly spawn and control the CLI process
|
|
5
|
-
* for integration testing using promise-based approach for deterministic results.
|
|
6
|
-
*/
|
|
7
|
-
import { authGetToken, getOrCreateSecretKey } from '#auth/auth';
|
|
8
|
-
import { SessionService } from '#session/service';
|
|
9
|
-
import { SocketClient } from '#socket/client';
|
|
10
|
-
import { getConfig } from '#utils/config';
|
|
11
|
-
import { expect } from 'chai';
|
|
12
|
-
import { execSync, spawn } from 'node:child_process';
|
|
13
|
-
import { join } from 'node:path';
|
|
14
|
-
describe('CLI bin/dev.js Integration <-> server <-> mocked client', function () {
|
|
15
|
-
this.timeout(30_000); // 30 second timeout for integration tests
|
|
16
|
-
let cliProcess = null;
|
|
17
|
-
const projectRoot = process.cwd();
|
|
18
|
-
const binPath = join(projectRoot, 'bin', 'dev.js');
|
|
19
|
-
// Clean up Claude sessions before each test
|
|
20
|
-
beforeEach(() => {
|
|
21
|
-
// Remove any existing Claude sessions for the playground project
|
|
22
|
-
const claudeProjectPath = process.env.HOME + '/.claude/projects/-Users-kirilldubovitskiy-projects-handy-cli-claude-cli-playground-project';
|
|
23
|
-
try {
|
|
24
|
-
execSync(`rm -rf "${claudeProjectPath}"`, { stdio: 'ignore' });
|
|
25
|
-
}
|
|
26
|
-
catch {
|
|
27
|
-
// Ignore errors if directory doesn't exist
|
|
28
|
-
}
|
|
29
|
-
});
|
|
30
|
-
afterEach(() => {
|
|
31
|
-
// Clean up any running processes
|
|
32
|
-
if (cliProcess && !cliProcess.killed) {
|
|
33
|
-
cliProcess.kill('SIGINT');
|
|
34
|
-
cliProcess = null;
|
|
35
|
-
}
|
|
36
|
-
});
|
|
37
|
-
it('Sanity check: should show version with --version flag', (done) => {
|
|
38
|
-
const outputs = [];
|
|
39
|
-
let processExited = false;
|
|
40
|
-
cliProcess = spawn('node', [binPath, '--version'], {
|
|
41
|
-
cwd: projectRoot,
|
|
42
|
-
env: { ...process.env },
|
|
43
|
-
stdio: ['pipe', 'pipe', 'pipe']
|
|
44
|
-
});
|
|
45
|
-
cliProcess.stdout?.on('data', (data) => {
|
|
46
|
-
outputs.push(data.toString());
|
|
47
|
-
});
|
|
48
|
-
cliProcess.on('exit', (code, _signal) => {
|
|
49
|
-
processExited = true;
|
|
50
|
-
// Should exit successfully
|
|
51
|
-
expect(code).to.equal(0);
|
|
52
|
-
// Should show version
|
|
53
|
-
const output = outputs.join('');
|
|
54
|
-
expect(output).to.match(/\d+\.\d+\.\d+/); // Matches semantic version
|
|
55
|
-
done();
|
|
56
|
-
});
|
|
57
|
-
cliProcess.on('error', (error) => {
|
|
58
|
-
done(error);
|
|
59
|
-
});
|
|
60
|
-
// Set a timeout in case process doesn't exit
|
|
61
|
-
setTimeout(() => {
|
|
62
|
-
if (!processExited) {
|
|
63
|
-
cliProcess?.kill('SIGTERM');
|
|
64
|
-
done(new Error('CLI process did not exit within timeout'));
|
|
65
|
-
}
|
|
66
|
-
}, 5000);
|
|
67
|
-
});
|
|
68
|
-
/**
|
|
69
|
-
* Run our bin/dev.js start command and respond to a single client messages
|
|
70
|
-
* that lists files in the directory of the playground project
|
|
71
|
-
* with a single file hello-world.js
|
|
72
|
-
*
|
|
73
|
-
* This test is a bit more complex, because we need to:
|
|
74
|
-
* 1. Spawn the CLI process in a playground directory with a single hello-world.js
|
|
75
|
-
* 2. Wait for the CLI process to start
|
|
76
|
-
* 3. We by default send a message to claude code to show current directory
|
|
77
|
-
* to make sure we are in the expected project + sanity check our system.
|
|
78
|
-
* 4. Extract the session ID from the CLI process output
|
|
79
|
-
* 5. Create a test client and connect to the server
|
|
80
|
-
* 6. Assert client recieves current directory within 5 seconds
|
|
81
|
-
* 7. The client sends a message asking to list files in the toy directory
|
|
82
|
-
* 8. Wait for the response to contain the hello-world.js file for 10 seconds
|
|
83
|
-
* 9. Disconnect the test client
|
|
84
|
-
* 8. Clean up the CLI process
|
|
85
|
-
*/
|
|
86
|
-
it('should run start command and respond to client messages', async () => {
|
|
87
|
-
console.log('=== Starting End-to-End Test ===');
|
|
88
|
-
const playgroundPath = join(projectRoot, 'claude-cli-playground-project');
|
|
89
|
-
// Create a promise-based test runner
|
|
90
|
-
const testResult = await new Promise((resolve, reject) => {
|
|
91
|
-
let sessionId = null;
|
|
92
|
-
let testCompleted = false;
|
|
93
|
-
const cleanup = () => {
|
|
94
|
-
if (cliProcess && !cliProcess.killed) {
|
|
95
|
-
cliProcess.kill('SIGINT');
|
|
96
|
-
cliProcess = null;
|
|
97
|
-
}
|
|
98
|
-
};
|
|
99
|
-
const completeTest = (error) => {
|
|
100
|
-
if (testCompleted)
|
|
101
|
-
return;
|
|
102
|
-
testCompleted = true;
|
|
103
|
-
cleanup();
|
|
104
|
-
if (error) {
|
|
105
|
-
reject(error);
|
|
106
|
-
}
|
|
107
|
-
else {
|
|
108
|
-
resolve();
|
|
109
|
-
}
|
|
110
|
-
};
|
|
111
|
-
// Overall test timeout
|
|
112
|
-
const testTimeout = setTimeout(() => {
|
|
113
|
-
completeTest(new Error('Test timeout: Did not complete within 30 seconds'));
|
|
114
|
-
}, 30_000);
|
|
115
|
-
console.log('Step 1: Spawning CLI process...');
|
|
116
|
-
cliProcess = spawn('node', [binPath, 'start'], {
|
|
117
|
-
cwd: playgroundPath,
|
|
118
|
-
env: { ...process.env },
|
|
119
|
-
stdio: ['pipe', 'pipe', 'pipe']
|
|
120
|
-
});
|
|
121
|
-
cliProcess.stdout?.on('data', (data) => {
|
|
122
|
-
const output = data.toString();
|
|
123
|
-
console.log('CLI stdout:', output);
|
|
124
|
-
// Parse session ID from output
|
|
125
|
-
const sessionMatch = output.match(/Session created: ([a-zA-Z0-9-]+)/);
|
|
126
|
-
if (sessionMatch) {
|
|
127
|
-
sessionId = sessionMatch[1];
|
|
128
|
-
console.log('Step 2: Extracted session ID:', sessionId);
|
|
129
|
-
}
|
|
130
|
-
// Look for ready signal
|
|
131
|
-
if (output.includes('Handy CLI is running') && sessionId) {
|
|
132
|
-
console.log('Step 3: CLI is ready with session ID!');
|
|
133
|
-
clearTimeout(testTimeout);
|
|
134
|
-
createTestClient(sessionId)
|
|
135
|
-
.then(() => completeTest())
|
|
136
|
-
.catch(completeTest);
|
|
137
|
-
}
|
|
138
|
-
});
|
|
139
|
-
cliProcess.stderr?.on('data', (data) => {
|
|
140
|
-
const error = data.toString();
|
|
141
|
-
// Only log real errors, not warnings
|
|
142
|
-
if (!error.includes('ExperimentalWarning') && !error.includes('DeprecationWarning')) {
|
|
143
|
-
console.log('CLI stderr:', error);
|
|
144
|
-
}
|
|
145
|
-
});
|
|
146
|
-
cliProcess.on('exit', (code, signal) => {
|
|
147
|
-
console.log(`CLI process exited with code ${code} and signal ${signal}`);
|
|
148
|
-
if (!testCompleted) {
|
|
149
|
-
completeTest(new Error(`CLI exited unexpectedly with code ${code}`));
|
|
150
|
-
}
|
|
151
|
-
});
|
|
152
|
-
cliProcess.on('error', (error) => {
|
|
153
|
-
console.log('CLI process error:', error);
|
|
154
|
-
completeTest(error);
|
|
155
|
-
});
|
|
156
|
-
});
|
|
157
|
-
async function createTestClient(sessionId) {
|
|
158
|
-
return new Promise((resolve, reject) => {
|
|
159
|
-
const runTestClient = async () => {
|
|
160
|
-
try {
|
|
161
|
-
console.log('Step 4: Creating test client...');
|
|
162
|
-
// Get auth token
|
|
163
|
-
const config = getConfig();
|
|
164
|
-
// This key would normally be read from the QR code from the terminal
|
|
165
|
-
const secret = await getOrCreateSecretKey();
|
|
166
|
-
const authToken = await authGetToken(config.serverUrl, secret);
|
|
167
|
-
console.log('Auth token obtained');
|
|
168
|
-
// Use the parsed session ID
|
|
169
|
-
const session = { id: sessionId };
|
|
170
|
-
const sessionService = new SessionService(config.serverUrl, authToken);
|
|
171
|
-
console.log('Using session:', session.id);
|
|
172
|
-
console.log('Step 5: Connecting test client...');
|
|
173
|
-
const socketClient = new SocketClient({
|
|
174
|
-
authToken,
|
|
175
|
-
serverUrl: config.serverUrl,
|
|
176
|
-
socketPath: config.socketPath,
|
|
177
|
-
});
|
|
178
|
-
let cwdResponseReceived = false;
|
|
179
|
-
let fileListResponseReceived = false;
|
|
180
|
-
let messageSent = false;
|
|
181
|
-
// Listen for updates
|
|
182
|
-
// eslint-disable-next-line complexity
|
|
183
|
-
socketClient.on('update', (update) => {
|
|
184
|
-
console.log('Received update type:', update.content?.t, 'for session:', update.content?.sid);
|
|
185
|
-
// Check if this is for our session
|
|
186
|
-
if (update.content.t === 'new-message' && update.content.sid === session.id) {
|
|
187
|
-
try {
|
|
188
|
-
// Handle nested decryption structure
|
|
189
|
-
let decryptedContent;
|
|
190
|
-
if (typeof update.content.c === 'object' && update.content.c.c && update.content.c.t === 'encrypted') {
|
|
191
|
-
// Nested encrypted structure
|
|
192
|
-
decryptedContent = sessionService.decryptContent(update.content.c.c);
|
|
193
|
-
}
|
|
194
|
-
else if (typeof update.content.c === 'string') {
|
|
195
|
-
decryptedContent = sessionService.decryptContent(update.content.c);
|
|
196
|
-
}
|
|
197
|
-
else {
|
|
198
|
-
// Already decrypted object
|
|
199
|
-
decryptedContent = update.content.c;
|
|
200
|
-
}
|
|
201
|
-
console.log('Decrypted content type:', decryptedContent?.type, 'data type:', decryptedContent?.data?.type);
|
|
202
|
-
// Check if it's a Claude response
|
|
203
|
-
if (typeof decryptedContent === 'object' &&
|
|
204
|
-
decryptedContent !== null &&
|
|
205
|
-
'type' in decryptedContent &&
|
|
206
|
-
decryptedContent.type === 'claude-response') {
|
|
207
|
-
const responseData = JSON.stringify(decryptedContent).toLowerCase();
|
|
208
|
-
console.log('Checking response - cwdResponseReceived:', cwdResponseReceived, 'includes playground:', responseData.includes('claude-cli-playground-project'));
|
|
209
|
-
// Step 6: Check for current working directory (initial command)
|
|
210
|
-
if (!cwdResponseReceived && responseData.includes('claude-cli-playground-project')) {
|
|
211
|
-
console.log('Step 6: Received current working directory response within 5 seconds!');
|
|
212
|
-
cwdResponseReceived = true;
|
|
213
|
-
// Now send the client message to list files
|
|
214
|
-
if (!messageSent) {
|
|
215
|
-
messageSent = true;
|
|
216
|
-
console.log('Step 7: Sending client message to list files...');
|
|
217
|
-
sessionService.sendMessage(session.id, {
|
|
218
|
-
content: 'ls',
|
|
219
|
-
type: 'text-input'
|
|
220
|
-
}).then(() => {
|
|
221
|
-
console.log('Client message sent successfully');
|
|
222
|
-
}).catch((error) => {
|
|
223
|
-
console.error('Failed to send client message:', error);
|
|
224
|
-
reject(error);
|
|
225
|
-
});
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
// Step 8: Check for hello-world.js in file listing
|
|
229
|
-
if (cwdResponseReceived && messageSent) {
|
|
230
|
-
// Log tool results to debug
|
|
231
|
-
if (decryptedContent.data?.type === 'user' && decryptedContent.data?.message?.content) {
|
|
232
|
-
// Check tool results in user messages
|
|
233
|
-
const toolResults = decryptedContent.data.message.content.filter((c) => c.type === 'tool_result');
|
|
234
|
-
// eslint-disable-next-line max-depth
|
|
235
|
-
if (toolResults.length > 0) {
|
|
236
|
-
console.log('Tool results found:', JSON.stringify(toolResults));
|
|
237
|
-
// Check if any tool result contains hello-world.js
|
|
238
|
-
const hasHelloWorld = toolResults.some((result) => result.content && result.content.toLowerCase().includes('hello-world.js'));
|
|
239
|
-
// eslint-disable-next-line max-depth
|
|
240
|
-
if (hasHelloWorld) {
|
|
241
|
-
console.log('Step 8: Found hello-world.js in tool results!');
|
|
242
|
-
fileListResponseReceived = true;
|
|
243
|
-
socketClient.disconnect();
|
|
244
|
-
console.log('=== Test Completed Successfully ===');
|
|
245
|
-
// Give the CLI time to shut down gracefully
|
|
246
|
-
setTimeout(() => {
|
|
247
|
-
if (cliProcess && !cliProcess.killed) {
|
|
248
|
-
cliProcess.kill('SIGINT'); // Use SIGINT for graceful shutdown
|
|
249
|
-
}
|
|
250
|
-
}, 100);
|
|
251
|
-
resolve();
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
// Also check in the full response
|
|
256
|
-
if (responseData.includes('hello-world.js')) {
|
|
257
|
-
console.log('Step 8: Found hello-world.js in response!');
|
|
258
|
-
fileListResponseReceived = true;
|
|
259
|
-
socketClient.disconnect();
|
|
260
|
-
console.log('=== Test Completed Successfully ===');
|
|
261
|
-
// Give the CLI time to shut down gracefully
|
|
262
|
-
setTimeout(() => {
|
|
263
|
-
if (cliProcess && !cliProcess.killed) {
|
|
264
|
-
cliProcess.kill('SIGINT'); // Use SIGINT for graceful shutdown
|
|
265
|
-
}
|
|
266
|
-
}, 100);
|
|
267
|
-
resolve();
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
catch (decryptError) {
|
|
273
|
-
console.log('Failed to decrypt message:', decryptError);
|
|
274
|
-
reject(decryptError);
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
});
|
|
278
|
-
socketClient.connect();
|
|
279
|
-
await socketClient.waitForAuth();
|
|
280
|
-
console.log('Test client connected and authenticated');
|
|
281
|
-
// Don't send any message here - we'll wait for CWD response first
|
|
282
|
-
console.log('Waiting for initial current working directory response...');
|
|
283
|
-
// Set timeouts for two-stage check
|
|
284
|
-
setTimeout(() => {
|
|
285
|
-
if (!cwdResponseReceived) {
|
|
286
|
-
socketClient.disconnect();
|
|
287
|
-
reject(new Error('Did not receive current working directory response within 5 seconds'));
|
|
288
|
-
}
|
|
289
|
-
}, 5000); // 5 second timeout for CWD response
|
|
290
|
-
setTimeout(() => {
|
|
291
|
-
if (cwdResponseReceived && !fileListResponseReceived) {
|
|
292
|
-
socketClient.disconnect();
|
|
293
|
-
reject(new Error('Did not receive hello-world.js in file listing response within 10 seconds'));
|
|
294
|
-
}
|
|
295
|
-
}, 15_000); // 15 second timeout for complete test
|
|
296
|
-
}
|
|
297
|
-
catch (error) {
|
|
298
|
-
console.log('Error in createTestClient:', error);
|
|
299
|
-
reject(error);
|
|
300
|
-
}
|
|
301
|
-
};
|
|
302
|
-
runTestClient();
|
|
303
|
-
});
|
|
304
|
-
}
|
|
305
|
-
return testResult;
|
|
306
|
-
});
|
|
307
|
-
});
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Message handler for handy-cli
|
|
3
|
-
*
|
|
4
|
-
* This module handles incoming messages from the socket server
|
|
5
|
-
* and coordinates between the socket client and Claude CLI.
|
|
6
|
-
*
|
|
7
|
-
* Key responsibilities:
|
|
8
|
-
* - Process incoming text-input messages
|
|
9
|
-
* - Forward messages to Claude CLI
|
|
10
|
-
* - Send Claude responses back through socket
|
|
11
|
-
* - Handle message encryption/decryption (future)
|
|
12
|
-
*
|
|
13
|
-
* Design decisions:
|
|
14
|
-
* - Uses event-driven architecture for loose coupling
|
|
15
|
-
* - Handles only text-input messages initially
|
|
16
|
-
* - Prepared for future encryption support
|
|
17
|
-
*/
|
|
18
|
-
import { ClaudeOptions } from '#claude/claude';
|
|
19
|
-
import { SessionService } from '#session/service';
|
|
20
|
-
import { SocketClient } from '#socket/client';
|
|
21
|
-
import { EventEmitter } from 'node:events';
|
|
22
|
-
export interface MessageHandlerOptions {
|
|
23
|
-
claudeOptions?: Partial<ClaudeOptions>;
|
|
24
|
-
sessionId: string;
|
|
25
|
-
sessionService: SessionService;
|
|
26
|
-
socketClient: SocketClient;
|
|
27
|
-
workingDirectory: string;
|
|
28
|
-
}
|
|
29
|
-
export declare class MessageHandler extends EventEmitter {
|
|
30
|
-
private claude;
|
|
31
|
-
private claudeOptions;
|
|
32
|
-
private sessionId;
|
|
33
|
-
private sessionService;
|
|
34
|
-
private socketClient;
|
|
35
|
-
private workingDirectory;
|
|
36
|
-
constructor(options: MessageHandlerOptions);
|
|
37
|
-
/**
|
|
38
|
-
* Handle initial command directly (for startup)
|
|
39
|
-
*/
|
|
40
|
-
handleInitialCommand(command: string): void;
|
|
41
|
-
/**
|
|
42
|
-
* Start handling messages
|
|
43
|
-
*/
|
|
44
|
-
start(): void;
|
|
45
|
-
/**
|
|
46
|
-
* Stop handling messages
|
|
47
|
-
*/
|
|
48
|
-
stop(): void;
|
|
49
|
-
/**
|
|
50
|
-
* Handle Claude responses
|
|
51
|
-
*/
|
|
52
|
-
private handleClaudeResponse;
|
|
53
|
-
/**
|
|
54
|
-
* Handle text input messages
|
|
55
|
-
*/
|
|
56
|
-
private handleTextInput;
|
|
57
|
-
/**
|
|
58
|
-
* Handle update messages from the server
|
|
59
|
-
*/
|
|
60
|
-
private handleUpdate;
|
|
61
|
-
/**
|
|
62
|
-
* Set up event handlers
|
|
63
|
-
*/
|
|
64
|
-
private setupHandlers;
|
|
65
|
-
}
|
|
@@ -1,187 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Message handler for handy-cli
|
|
3
|
-
*
|
|
4
|
-
* This module handles incoming messages from the socket server
|
|
5
|
-
* and coordinates between the socket client and Claude CLI.
|
|
6
|
-
*
|
|
7
|
-
* Key responsibilities:
|
|
8
|
-
* - Process incoming text-input messages
|
|
9
|
-
* - Forward messages to Claude CLI
|
|
10
|
-
* - Send Claude responses back through socket
|
|
11
|
-
* - Handle message encryption/decryption (future)
|
|
12
|
-
*
|
|
13
|
-
* Design decisions:
|
|
14
|
-
* - Uses event-driven architecture for loose coupling
|
|
15
|
-
* - Handles only text-input messages initially
|
|
16
|
-
* - Prepared for future encryption support
|
|
17
|
-
*/
|
|
18
|
-
import { Claude } from '#claude/claude';
|
|
19
|
-
import { logger } from '#utils/logger';
|
|
20
|
-
import { EventEmitter } from 'node:events';
|
|
21
|
-
// eslint-disable-next-line unicorn/prefer-event-target
|
|
22
|
-
export class MessageHandler extends EventEmitter {
|
|
23
|
-
claude;
|
|
24
|
-
claudeOptions;
|
|
25
|
-
sessionId;
|
|
26
|
-
sessionService;
|
|
27
|
-
socketClient;
|
|
28
|
-
workingDirectory;
|
|
29
|
-
constructor(options) {
|
|
30
|
-
super();
|
|
31
|
-
this.socketClient = options.socketClient;
|
|
32
|
-
this.claude = new Claude();
|
|
33
|
-
this.sessionService = options.sessionService;
|
|
34
|
-
this.sessionId = options.sessionId;
|
|
35
|
-
this.workingDirectory = options.workingDirectory;
|
|
36
|
-
this.claudeOptions = options.claudeOptions || {};
|
|
37
|
-
this.setupHandlers();
|
|
38
|
-
}
|
|
39
|
-
/**
|
|
40
|
-
* Handle initial command directly (for startup)
|
|
41
|
-
*/
|
|
42
|
-
handleInitialCommand(command) {
|
|
43
|
-
logger.info('Handling initial command:', command);
|
|
44
|
-
// Run Claude command
|
|
45
|
-
this.claude.runClaudeCodeTurn(command, undefined, // No session ID for initial command
|
|
46
|
-
{
|
|
47
|
-
workingDirectory: this.workingDirectory,
|
|
48
|
-
...this.claudeOptions
|
|
49
|
-
});
|
|
50
|
-
}
|
|
51
|
-
/**
|
|
52
|
-
* Start handling messages
|
|
53
|
-
*/
|
|
54
|
-
start() {
|
|
55
|
-
logger.info('Message handler started');
|
|
56
|
-
// Any initialization logic can go here
|
|
57
|
-
}
|
|
58
|
-
/**
|
|
59
|
-
* Stop handling messages
|
|
60
|
-
*/
|
|
61
|
-
stop() {
|
|
62
|
-
logger.info('Message handler stopped');
|
|
63
|
-
this.claude.kill();
|
|
64
|
-
}
|
|
65
|
-
/**
|
|
66
|
-
* Handle Claude responses
|
|
67
|
-
*/
|
|
68
|
-
async handleClaudeResponse(response) {
|
|
69
|
-
logger.info('Claude response:', JSON.stringify(response, null, 2));
|
|
70
|
-
try {
|
|
71
|
-
// Send the response to the server session
|
|
72
|
-
await this.sessionService.sendMessage(this.sessionId, {
|
|
73
|
-
data: response,
|
|
74
|
-
type: 'claude-response'
|
|
75
|
-
});
|
|
76
|
-
// Emit for local handling
|
|
77
|
-
this.emit('claudeResponse', response);
|
|
78
|
-
}
|
|
79
|
-
catch (error) {
|
|
80
|
-
logger.error('Failed to send Claude response to server:', error);
|
|
81
|
-
this.emit('error', error);
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
/**
|
|
85
|
-
* Handle text input messages
|
|
86
|
-
*/
|
|
87
|
-
handleTextInput(message) {
|
|
88
|
-
logger.info('Received text input:', message.content);
|
|
89
|
-
// Run Claude command (kills any existing process automatically)
|
|
90
|
-
this.claude.runClaudeCodeTurn(message.content, undefined, // No session resuming for now
|
|
91
|
-
{
|
|
92
|
-
workingDirectory: this.workingDirectory,
|
|
93
|
-
...this.claudeOptions
|
|
94
|
-
});
|
|
95
|
-
}
|
|
96
|
-
/**
|
|
97
|
-
* Handle update messages from the server
|
|
98
|
-
*/
|
|
99
|
-
handleUpdate(update) {
|
|
100
|
-
logger.debug('Received update:', JSON.stringify(update, null, 2));
|
|
101
|
-
// Check if this is a new message for our session
|
|
102
|
-
if (update.content.t === 'new-message') {
|
|
103
|
-
const { c, sid } = update.content;
|
|
104
|
-
// Verify this message is for our session
|
|
105
|
-
if (sid !== this.sessionId) {
|
|
106
|
-
logger.debug('Message for different session, ignoring');
|
|
107
|
-
return;
|
|
108
|
-
}
|
|
109
|
-
try {
|
|
110
|
-
// Log the raw content structure for debugging
|
|
111
|
-
logger.debug('Raw content (c):', JSON.stringify(c));
|
|
112
|
-
logger.debug('Content type:', typeof c);
|
|
113
|
-
// Check if content is already a string or needs extraction
|
|
114
|
-
let encryptedContent;
|
|
115
|
-
if (typeof c === 'string') {
|
|
116
|
-
// Direct string content
|
|
117
|
-
encryptedContent = c;
|
|
118
|
-
}
|
|
119
|
-
else if (typeof c === 'object' && c !== null && 'c' in c && typeof c.c === 'string') {
|
|
120
|
-
// Nested structure: { c: 'base64string', t: 'encrypted' }
|
|
121
|
-
logger.debug('Extracting from nested structure');
|
|
122
|
-
encryptedContent = c.c;
|
|
123
|
-
}
|
|
124
|
-
else {
|
|
125
|
-
logger.error('Invalid content structure:', c);
|
|
126
|
-
return;
|
|
127
|
-
}
|
|
128
|
-
// Decrypt the content
|
|
129
|
-
const decryptedContent = this.sessionService.decryptContent(encryptedContent);
|
|
130
|
-
logger.debug('Decrypted content:', decryptedContent);
|
|
131
|
-
// Type guard for the decrypted content
|
|
132
|
-
if (typeof decryptedContent === 'object' &&
|
|
133
|
-
decryptedContent !== null &&
|
|
134
|
-
'type' in decryptedContent) {
|
|
135
|
-
const message = decryptedContent;
|
|
136
|
-
// Handle the message based on type
|
|
137
|
-
if (message.type === 'text-input') {
|
|
138
|
-
this.handleTextInput(decryptedContent);
|
|
139
|
-
}
|
|
140
|
-
else if (message.type === 'claude-response') {
|
|
141
|
-
// This is our own claude-response being echoed back from the server
|
|
142
|
-
// We can safely ignore it or log it for debugging
|
|
143
|
-
logger.debug('Received claude-response echo from server:', message);
|
|
144
|
-
}
|
|
145
|
-
else {
|
|
146
|
-
logger.warn('Unknown message type:', message.type);
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
else {
|
|
150
|
-
logger.error('Invalid message format');
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
catch (error) {
|
|
154
|
-
logger.error('Failed to process message:', error);
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
/**
|
|
159
|
-
* Set up event handlers
|
|
160
|
-
*/
|
|
161
|
-
setupHandlers() {
|
|
162
|
-
// Handle updates from socket server
|
|
163
|
-
this.socketClient.on('update', (update) => {
|
|
164
|
-
this.handleUpdate(update);
|
|
165
|
-
});
|
|
166
|
-
// Handle Claude responses
|
|
167
|
-
this.claude.on('response', (response) => {
|
|
168
|
-
this.handleClaudeResponse(response);
|
|
169
|
-
});
|
|
170
|
-
// Handle Claude output (non-JSON)
|
|
171
|
-
this.claude.on('output', (output) => {
|
|
172
|
-
logger.debug('Claude output:', output);
|
|
173
|
-
// For now, we'll log non-JSON output
|
|
174
|
-
// In the future, we might want to send this to the client
|
|
175
|
-
});
|
|
176
|
-
// Handle Claude errors
|
|
177
|
-
this.claude.on('error', (error) => {
|
|
178
|
-
logger.error('Claude error:', error);
|
|
179
|
-
this.emit('error', error);
|
|
180
|
-
});
|
|
181
|
-
// Handle Claude exit
|
|
182
|
-
this.claude.on('exit', (exitInfo) => {
|
|
183
|
-
logger.info('Claude exited:', exitInfo);
|
|
184
|
-
this.emit('claudeExit', exitInfo);
|
|
185
|
-
});
|
|
186
|
-
}
|
|
187
|
-
}
|
package/dist/index.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { run } from '@oclif/core';
|
package/dist/index.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { run } from '@oclif/core';
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Session service for managing handy-server sessions
|
|
3
|
-
*/
|
|
4
|
-
import type { CreateSessionResponse, GetMessagesResponse, SendMessageResponse } from '#session/types';
|
|
5
|
-
export declare class SessionService {
|
|
6
|
-
private readonly serverUrl;
|
|
7
|
-
private readonly authToken;
|
|
8
|
-
constructor(serverUrl: string, authToken: string);
|
|
9
|
-
/**
|
|
10
|
-
* Create a new session or load existing one with the given tag
|
|
11
|
-
*/
|
|
12
|
-
createSession(tag: string): Promise<CreateSessionResponse>;
|
|
13
|
-
/**
|
|
14
|
-
* Decrypt a message content
|
|
15
|
-
* Note: In real implementation, this would use proper decryption
|
|
16
|
-
*/
|
|
17
|
-
decryptContent(encryptedContent: string): unknown;
|
|
18
|
-
/**
|
|
19
|
-
* Get messages from a session
|
|
20
|
-
*/
|
|
21
|
-
getMessages(sessionId: string): Promise<GetMessagesResponse>;
|
|
22
|
-
/**
|
|
23
|
-
* Send a message to a session
|
|
24
|
-
* Note: In real implementation, we'd encrypt the content before sending
|
|
25
|
-
*/
|
|
26
|
-
sendMessage(sessionId: string, content: unknown): Promise<SendMessageResponse>;
|
|
27
|
-
}
|