happy-coder 0.1.6 → 0.1.9
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 +354 -917
- package/dist/index.mjs +280 -843
- package/dist/install-B2r_gX72.cjs +109 -0
- package/dist/install-HKe7dyS4.mjs +107 -0
- package/dist/lib.cjs +32 -0
- package/dist/lib.d.cts +727 -0
- package/dist/lib.d.mts +727 -0
- package/dist/lib.mjs +14 -0
- package/dist/run-FBXkmmN7.mjs +32 -0
- package/dist/run-q2To6b-c.cjs +34 -0
- package/dist/types-fXgEaaqP.mjs +861 -0
- package/dist/types-mykDX2xe.cjs +872 -0
- package/dist/uninstall-C42CoSCI.cjs +53 -0
- package/dist/uninstall-CLkTtlMv.mjs +51 -0
- package/package.json +28 -13
- 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
package/dist/claude/claude.js
DELETED
|
@@ -1,170 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Simplified Claude CLI integration
|
|
3
|
-
*
|
|
4
|
-
* This module provides a simple interface to spawn Claude CLI for each command.
|
|
5
|
-
* Each command runs in its own process and exits when complete.
|
|
6
|
-
*
|
|
7
|
-
* Key responsibilities:
|
|
8
|
-
* - Spawn Claude CLI with appropriate arguments
|
|
9
|
-
* - Track session ID across command invocations
|
|
10
|
-
* - Parse and emit Claude responses
|
|
11
|
-
* - Handle process lifecycle for each command
|
|
12
|
-
*
|
|
13
|
-
* Design decisions:
|
|
14
|
-
* - One process per command (no stdin interaction)
|
|
15
|
-
* - Session ID persisted in memory only
|
|
16
|
-
* - Kill any existing process before starting new one
|
|
17
|
-
* - Simple event-based API for responses
|
|
18
|
-
*/
|
|
19
|
-
import { logger } from '#utils/logger';
|
|
20
|
-
import { spawn } from 'node:child_process';
|
|
21
|
-
import { EventEmitter } from 'node:events';
|
|
22
|
-
// eslint-disable-next-line unicorn/prefer-event-target
|
|
23
|
-
export class Claude extends EventEmitter {
|
|
24
|
-
currentProcess;
|
|
25
|
-
currentSessionId;
|
|
26
|
-
/**
|
|
27
|
-
* Get the current session ID
|
|
28
|
-
*/
|
|
29
|
-
getSessionId() {
|
|
30
|
-
return this.currentSessionId;
|
|
31
|
-
}
|
|
32
|
-
/**
|
|
33
|
-
* Kill the current process if running
|
|
34
|
-
*/
|
|
35
|
-
kill() {
|
|
36
|
-
if (this.currentProcess && !this.currentProcess.killed) {
|
|
37
|
-
logger.info('Killing Claude process');
|
|
38
|
-
this.currentProcess.kill();
|
|
39
|
-
this.currentProcess = undefined;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
/**
|
|
43
|
-
* Run a single Claude command
|
|
44
|
-
* Kills any existing process and spawns a new one
|
|
45
|
-
*/
|
|
46
|
-
async runClaudeCodeTurn(command, sessionId, options) {
|
|
47
|
-
// Kill any existing process - wait for it to exit
|
|
48
|
-
if (this.currentProcess && !this.currentProcess.killed) {
|
|
49
|
-
logger.info('Killing existing Claude process');
|
|
50
|
-
await this.killAndWait();
|
|
51
|
-
}
|
|
52
|
-
// Build command arguments (no session resuming for now)
|
|
53
|
-
const args = this.buildArgs(command, undefined, options);
|
|
54
|
-
logger.info('Spawning Claude CLI with args:', args);
|
|
55
|
-
// Spawn the process
|
|
56
|
-
this.currentProcess = spawn('claude', args, {
|
|
57
|
-
cwd: options.workingDirectory,
|
|
58
|
-
stdio: ['pipe', 'pipe', 'pipe']
|
|
59
|
-
});
|
|
60
|
-
// Close stdin immediately (we don't send input)
|
|
61
|
-
this.currentProcess.stdin?.end();
|
|
62
|
-
// Handle stdout (JSON responses)
|
|
63
|
-
let outputBuffer = '';
|
|
64
|
-
this.currentProcess.stdout?.on('data', (data) => {
|
|
65
|
-
outputBuffer += data.toString();
|
|
66
|
-
// Process complete lines
|
|
67
|
-
const lines = outputBuffer.split('\n');
|
|
68
|
-
outputBuffer = lines.pop() || '';
|
|
69
|
-
for (const line of lines) {
|
|
70
|
-
if (line.trim()) {
|
|
71
|
-
this.processOutput(line);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
});
|
|
75
|
-
// Handle stderr
|
|
76
|
-
this.currentProcess.stderr?.on('data', (data) => {
|
|
77
|
-
const error = data.toString();
|
|
78
|
-
logger.error('Claude stderr:', error);
|
|
79
|
-
this.emit('error', error);
|
|
80
|
-
});
|
|
81
|
-
// Handle process exit
|
|
82
|
-
this.currentProcess.on('exit', (code, signal) => {
|
|
83
|
-
logger.info(`Claude process exited with code ${code} and signal ${signal}`);
|
|
84
|
-
this.emit('exit', { code, signal });
|
|
85
|
-
this.currentProcess = undefined;
|
|
86
|
-
});
|
|
87
|
-
// Handle process errors
|
|
88
|
-
this.currentProcess.on('error', (error) => {
|
|
89
|
-
logger.error('Claude process error:', error);
|
|
90
|
-
this.emit('processError', error);
|
|
91
|
-
this.currentProcess = undefined;
|
|
92
|
-
});
|
|
93
|
-
}
|
|
94
|
-
/**
|
|
95
|
-
* Build command line arguments for Claude
|
|
96
|
-
*/
|
|
97
|
-
buildArgs(command, sessionId, options) {
|
|
98
|
-
const args = [
|
|
99
|
-
'--print', command,
|
|
100
|
-
'--output-format', 'stream-json',
|
|
101
|
-
'--verbose'
|
|
102
|
-
];
|
|
103
|
-
// Add model
|
|
104
|
-
if (options.model) {
|
|
105
|
-
args.push('--model', options.model);
|
|
106
|
-
}
|
|
107
|
-
// Add permission mode
|
|
108
|
-
if (options.permissionMode) {
|
|
109
|
-
const modeMap = {
|
|
110
|
-
'auto': 'acceptEdits',
|
|
111
|
-
'default': 'default',
|
|
112
|
-
'plan': 'bypassPermissions'
|
|
113
|
-
};
|
|
114
|
-
args.push('--permission-mode', modeMap[options.permissionMode]);
|
|
115
|
-
}
|
|
116
|
-
// Add skip permissions flag
|
|
117
|
-
if (options.skipPermissions) {
|
|
118
|
-
args.push('--dangerously-skip-permissions');
|
|
119
|
-
}
|
|
120
|
-
// Add session resume if we have a session ID
|
|
121
|
-
if (sessionId) {
|
|
122
|
-
args.push('--resume', sessionId);
|
|
123
|
-
}
|
|
124
|
-
return args;
|
|
125
|
-
}
|
|
126
|
-
/**
|
|
127
|
-
* Kill the current process and wait for it to exit
|
|
128
|
-
*/
|
|
129
|
-
async killAndWait() {
|
|
130
|
-
if (!this.currentProcess || this.currentProcess.killed) {
|
|
131
|
-
return;
|
|
132
|
-
}
|
|
133
|
-
return new Promise((resolve) => {
|
|
134
|
-
const process = this.currentProcess;
|
|
135
|
-
// Set up exit handler
|
|
136
|
-
const exitHandler = () => {
|
|
137
|
-
this.currentProcess = undefined;
|
|
138
|
-
resolve();
|
|
139
|
-
};
|
|
140
|
-
process.once('exit', exitHandler);
|
|
141
|
-
// Kill the process
|
|
142
|
-
process.kill();
|
|
143
|
-
// Set a timeout in case the process doesn't exit
|
|
144
|
-
setTimeout(() => {
|
|
145
|
-
process.removeListener('exit', exitHandler);
|
|
146
|
-
this.currentProcess = undefined;
|
|
147
|
-
resolve();
|
|
148
|
-
}, 1000); // 1 second timeout
|
|
149
|
-
});
|
|
150
|
-
}
|
|
151
|
-
/**
|
|
152
|
-
* Process a line of output from Claude
|
|
153
|
-
*/
|
|
154
|
-
processOutput(line) {
|
|
155
|
-
try {
|
|
156
|
-
const response = JSON.parse(line);
|
|
157
|
-
// Capture session ID from responses
|
|
158
|
-
if (response.session_id) {
|
|
159
|
-
this.currentSessionId = response.session_id;
|
|
160
|
-
logger.info('Session ID updated:', this.currentSessionId);
|
|
161
|
-
}
|
|
162
|
-
// Emit the parsed response
|
|
163
|
-
this.emit('response', response);
|
|
164
|
-
}
|
|
165
|
-
catch {
|
|
166
|
-
// Not JSON, emit as regular output
|
|
167
|
-
this.emit('output', line);
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
}
|
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Integration tests for Claude CLI
|
|
3
|
-
*
|
|
4
|
-
* These tests verify that we can properly interact with Claude CLI,
|
|
5
|
-
* send commands, and receive responses.
|
|
6
|
-
*/
|
|
7
|
-
import { Claude } from '#claude/claude';
|
|
8
|
-
import { logger } from '#utils/logger';
|
|
9
|
-
import { expect } from 'chai';
|
|
10
|
-
import { existsSync } from 'node:fs';
|
|
11
|
-
import { resolve } from 'node:path';
|
|
12
|
-
describe('Claude CLI Integration', function () {
|
|
13
|
-
this.timeout(60_000); // 60 second timeout for Claude operations
|
|
14
|
-
let claude;
|
|
15
|
-
const playgroundPath = resolve('./claude-cli-playground-project');
|
|
16
|
-
before(() => {
|
|
17
|
-
// Verify playground directory exists
|
|
18
|
-
if (!existsSync(playgroundPath)) {
|
|
19
|
-
throw new Error('Playground directory not found. Run from handy-cli root directory.');
|
|
20
|
-
}
|
|
21
|
-
});
|
|
22
|
-
beforeEach(() => {
|
|
23
|
-
// Create a new Claude instance for each test
|
|
24
|
-
claude = new Claude();
|
|
25
|
-
});
|
|
26
|
-
afterEach(() => {
|
|
27
|
-
// Clean up Claude process
|
|
28
|
-
claude.kill();
|
|
29
|
-
});
|
|
30
|
-
it('should execute ls command and list files in playground directory', (done) => {
|
|
31
|
-
const responses = [];
|
|
32
|
-
let hasListedFiles = false;
|
|
33
|
-
let sessionId;
|
|
34
|
-
let testCompleted = false;
|
|
35
|
-
let hasSeenToolUse = false;
|
|
36
|
-
let hasSeenToolResult = false;
|
|
37
|
-
// Set a timeout to prevent infinite hanging
|
|
38
|
-
const timeout = setTimeout(() => {
|
|
39
|
-
if (!testCompleted) {
|
|
40
|
-
testCompleted = true;
|
|
41
|
-
claude.kill();
|
|
42
|
-
done(new Error('Test timeout: Claude did not respond within expected time'));
|
|
43
|
-
}
|
|
44
|
-
}, 15_000); // 15 second timeout
|
|
45
|
-
claude.on('response', (response) => {
|
|
46
|
-
responses.push(response);
|
|
47
|
-
logger.info('Response type:', response.type, 'Session ID:', response.session_id);
|
|
48
|
-
// Capture session ID
|
|
49
|
-
if (response.session_id) {
|
|
50
|
-
sessionId = response.session_id;
|
|
51
|
-
logger.info('Captured session ID:', sessionId);
|
|
52
|
-
}
|
|
53
|
-
// Check for tool use and tool results based on actual Claude output
|
|
54
|
-
if (response.type === 'assistant' && response.data) {
|
|
55
|
-
const content = JSON.stringify(response.data).toLowerCase();
|
|
56
|
-
if (content.includes('tool_use') || content.includes('ls')) {
|
|
57
|
-
hasSeenToolUse = true;
|
|
58
|
-
logger.info('Detected tool use in assistant response');
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
if (response.type === 'user' && response.data) {
|
|
62
|
-
const content = JSON.stringify(response.data).toLowerCase();
|
|
63
|
-
if (content.includes('tool_result') || content.includes('hello-world.js')) {
|
|
64
|
-
hasSeenToolResult = true;
|
|
65
|
-
hasListedFiles = true;
|
|
66
|
-
logger.info('Detected tool result with file listing');
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
// Check various response types for file listing
|
|
70
|
-
if (response.type === 'assistant' || response.type === 'user' || response.type === 'claude-response') {
|
|
71
|
-
const content = JSON.stringify(response).toLowerCase();
|
|
72
|
-
// Check for expected files in playground
|
|
73
|
-
if (content.includes('hello-world.js')) {
|
|
74
|
-
hasListedFiles = true;
|
|
75
|
-
logger.info('Found hello-world.js in response');
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
// Complete test when we have seen both tool use and tool result
|
|
79
|
-
if (hasSeenToolUse && hasSeenToolResult && hasListedFiles && sessionId && !testCompleted) {
|
|
80
|
-
testCompleted = true;
|
|
81
|
-
clearTimeout(timeout);
|
|
82
|
-
expect(sessionId).to.be.a('string');
|
|
83
|
-
expect(sessionId).to.have.length.greaterThan(0);
|
|
84
|
-
expect(hasListedFiles).to.equal(true, 'Claude should have listed files');
|
|
85
|
-
expect(responses).to.have.length.greaterThan(0);
|
|
86
|
-
done();
|
|
87
|
-
}
|
|
88
|
-
});
|
|
89
|
-
claude.on('error', (error) => {
|
|
90
|
-
if (!testCompleted) {
|
|
91
|
-
testCompleted = true;
|
|
92
|
-
clearTimeout(timeout);
|
|
93
|
-
done(new Error(`Claude error: ${error}`));
|
|
94
|
-
}
|
|
95
|
-
});
|
|
96
|
-
claude.on('processError', (error) => {
|
|
97
|
-
if (!testCompleted) {
|
|
98
|
-
testCompleted = true;
|
|
99
|
-
clearTimeout(timeout);
|
|
100
|
-
// Claude CLI might not be installed
|
|
101
|
-
console.warn('Claude CLI not available:', error.message);
|
|
102
|
-
done();
|
|
103
|
-
}
|
|
104
|
-
});
|
|
105
|
-
claude.on('exit', (exitInfo) => {
|
|
106
|
-
if (!testCompleted) {
|
|
107
|
-
testCompleted = true;
|
|
108
|
-
clearTimeout(timeout);
|
|
109
|
-
logger.info(`Claude process exited with code ${exitInfo.code} and signal ${exitInfo.signal}`);
|
|
110
|
-
// Check if we got the expected responses even though process exited
|
|
111
|
-
if (sessionId && responses.length > 0 && (hasListedFiles || hasSeenToolResult)) {
|
|
112
|
-
// Process exited but we got expected responses - this is normal
|
|
113
|
-
expect(sessionId).to.be.a('string');
|
|
114
|
-
expect(responses).to.have.length.greaterThan(0);
|
|
115
|
-
done();
|
|
116
|
-
}
|
|
117
|
-
else {
|
|
118
|
-
// Process exited without proper responses - this is an error
|
|
119
|
-
done(new Error(`Claude process exited with code ${exitInfo.code} and signal ${exitInfo.signal} without providing expected responses`));
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
});
|
|
123
|
-
// Start Claude with ls command
|
|
124
|
-
claude.runClaudeCodeTurn('Use the LS tool to list files in the current directory and tell me the first 5 files or folders you see.', undefined, {
|
|
125
|
-
model: 'sonnet',
|
|
126
|
-
permissionMode: 'default',
|
|
127
|
-
workingDirectory: playgroundPath
|
|
128
|
-
});
|
|
129
|
-
});
|
|
130
|
-
});
|
package/dist/claude/types.d.ts
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Types for Claude CLI interaction
|
|
3
|
-
*
|
|
4
|
-
* This module defines types for the JSON line output from Claude CLI
|
|
5
|
-
* and configuration options for spawning Claude processes.
|
|
6
|
-
*/
|
|
7
|
-
import type { ChildProcess } from 'node:child_process';
|
|
8
|
-
/**
|
|
9
|
-
* Claude CLI spawn options
|
|
10
|
-
*/
|
|
11
|
-
export interface ClaudeSpawnOptions {
|
|
12
|
-
allowedTools?: string[];
|
|
13
|
-
disallowedTools?: string[];
|
|
14
|
-
model?: string;
|
|
15
|
-
permissionMode?: 'auto' | 'default' | 'plan';
|
|
16
|
-
sessionId?: string;
|
|
17
|
-
skipPermissions?: boolean;
|
|
18
|
-
workingDirectory: string;
|
|
19
|
-
}
|
|
20
|
-
/**
|
|
21
|
-
* Claude CLI JSON output types
|
|
22
|
-
*/
|
|
23
|
-
export interface ClaudeResponse {
|
|
24
|
-
data?: unknown;
|
|
25
|
-
error?: string;
|
|
26
|
-
message?: string;
|
|
27
|
-
session_id?: string;
|
|
28
|
-
type: string;
|
|
29
|
-
}
|
|
30
|
-
/**
|
|
31
|
-
* Claude process state
|
|
32
|
-
*/
|
|
33
|
-
export interface ClaudeProcess {
|
|
34
|
-
isRunning: boolean;
|
|
35
|
-
process: ChildProcess;
|
|
36
|
-
sessionId?: string;
|
|
37
|
-
}
|
package/dist/claude/types.js
DELETED
package/dist/commands/start.d.ts
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Start command for handy-cli
|
|
3
|
-
*
|
|
4
|
-
* This is the main command that starts a Claude Code session and connects
|
|
5
|
-
* it to the handy server for remote access.
|
|
6
|
-
*
|
|
7
|
-
* Key responsibilities:
|
|
8
|
-
* - Initialize authentication
|
|
9
|
-
* - Establish socket connection
|
|
10
|
-
* - Start Claude session
|
|
11
|
-
* - Handle message routing
|
|
12
|
-
* - Graceful shutdown
|
|
13
|
-
*
|
|
14
|
-
* Design decisions:
|
|
15
|
-
* - Uses oclif command framework as requested
|
|
16
|
-
* - Handles all initialization in sequence
|
|
17
|
-
* - Provides clear error messages
|
|
18
|
-
* - Supports graceful shutdown on SIGINT/SIGTERM
|
|
19
|
-
*/
|
|
20
|
-
import { Command } from '@oclif/core';
|
|
21
|
-
export default class Start extends Command {
|
|
22
|
-
static description: string;
|
|
23
|
-
static examples: string[];
|
|
24
|
-
static flags: {
|
|
25
|
-
model: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
26
|
-
'permission-mode': import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
27
|
-
'skip-permissions': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
28
|
-
'test-session': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
29
|
-
};
|
|
30
|
-
private messageHandler?;
|
|
31
|
-
private sessionId?;
|
|
32
|
-
private sessionService?;
|
|
33
|
-
private socketClient?;
|
|
34
|
-
run(): Promise<void>;
|
|
35
|
-
private cleanup;
|
|
36
|
-
private setupShutdownHandlers;
|
|
37
|
-
private shutdown;
|
|
38
|
-
}
|
package/dist/commands/start.js
DELETED
|
@@ -1,161 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Start command for handy-cli
|
|
3
|
-
*
|
|
4
|
-
* This is the main command that starts a Claude Code session and connects
|
|
5
|
-
* it to the handy server for remote access.
|
|
6
|
-
*
|
|
7
|
-
* Key responsibilities:
|
|
8
|
-
* - Initialize authentication
|
|
9
|
-
* - Establish socket connection
|
|
10
|
-
* - Start Claude session
|
|
11
|
-
* - Handle message routing
|
|
12
|
-
* - Graceful shutdown
|
|
13
|
-
*
|
|
14
|
-
* Design decisions:
|
|
15
|
-
* - Uses oclif command framework as requested
|
|
16
|
-
* - Handles all initialization in sequence
|
|
17
|
-
* - Provides clear error messages
|
|
18
|
-
* - Supports graceful shutdown on SIGINT/SIGTERM
|
|
19
|
-
*/
|
|
20
|
-
import { authGetToken, generateHandyUrl, getOrCreateSecretKey } from '#auth/auth';
|
|
21
|
-
import { MessageHandler } from '#handlers/message-handler';
|
|
22
|
-
import { SessionService } from '#session/service';
|
|
23
|
-
import { SocketClient } from '#socket/client';
|
|
24
|
-
import { getConfig } from '#utils/config';
|
|
25
|
-
import { logger } from '#utils/logger';
|
|
26
|
-
import { displayQRCode } from '#utils/qrcode';
|
|
27
|
-
import { Command, Flags } from '@oclif/core';
|
|
28
|
-
import { basename } from 'node:path';
|
|
29
|
-
export default class Start extends Command {
|
|
30
|
-
static description = 'Start a Claude Code session connected to the handy server';
|
|
31
|
-
static examples = [
|
|
32
|
-
'<%= config.bin %> <%= command.id %>',
|
|
33
|
-
'<%= config.bin %> <%= command.id %> --model sonnet',
|
|
34
|
-
'<%= config.bin %> <%= command.id %> --skip-permissions',
|
|
35
|
-
];
|
|
36
|
-
static flags = {
|
|
37
|
-
model: Flags.string({
|
|
38
|
-
char: 'm',
|
|
39
|
-
default: 'sonnet',
|
|
40
|
-
description: 'Claude model to use',
|
|
41
|
-
options: ['sonnet', 'opus', 'haiku'],
|
|
42
|
-
}),
|
|
43
|
-
'permission-mode': Flags.string({
|
|
44
|
-
default: 'auto',
|
|
45
|
-
description: 'Permission mode for Claude',
|
|
46
|
-
options: ['plan', 'auto', 'default'],
|
|
47
|
-
}),
|
|
48
|
-
'skip-permissions': Flags.boolean({
|
|
49
|
-
default: true,
|
|
50
|
-
description: 'Skip permission prompts (dangerous)',
|
|
51
|
-
}),
|
|
52
|
-
'test-session': Flags.string({
|
|
53
|
-
description: 'Use a specific session ID for testing (internal use)',
|
|
54
|
-
hidden: true,
|
|
55
|
-
}),
|
|
56
|
-
};
|
|
57
|
-
messageHandler;
|
|
58
|
-
sessionId;
|
|
59
|
-
sessionService;
|
|
60
|
-
socketClient;
|
|
61
|
-
async run() {
|
|
62
|
-
const { flags } = await this.parse(Start);
|
|
63
|
-
try {
|
|
64
|
-
// Load configuration
|
|
65
|
-
const config = getConfig();
|
|
66
|
-
logger.info('Starting handy-cli...');
|
|
67
|
-
// Step 1: Authentication
|
|
68
|
-
logger.info('Authenticating with server...');
|
|
69
|
-
const secret = await getOrCreateSecretKey();
|
|
70
|
-
const authToken = await authGetToken(config.serverUrl, secret);
|
|
71
|
-
logger.info('Authentication successful');
|
|
72
|
-
// Step 1.5: Display QR code for mobile connection
|
|
73
|
-
const handyUrl = generateHandyUrl(secret);
|
|
74
|
-
displayQRCode(handyUrl);
|
|
75
|
-
// Step 2: Connect to socket server
|
|
76
|
-
logger.info('Connecting to socket server...');
|
|
77
|
-
this.socketClient = new SocketClient({
|
|
78
|
-
authToken,
|
|
79
|
-
serverUrl: config.serverUrl,
|
|
80
|
-
socketPath: config.socketPath,
|
|
81
|
-
});
|
|
82
|
-
this.socketClient.connect();
|
|
83
|
-
// Wait for authentication
|
|
84
|
-
const user = await this.socketClient.waitForAuth();
|
|
85
|
-
logger.info(`Connected as user: ${user}`);
|
|
86
|
-
// Step 3: Create server session
|
|
87
|
-
const workingDirectory = process.cwd();
|
|
88
|
-
const sessionTag = basename(workingDirectory);
|
|
89
|
-
logger.info(`Creating server session with tag: ${sessionTag}`);
|
|
90
|
-
this.sessionService = new SessionService(config.serverUrl, authToken);
|
|
91
|
-
const { session } = await this.sessionService.createSession(sessionTag);
|
|
92
|
-
this.sessionId = session.id;
|
|
93
|
-
logger.info(`Session created: ${this.sessionId}`);
|
|
94
|
-
// Step 4: Initialize Claude
|
|
95
|
-
logger.info(`Initializing Claude in: ${workingDirectory}`);
|
|
96
|
-
// Step 4: Set up message handler with session ID
|
|
97
|
-
this.messageHandler = new MessageHandler({
|
|
98
|
-
claudeOptions: {
|
|
99
|
-
model: flags.model,
|
|
100
|
-
permissionMode: flags['permission-mode'],
|
|
101
|
-
skipPermissions: flags['skip-permissions']
|
|
102
|
-
},
|
|
103
|
-
sessionId: this.sessionId,
|
|
104
|
-
sessionService: this.sessionService,
|
|
105
|
-
socketClient: this.socketClient,
|
|
106
|
-
workingDirectory
|
|
107
|
-
});
|
|
108
|
-
this.messageHandler.start();
|
|
109
|
-
// Set up event handlers for logging
|
|
110
|
-
this.messageHandler.on('claudeResponse', (response) => {
|
|
111
|
-
logger.info('Claude response:', JSON.stringify(response, null, 2));
|
|
112
|
-
});
|
|
113
|
-
this.messageHandler.on('error', (error) => {
|
|
114
|
-
logger.error('Handler error:', error);
|
|
115
|
-
});
|
|
116
|
-
this.messageHandler.on('claudeExit', (exitInfo) => {
|
|
117
|
-
logger.info('Claude process exited:', exitInfo);
|
|
118
|
-
});
|
|
119
|
-
// Step 5: Start initial Claude session
|
|
120
|
-
logger.info('Starting Claude Code session...');
|
|
121
|
-
logger.info('Model:', flags.model);
|
|
122
|
-
logger.info('Permission mode:', flags['permission-mode']);
|
|
123
|
-
logger.info('Skip permissions:', flags['skip-permissions']);
|
|
124
|
-
// Start with a command to show current working directory to ensure we
|
|
125
|
-
// are in the correct project
|
|
126
|
-
const initialCommand = 'Show current working directory';
|
|
127
|
-
logger.info('Sending initial command to Claude:', initialCommand);
|
|
128
|
-
// Send the initial command through the message handler to ensure it's properly captured
|
|
129
|
-
this.messageHandler.handleInitialCommand(initialCommand);
|
|
130
|
-
// Set up graceful shutdown
|
|
131
|
-
this.setupShutdownHandlers();
|
|
132
|
-
logger.info('Handy CLI is running. Press Ctrl+C to stop.');
|
|
133
|
-
logger.info('Waiting for commands from connected clients...');
|
|
134
|
-
// Keep the process running
|
|
135
|
-
await new Promise(() => { });
|
|
136
|
-
}
|
|
137
|
-
catch (error) {
|
|
138
|
-
logger.error('Failed to start:', error);
|
|
139
|
-
this.cleanup();
|
|
140
|
-
this.exit(1);
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
cleanup() {
|
|
144
|
-
if (this.messageHandler) {
|
|
145
|
-
this.messageHandler.stop();
|
|
146
|
-
}
|
|
147
|
-
if (this.socketClient) {
|
|
148
|
-
this.socketClient.disconnect();
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
setupShutdownHandlers() {
|
|
152
|
-
process.on('SIGINT', () => this.shutdown());
|
|
153
|
-
process.on('SIGTERM', () => this.shutdown());
|
|
154
|
-
}
|
|
155
|
-
shutdown() {
|
|
156
|
-
logger.info('Shutting down...');
|
|
157
|
-
this.cleanup();
|
|
158
|
-
// Use OCLIF's exit method for proper shutdown
|
|
159
|
-
this.exit(0);
|
|
160
|
-
}
|
|
161
|
-
}
|