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.
Files changed (50) hide show
  1. package/README.md +38 -0
  2. package/bin/happy +2 -0
  3. package/bin/happy.cmd +2 -0
  4. package/dist/auth/auth.d.ts +38 -0
  5. package/dist/auth/auth.js +76 -0
  6. package/dist/auth/auth.test.d.ts +7 -0
  7. package/dist/auth/auth.test.js +96 -0
  8. package/dist/auth/crypto.d.ts +25 -0
  9. package/dist/auth/crypto.js +36 -0
  10. package/dist/claude/claude.d.ts +54 -0
  11. package/dist/claude/claude.js +170 -0
  12. package/dist/claude/claude.test.d.ts +7 -0
  13. package/dist/claude/claude.test.js +130 -0
  14. package/dist/claude/types.d.ts +37 -0
  15. package/dist/claude/types.js +7 -0
  16. package/dist/commands/start.d.ts +38 -0
  17. package/dist/commands/start.js +161 -0
  18. package/dist/commands/start.test.d.ts +7 -0
  19. package/dist/commands/start.test.js +307 -0
  20. package/dist/handlers/message-handler.d.ts +65 -0
  21. package/dist/handlers/message-handler.js +187 -0
  22. package/dist/index.cjs +603 -0
  23. package/dist/index.d.cts +1 -0
  24. package/dist/index.d.mts +1 -0
  25. package/dist/index.d.ts +1 -0
  26. package/dist/index.js +1 -0
  27. package/dist/index.mjs +583 -0
  28. package/dist/session/service.d.ts +27 -0
  29. package/dist/session/service.js +93 -0
  30. package/dist/session/service.test.d.ts +7 -0
  31. package/dist/session/service.test.js +71 -0
  32. package/dist/session/types.d.ts +44 -0
  33. package/dist/session/types.js +4 -0
  34. package/dist/socket/client.d.ts +50 -0
  35. package/dist/socket/client.js +136 -0
  36. package/dist/socket/client.test.d.ts +7 -0
  37. package/dist/socket/client.test.js +74 -0
  38. package/dist/socket/types.d.ts +80 -0
  39. package/dist/socket/types.js +12 -0
  40. package/dist/utils/config.d.ts +22 -0
  41. package/dist/utils/config.js +23 -0
  42. package/dist/utils/logger.d.ts +26 -0
  43. package/dist/utils/logger.js +60 -0
  44. package/dist/utils/paths.d.ts +18 -0
  45. package/dist/utils/paths.js +24 -0
  46. package/dist/utils/qrcode.d.ts +19 -0
  47. package/dist/utils/qrcode.js +37 -0
  48. package/dist/utils/qrcode.test.d.ts +7 -0
  49. package/dist/utils/qrcode.test.js +14 -0
  50. 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,4 @@
1
+ /**
2
+ * Session types for handy-server integration
3
+ */
4
+ export {};
@@ -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,7 @@
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
+ export {};
@@ -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;