happy-coder 0.1.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/README.md +38 -0
- package/bin/happy +2 -0
- package/bin/happy.cmd +2 -0
- package/dist/auth/auth.d.ts +38 -0
- package/dist/auth/auth.js +76 -0
- package/dist/auth/auth.test.d.ts +7 -0
- package/dist/auth/auth.test.js +96 -0
- package/dist/auth/crypto.d.ts +25 -0
- package/dist/auth/crypto.js +36 -0
- package/dist/claude/claude.d.ts +54 -0
- package/dist/claude/claude.js +170 -0
- package/dist/claude/claude.test.d.ts +7 -0
- package/dist/claude/claude.test.js +130 -0
- package/dist/claude/types.d.ts +37 -0
- package/dist/claude/types.js +7 -0
- package/dist/commands/start.d.ts +38 -0
- package/dist/commands/start.js +161 -0
- package/dist/commands/start.test.d.ts +7 -0
- package/dist/commands/start.test.js +307 -0
- package/dist/handlers/message-handler.d.ts +65 -0
- package/dist/handlers/message-handler.js +187 -0
- package/dist/index.cjs +603 -0
- package/dist/index.d.cts +1 -0
- package/dist/index.d.mts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/index.mjs +583 -0
- package/dist/session/service.d.ts +27 -0
- package/dist/session/service.js +93 -0
- package/dist/session/service.test.d.ts +7 -0
- package/dist/session/service.test.js +71 -0
- package/dist/session/types.d.ts +44 -0
- package/dist/session/types.js +4 -0
- package/dist/socket/client.d.ts +50 -0
- package/dist/socket/client.js +136 -0
- package/dist/socket/client.test.d.ts +7 -0
- package/dist/socket/client.test.js +74 -0
- package/dist/socket/types.d.ts +80 -0
- package/dist/socket/types.js +12 -0
- package/dist/utils/config.d.ts +22 -0
- package/dist/utils/config.js +23 -0
- package/dist/utils/logger.d.ts +26 -0
- package/dist/utils/logger.js +60 -0
- package/dist/utils/paths.d.ts +18 -0
- package/dist/utils/paths.js +24 -0
- package/dist/utils/qrcode.d.ts +19 -0
- package/dist/utils/qrcode.js +37 -0
- package/dist/utils/qrcode.test.d.ts +7 -0
- package/dist/utils/qrcode.test.js +14 -0
- package/package.json +60 -0
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for the session service module
|
|
3
|
+
*
|
|
4
|
+
* This test verifies the complete session workflow: create session, send message, read message
|
|
5
|
+
* using the real handy server API. No mocking is used as per project requirements.
|
|
6
|
+
*/
|
|
7
|
+
import { authGetToken, getOrCreateSecretKey } from '#auth/auth';
|
|
8
|
+
import { getConfig } from '#utils/config';
|
|
9
|
+
import { expect } from 'chai';
|
|
10
|
+
import { SessionService } from './service.js';
|
|
11
|
+
describe('SessionService', () => {
|
|
12
|
+
let sessionService;
|
|
13
|
+
let authToken;
|
|
14
|
+
before(async () => {
|
|
15
|
+
// Get auth token for tests
|
|
16
|
+
const config = getConfig();
|
|
17
|
+
const secret = await getOrCreateSecretKey();
|
|
18
|
+
authToken = await authGetToken(config.serverUrl, secret);
|
|
19
|
+
// Create session service instance
|
|
20
|
+
sessionService = new SessionService(config.serverUrl, authToken);
|
|
21
|
+
});
|
|
22
|
+
it('should create session, send message, and read message back', async () => {
|
|
23
|
+
// 1. Create a session
|
|
24
|
+
const tag = `test-session-${Date.now()}`;
|
|
25
|
+
const sessionResponse = await sessionService.createSession(tag);
|
|
26
|
+
expect(sessionResponse).to.be.an('object');
|
|
27
|
+
expect(sessionResponse.session).to.be.an('object');
|
|
28
|
+
expect(sessionResponse.session.id).to.be.a('string');
|
|
29
|
+
expect(sessionResponse.session.tag).to.equal(tag);
|
|
30
|
+
expect(sessionResponse.session.seq).to.be.a('number');
|
|
31
|
+
expect(sessionResponse.session.createdAt).to.be.a('number');
|
|
32
|
+
expect(sessionResponse.session.updatedAt).to.be.a('number');
|
|
33
|
+
const sessionId = sessionResponse.session.id;
|
|
34
|
+
// 2. Send a message to the session
|
|
35
|
+
const messageContent = {
|
|
36
|
+
content: 'Hello, this is a test message from the session service test',
|
|
37
|
+
type: 'text-input'
|
|
38
|
+
};
|
|
39
|
+
const sendResponse = await sessionService.sendMessage(sessionId, messageContent);
|
|
40
|
+
expect(sendResponse).to.be.an('object');
|
|
41
|
+
expect(sendResponse.message).to.be.an('object');
|
|
42
|
+
expect(sendResponse.message.id).to.be.a('string');
|
|
43
|
+
expect(sendResponse.message.seq).to.be.a('number');
|
|
44
|
+
expect(sendResponse.message.content).to.be.an('object');
|
|
45
|
+
expect(sendResponse.message.content.c).to.be.a('string');
|
|
46
|
+
expect(sendResponse.message.content.t).to.equal('encrypted');
|
|
47
|
+
expect(sendResponse.message.createdAt).to.be.a('number');
|
|
48
|
+
expect(sendResponse.message.updatedAt).to.be.a('number');
|
|
49
|
+
// 3. Read messages from the session
|
|
50
|
+
const getResponse = await sessionService.getMessages(sessionId);
|
|
51
|
+
expect(getResponse).to.be.an('object');
|
|
52
|
+
expect(getResponse.messages).to.be.an('array');
|
|
53
|
+
expect(getResponse.messages.length).to.be.greaterThan(0);
|
|
54
|
+
// Find our message (should be the first one since it's ordered by createdAt desc)
|
|
55
|
+
const retrievedMessage = getResponse.messages[0];
|
|
56
|
+
expect(retrievedMessage).to.be.an('object');
|
|
57
|
+
expect(retrievedMessage.id).to.equal(sendResponse.message.id);
|
|
58
|
+
expect(retrievedMessage.seq).to.equal(sendResponse.message.seq);
|
|
59
|
+
expect(retrievedMessage.content).to.be.an('object');
|
|
60
|
+
expect(retrievedMessage.content.c).to.equal(sendResponse.message.content.c);
|
|
61
|
+
expect(retrievedMessage.content.t).to.equal('encrypted');
|
|
62
|
+
// 4. Decrypt the retrieved message and verify it matches what we sent
|
|
63
|
+
const decryptedContent = sessionService.decryptContent(retrievedMessage.content.c);
|
|
64
|
+
expect(decryptedContent).to.deep.equal(messageContent);
|
|
65
|
+
console.log('✅ Complete workflow test passed:');
|
|
66
|
+
console.log(` - Created session: ${sessionId}`);
|
|
67
|
+
console.log(` - Sent message with seq: ${sendResponse.message.seq}`);
|
|
68
|
+
console.log(` - Retrieved ${getResponse.messages.length} messages`);
|
|
69
|
+
console.log(` - Decrypted content matches original: ${JSON.stringify(messageContent)}`);
|
|
70
|
+
});
|
|
71
|
+
});
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session types for handy-server integration
|
|
3
|
+
*/
|
|
4
|
+
export interface Session {
|
|
5
|
+
createdAt: number;
|
|
6
|
+
id: string;
|
|
7
|
+
seq: number;
|
|
8
|
+
tag: string;
|
|
9
|
+
updatedAt: number;
|
|
10
|
+
}
|
|
11
|
+
export interface SessionMessage {
|
|
12
|
+
content: MessageContent;
|
|
13
|
+
createdAt: number;
|
|
14
|
+
id: string;
|
|
15
|
+
seq: number;
|
|
16
|
+
updatedAt: number;
|
|
17
|
+
}
|
|
18
|
+
export interface MessageContent {
|
|
19
|
+
c: string;
|
|
20
|
+
t: 'encrypted';
|
|
21
|
+
}
|
|
22
|
+
export interface CreateSessionResponse {
|
|
23
|
+
session: Session;
|
|
24
|
+
}
|
|
25
|
+
export interface SendMessageResponse {
|
|
26
|
+
message: SessionMessage;
|
|
27
|
+
}
|
|
28
|
+
export interface ListSessionsResponse {
|
|
29
|
+
sessions: Session[];
|
|
30
|
+
}
|
|
31
|
+
export interface GetMessagesResponse {
|
|
32
|
+
messages: SessionMessage[];
|
|
33
|
+
}
|
|
34
|
+
export interface SocketUpdate {
|
|
35
|
+
content: {
|
|
36
|
+
c: string;
|
|
37
|
+
mid: string;
|
|
38
|
+
sid: string;
|
|
39
|
+
t: 'new-message';
|
|
40
|
+
};
|
|
41
|
+
createdAt: number;
|
|
42
|
+
id: string;
|
|
43
|
+
seq: number;
|
|
44
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Socket.IO client for handy-cli
|
|
3
|
+
*
|
|
4
|
+
* This module manages the WebSocket connection to the handy server.
|
|
5
|
+
* It handles authentication, connection lifecycle, and message routing.
|
|
6
|
+
*
|
|
7
|
+
* Key responsibilities:
|
|
8
|
+
* - Establish authenticated socket connection
|
|
9
|
+
* - Handle reconnection logic
|
|
10
|
+
* - Route messages between server and local handlers
|
|
11
|
+
* - Manage connection state
|
|
12
|
+
*
|
|
13
|
+
* Design decisions:
|
|
14
|
+
* - Uses socket.io-client for compatibility with server
|
|
15
|
+
* - Auth token passed in handshake for authentication
|
|
16
|
+
* - Automatic reconnection with exponential backoff
|
|
17
|
+
* - Event emitter pattern for decoupled message handling
|
|
18
|
+
*/
|
|
19
|
+
import { EventEmitter } from 'node:events';
|
|
20
|
+
export interface SocketClientOptions {
|
|
21
|
+
authToken: string;
|
|
22
|
+
serverUrl: string;
|
|
23
|
+
socketPath: string;
|
|
24
|
+
}
|
|
25
|
+
export declare class SocketClient extends EventEmitter {
|
|
26
|
+
private isConnected;
|
|
27
|
+
private options;
|
|
28
|
+
private socket;
|
|
29
|
+
constructor(options: SocketClientOptions);
|
|
30
|
+
/**
|
|
31
|
+
* Connect to the socket server
|
|
32
|
+
*/
|
|
33
|
+
connect(): void;
|
|
34
|
+
/**
|
|
35
|
+
* Disconnect from the socket server
|
|
36
|
+
*/
|
|
37
|
+
disconnect(): void;
|
|
38
|
+
/**
|
|
39
|
+
* Check if socket is connected
|
|
40
|
+
*/
|
|
41
|
+
getIsConnected(): boolean;
|
|
42
|
+
/**
|
|
43
|
+
* Wait for socket to be authenticated
|
|
44
|
+
*/
|
|
45
|
+
waitForAuth(): Promise<string>;
|
|
46
|
+
/**
|
|
47
|
+
* Set up socket event handlers
|
|
48
|
+
*/
|
|
49
|
+
private setupEventHandlers;
|
|
50
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Socket.IO client for handy-cli
|
|
3
|
+
*
|
|
4
|
+
* This module manages the WebSocket connection to the handy server.
|
|
5
|
+
* It handles authentication, connection lifecycle, and message routing.
|
|
6
|
+
*
|
|
7
|
+
* Key responsibilities:
|
|
8
|
+
* - Establish authenticated socket connection
|
|
9
|
+
* - Handle reconnection logic
|
|
10
|
+
* - Route messages between server and local handlers
|
|
11
|
+
* - Manage connection state
|
|
12
|
+
*
|
|
13
|
+
* Design decisions:
|
|
14
|
+
* - Uses socket.io-client for compatibility with server
|
|
15
|
+
* - Auth token passed in handshake for authentication
|
|
16
|
+
* - Automatic reconnection with exponential backoff
|
|
17
|
+
* - Event emitter pattern for decoupled message handling
|
|
18
|
+
*/
|
|
19
|
+
import { logger } from '#utils/logger';
|
|
20
|
+
import { EventEmitter } from 'node:events';
|
|
21
|
+
import { io } from 'socket.io-client';
|
|
22
|
+
// eslint-disable-next-line unicorn/prefer-event-target
|
|
23
|
+
export class SocketClient extends EventEmitter {
|
|
24
|
+
isConnected = false;
|
|
25
|
+
options;
|
|
26
|
+
socket = null;
|
|
27
|
+
constructor(options) {
|
|
28
|
+
super();
|
|
29
|
+
this.options = options;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Connect to the socket server
|
|
33
|
+
*/
|
|
34
|
+
connect() {
|
|
35
|
+
if (this.socket) {
|
|
36
|
+
logger.warn('Socket already connected');
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
logger.info('Connecting to socket server...');
|
|
40
|
+
logger.debug(`Server URL: ${this.options.serverUrl}`);
|
|
41
|
+
logger.debug(`Socket path: ${this.options.socketPath}`);
|
|
42
|
+
this.socket = io(this.options.serverUrl, {
|
|
43
|
+
auth: {
|
|
44
|
+
token: this.options.authToken
|
|
45
|
+
},
|
|
46
|
+
path: this.options.socketPath,
|
|
47
|
+
reconnection: true,
|
|
48
|
+
reconnectionAttempts: Infinity,
|
|
49
|
+
reconnectionDelay: 1000,
|
|
50
|
+
reconnectionDelayMax: 5000,
|
|
51
|
+
transports: ['websocket', 'polling'],
|
|
52
|
+
withCredentials: true
|
|
53
|
+
});
|
|
54
|
+
this.setupEventHandlers();
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Disconnect from the socket server
|
|
58
|
+
*/
|
|
59
|
+
disconnect() {
|
|
60
|
+
if (this.socket) {
|
|
61
|
+
logger.info('Disconnecting socket...');
|
|
62
|
+
this.socket.disconnect();
|
|
63
|
+
this.socket = null;
|
|
64
|
+
this.isConnected = false;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Check if socket is connected
|
|
69
|
+
*/
|
|
70
|
+
getIsConnected() {
|
|
71
|
+
return this.isConnected;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Wait for socket to be authenticated
|
|
75
|
+
*/
|
|
76
|
+
async waitForAuth() {
|
|
77
|
+
return new Promise((resolve, reject) => {
|
|
78
|
+
const timeout = setTimeout(() => {
|
|
79
|
+
reject(new Error('Authentication timeout'));
|
|
80
|
+
}, 10_000); // 10 second timeout
|
|
81
|
+
this.once('authenticated', (user) => {
|
|
82
|
+
clearTimeout(timeout);
|
|
83
|
+
resolve(user);
|
|
84
|
+
});
|
|
85
|
+
this.once('authError', () => {
|
|
86
|
+
clearTimeout(timeout);
|
|
87
|
+
reject(new Error('Authentication failed'));
|
|
88
|
+
});
|
|
89
|
+
this.once('error', (error) => {
|
|
90
|
+
clearTimeout(timeout);
|
|
91
|
+
reject(error);
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Set up socket event handlers
|
|
97
|
+
*/
|
|
98
|
+
setupEventHandlers() {
|
|
99
|
+
if (!this.socket)
|
|
100
|
+
return;
|
|
101
|
+
// Connection events
|
|
102
|
+
this.socket.on('connect', () => {
|
|
103
|
+
logger.info('Socket connected successfully');
|
|
104
|
+
this.isConnected = true;
|
|
105
|
+
this.emit('connected');
|
|
106
|
+
});
|
|
107
|
+
this.socket.on('disconnect', (reason) => {
|
|
108
|
+
logger.warn('Socket disconnected:', reason);
|
|
109
|
+
this.isConnected = false;
|
|
110
|
+
this.emit('disconnected', reason);
|
|
111
|
+
});
|
|
112
|
+
this.socket.on('connect_error', (error) => {
|
|
113
|
+
logger.error('Socket connection error:', error.message);
|
|
114
|
+
this.emit('error', error);
|
|
115
|
+
});
|
|
116
|
+
// Server events
|
|
117
|
+
this.socket.on('auth', (data) => {
|
|
118
|
+
if (data.success) {
|
|
119
|
+
logger.info('Socket authenticated successfully for user:', data.user);
|
|
120
|
+
this.emit('authenticated', data.user);
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
logger.error('Socket authentication failed');
|
|
124
|
+
this.emit('authError');
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
this.socket.on('error', (data) => {
|
|
128
|
+
logger.error('Server error:', data.message);
|
|
129
|
+
this.emit('serverError', data.message);
|
|
130
|
+
});
|
|
131
|
+
this.socket.on('update', (data) => {
|
|
132
|
+
logger.debug('Received update:', data);
|
|
133
|
+
this.emit('update', data);
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for the socket client module
|
|
3
|
+
*
|
|
4
|
+
* These tests verify socket connection and authentication with the real server.
|
|
5
|
+
* No mocking is used as per project requirements.
|
|
6
|
+
*/
|
|
7
|
+
import { authGetToken, getOrCreateSecretKey } from '#auth/auth';
|
|
8
|
+
import { getConfig } from '#utils/config';
|
|
9
|
+
import { expect } from 'chai';
|
|
10
|
+
import { SocketClient } from './client.js';
|
|
11
|
+
describe('SocketClient', () => {
|
|
12
|
+
let client = null;
|
|
13
|
+
let authToken;
|
|
14
|
+
before(async () => {
|
|
15
|
+
// Get auth token for tests
|
|
16
|
+
const config = getConfig();
|
|
17
|
+
const secret = await getOrCreateSecretKey();
|
|
18
|
+
authToken = await authGetToken(config.serverUrl, secret);
|
|
19
|
+
});
|
|
20
|
+
afterEach(() => {
|
|
21
|
+
// Clean up socket connection
|
|
22
|
+
if (client) {
|
|
23
|
+
client.disconnect();
|
|
24
|
+
client = null;
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
describe('connection', () => {
|
|
28
|
+
it('should connect to the server with valid auth token', async () => {
|
|
29
|
+
const config = getConfig();
|
|
30
|
+
client = new SocketClient({
|
|
31
|
+
authToken,
|
|
32
|
+
serverUrl: config.serverUrl,
|
|
33
|
+
socketPath: config.socketPath
|
|
34
|
+
});
|
|
35
|
+
client.connect();
|
|
36
|
+
// Wait for authentication
|
|
37
|
+
const user = await client.waitForAuth();
|
|
38
|
+
expect(user).to.be.a('string');
|
|
39
|
+
expect(client.getIsConnected()).to.equal(true);
|
|
40
|
+
});
|
|
41
|
+
it('should emit error with invalid auth token', async () => {
|
|
42
|
+
const config = getConfig();
|
|
43
|
+
client = new SocketClient({
|
|
44
|
+
authToken: 'invalid-token',
|
|
45
|
+
serverUrl: config.serverUrl,
|
|
46
|
+
socketPath: config.socketPath
|
|
47
|
+
});
|
|
48
|
+
client.connect();
|
|
49
|
+
// Should reject with auth error
|
|
50
|
+
try {
|
|
51
|
+
await client.waitForAuth();
|
|
52
|
+
expect.fail('Should have thrown auth error');
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
expect(error.message).to.include('Authentication');
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
describe('events', () => {
|
|
60
|
+
it('should emit connected event on successful connection', (done) => {
|
|
61
|
+
const config = getConfig();
|
|
62
|
+
client = new SocketClient({
|
|
63
|
+
authToken,
|
|
64
|
+
serverUrl: config.serverUrl,
|
|
65
|
+
socketPath: config.socketPath
|
|
66
|
+
});
|
|
67
|
+
client.once('connected', () => {
|
|
68
|
+
expect(client.getIsConnected()).to.equal(true);
|
|
69
|
+
done();
|
|
70
|
+
});
|
|
71
|
+
client.connect();
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
});
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Socket message types for handy-cli
|
|
3
|
+
*
|
|
4
|
+
* This module defines TypeScript interfaces for socket communication with the handy server.
|
|
5
|
+
* These types mirror the server's message format for type-safe communication.
|
|
6
|
+
*
|
|
7
|
+
* Key design decisions:
|
|
8
|
+
* - All messages are strongly typed to prevent runtime errors
|
|
9
|
+
* - Message content is encrypted using the session's encryption key
|
|
10
|
+
* - Types match the server's Prisma JSON types exactly
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Base message content structure for encrypted messages
|
|
14
|
+
*/
|
|
15
|
+
export interface SessionMessageContent {
|
|
16
|
+
c: string;
|
|
17
|
+
t: 'encrypted';
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Update body for new messages
|
|
21
|
+
*/
|
|
22
|
+
export interface UpdateBody {
|
|
23
|
+
c: SessionMessageContent | string;
|
|
24
|
+
mid: string;
|
|
25
|
+
sid: string;
|
|
26
|
+
t: 'new-message';
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Update event from server
|
|
30
|
+
*/
|
|
31
|
+
export interface Update {
|
|
32
|
+
content: UpdateBody;
|
|
33
|
+
createdAt: number;
|
|
34
|
+
id: string;
|
|
35
|
+
seq: number;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Socket events from server to client
|
|
39
|
+
*/
|
|
40
|
+
export interface ServerToClientEvents {
|
|
41
|
+
auth: (data: {
|
|
42
|
+
success: boolean;
|
|
43
|
+
user: string;
|
|
44
|
+
}) => void;
|
|
45
|
+
error: (data: {
|
|
46
|
+
message: string;
|
|
47
|
+
}) => void;
|
|
48
|
+
update: (data: Update) => void;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Socket events from client to server
|
|
52
|
+
*/
|
|
53
|
+
export interface ClientToServerEvents {
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Client message types that we'll handle locally
|
|
57
|
+
*/
|
|
58
|
+
export interface TextInputMessage {
|
|
59
|
+
content: string;
|
|
60
|
+
type: 'text-input';
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Session information
|
|
64
|
+
*/
|
|
65
|
+
export interface Session {
|
|
66
|
+
createdAt: number;
|
|
67
|
+
id: string;
|
|
68
|
+
seq: number;
|
|
69
|
+
updatedAt: number;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Session message from API
|
|
73
|
+
*/
|
|
74
|
+
export interface SessionMessage {
|
|
75
|
+
content: SessionMessageContent;
|
|
76
|
+
createdAt: number;
|
|
77
|
+
id: string;
|
|
78
|
+
seq: number;
|
|
79
|
+
updatedAt: number;
|
|
80
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Socket message types for handy-cli
|
|
3
|
+
*
|
|
4
|
+
* This module defines TypeScript interfaces for socket communication with the handy server.
|
|
5
|
+
* These types mirror the server's message format for type-safe communication.
|
|
6
|
+
*
|
|
7
|
+
* Key design decisions:
|
|
8
|
+
* - All messages are strongly typed to prevent runtime errors
|
|
9
|
+
* - Message content is encrypted using the session's encryption key
|
|
10
|
+
* - Types match the server's Prisma JSON types exactly
|
|
11
|
+
*/
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration management for handy-cli
|
|
3
|
+
*
|
|
4
|
+
* This module provides hardcoded configuration values for the application.
|
|
5
|
+
*
|
|
6
|
+
* Key responsibilities:
|
|
7
|
+
* - Provide typed configuration object
|
|
8
|
+
* - Centralize configuration constants
|
|
9
|
+
*
|
|
10
|
+
* Design decisions:
|
|
11
|
+
* - Hardcoded values instead of environment variables for simplicity
|
|
12
|
+
* - Server URL points to the known handy-api server
|
|
13
|
+
* - Socket path uses a known path for session updates
|
|
14
|
+
*/
|
|
15
|
+
export interface Config {
|
|
16
|
+
serverUrl: string;
|
|
17
|
+
socketPath: string;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Get the application configuration
|
|
21
|
+
*/
|
|
22
|
+
export declare function getConfig(): Config;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration management for handy-cli
|
|
3
|
+
*
|
|
4
|
+
* This module provides hardcoded configuration values for the application.
|
|
5
|
+
*
|
|
6
|
+
* Key responsibilities:
|
|
7
|
+
* - Provide typed configuration object
|
|
8
|
+
* - Centralize configuration constants
|
|
9
|
+
*
|
|
10
|
+
* Design decisions:
|
|
11
|
+
* - Hardcoded values instead of environment variables for simplicity
|
|
12
|
+
* - Server URL points to the known handy-api server
|
|
13
|
+
* - Socket path uses a known path for session updates
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* Get the application configuration
|
|
17
|
+
*/
|
|
18
|
+
export function getConfig() {
|
|
19
|
+
return {
|
|
20
|
+
serverUrl: 'https://handy-api.korshakov.org',
|
|
21
|
+
socketPath: '/v1/updates'
|
|
22
|
+
};
|
|
23
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Logger utility for handy-cli
|
|
3
|
+
*
|
|
4
|
+
* This module provides structured logging with different log levels and colors.
|
|
5
|
+
*
|
|
6
|
+
* Key responsibilities:
|
|
7
|
+
* - Provide consistent logging interface
|
|
8
|
+
* - Color-coded output for different log levels
|
|
9
|
+
* - Timestamp prefixes for better debugging
|
|
10
|
+
*/
|
|
11
|
+
export declare enum LogLevel {
|
|
12
|
+
DEBUG = "DEBUG",
|
|
13
|
+
ERROR = "ERROR",
|
|
14
|
+
INFO = "INFO",
|
|
15
|
+
WARN = "WARN"
|
|
16
|
+
}
|
|
17
|
+
declare class Logger {
|
|
18
|
+
debug(message: string, ...args: unknown[]): void;
|
|
19
|
+
error(message: string, ...args: unknown[]): void;
|
|
20
|
+
info(message: string, ...args: unknown[]): void;
|
|
21
|
+
warn(message: string, ...args: unknown[]): void;
|
|
22
|
+
private getTimestamp;
|
|
23
|
+
private log;
|
|
24
|
+
}
|
|
25
|
+
export declare const logger: Logger;
|
|
26
|
+
export {};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Logger utility for handy-cli
|
|
3
|
+
*
|
|
4
|
+
* This module provides structured logging with different log levels and colors.
|
|
5
|
+
*
|
|
6
|
+
* Key responsibilities:
|
|
7
|
+
* - Provide consistent logging interface
|
|
8
|
+
* - Color-coded output for different log levels
|
|
9
|
+
* - Timestamp prefixes for better debugging
|
|
10
|
+
*/
|
|
11
|
+
import chalk from 'chalk';
|
|
12
|
+
export var LogLevel;
|
|
13
|
+
(function (LogLevel) {
|
|
14
|
+
LogLevel["DEBUG"] = "DEBUG";
|
|
15
|
+
LogLevel["ERROR"] = "ERROR";
|
|
16
|
+
LogLevel["INFO"] = "INFO";
|
|
17
|
+
LogLevel["WARN"] = "WARN";
|
|
18
|
+
})(LogLevel || (LogLevel = {}));
|
|
19
|
+
class Logger {
|
|
20
|
+
debug(message, ...args) {
|
|
21
|
+
if (process.env.DEBUG) {
|
|
22
|
+
this.log(LogLevel.DEBUG, message, ...args);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
error(message, ...args) {
|
|
26
|
+
this.log(LogLevel.ERROR, message, ...args);
|
|
27
|
+
}
|
|
28
|
+
info(message, ...args) {
|
|
29
|
+
this.log(LogLevel.INFO, message, ...args);
|
|
30
|
+
}
|
|
31
|
+
warn(message, ...args) {
|
|
32
|
+
this.log(LogLevel.WARN, message, ...args);
|
|
33
|
+
}
|
|
34
|
+
getTimestamp() {
|
|
35
|
+
return new Date().toISOString();
|
|
36
|
+
}
|
|
37
|
+
log(level, message, ...args) {
|
|
38
|
+
const timestamp = this.getTimestamp();
|
|
39
|
+
const prefix = `[${timestamp}] [${level}]`;
|
|
40
|
+
switch (level) {
|
|
41
|
+
case LogLevel.DEBUG: {
|
|
42
|
+
console.log(chalk.gray(prefix), message, ...args);
|
|
43
|
+
break;
|
|
44
|
+
}
|
|
45
|
+
case LogLevel.ERROR: {
|
|
46
|
+
console.error(chalk.red(prefix), message, ...args);
|
|
47
|
+
break;
|
|
48
|
+
}
|
|
49
|
+
case LogLevel.INFO: {
|
|
50
|
+
console.log(chalk.blue(prefix), message, ...args);
|
|
51
|
+
break;
|
|
52
|
+
}
|
|
53
|
+
case LogLevel.WARN: {
|
|
54
|
+
console.log(chalk.yellow(prefix), message, ...args);
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
export const logger = new Logger();
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Path utilities for handy-cli
|
|
3
|
+
*
|
|
4
|
+
* This module provides utilities for working with file paths,
|
|
5
|
+
* particularly for accessing user home directory and config files.
|
|
6
|
+
*
|
|
7
|
+
* Key responsibilities:
|
|
8
|
+
* - Resolve paths relative to user home directory
|
|
9
|
+
* - Provide consistent paths for config and secret files
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* Get the path to the secret key file in user's home directory
|
|
13
|
+
*/
|
|
14
|
+
export declare function getSecretKeyPath(): string;
|
|
15
|
+
/**
|
|
16
|
+
* Get the user's home directory
|
|
17
|
+
*/
|
|
18
|
+
export declare function getHomeDir(): string;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Path utilities for handy-cli
|
|
3
|
+
*
|
|
4
|
+
* This module provides utilities for working with file paths,
|
|
5
|
+
* particularly for accessing user home directory and config files.
|
|
6
|
+
*
|
|
7
|
+
* Key responsibilities:
|
|
8
|
+
* - Resolve paths relative to user home directory
|
|
9
|
+
* - Provide consistent paths for config and secret files
|
|
10
|
+
*/
|
|
11
|
+
import { homedir } from 'node:os';
|
|
12
|
+
import { join } from 'node:path';
|
|
13
|
+
/**
|
|
14
|
+
* Get the path to the secret key file in user's home directory
|
|
15
|
+
*/
|
|
16
|
+
export function getSecretKeyPath() {
|
|
17
|
+
return join(homedir(), '.handy-claude-code.key');
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Get the user's home directory
|
|
21
|
+
*/
|
|
22
|
+
export function getHomeDir() {
|
|
23
|
+
return homedir();
|
|
24
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QR Code utility for handy-cli
|
|
3
|
+
*
|
|
4
|
+
* This module handles QR code generation and terminal display for the handy:// URL.
|
|
5
|
+
*
|
|
6
|
+
* Key responsibilities:
|
|
7
|
+
* - Generate QR codes for terminal display
|
|
8
|
+
* - Format QR codes with proper spacing and borders
|
|
9
|
+
* - Handle errors gracefully
|
|
10
|
+
*
|
|
11
|
+
* Design decisions:
|
|
12
|
+
* - Uses qrcode-terminal for ASCII art QR codes
|
|
13
|
+
* - Displays QR codes with clear instructions
|
|
14
|
+
* - Provides fallback text if QR generation fails
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* Display a QR code in the terminal for the given URL
|
|
18
|
+
*/
|
|
19
|
+
export declare function displayQRCode(url: string): void;
|