genbox-agent 0.0.2 → 1.0.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 (46) hide show
  1. package/dist/configure-hooks.d.ts +17 -0
  2. package/dist/configure-hooks.d.ts.map +1 -0
  3. package/dist/configure-hooks.js +235 -0
  4. package/dist/configure-hooks.js.map +1 -0
  5. package/dist/index.d.ts +16 -0
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +41 -1
  8. package/dist/index.js.map +1 -1
  9. package/dist/providers/base-provider.d.ts +125 -0
  10. package/dist/providers/base-provider.d.ts.map +1 -0
  11. package/dist/providers/base-provider.js +217 -0
  12. package/dist/providers/base-provider.js.map +1 -0
  13. package/dist/providers/claude-provider.d.ts +35 -0
  14. package/dist/providers/claude-provider.d.ts.map +1 -0
  15. package/dist/providers/claude-provider.js +298 -0
  16. package/dist/providers/claude-provider.js.map +1 -0
  17. package/dist/providers/codex-provider.d.ts +73 -0
  18. package/dist/providers/codex-provider.d.ts.map +1 -0
  19. package/dist/providers/codex-provider.js +426 -0
  20. package/dist/providers/codex-provider.js.map +1 -0
  21. package/dist/providers/gemini-provider.d.ts +37 -0
  22. package/dist/providers/gemini-provider.d.ts.map +1 -0
  23. package/dist/providers/gemini-provider.js +352 -0
  24. package/dist/providers/gemini-provider.js.map +1 -0
  25. package/dist/providers/index.d.ts +128 -0
  26. package/dist/providers/index.d.ts.map +1 -0
  27. package/dist/providers/index.js +293 -0
  28. package/dist/providers/index.js.map +1 -0
  29. package/dist/types/index.d.ts +123 -0
  30. package/dist/types/index.d.ts.map +1 -0
  31. package/dist/types/index.js +9 -0
  32. package/dist/types/index.js.map +1 -0
  33. package/dist/unified-daemon.d.ts +18 -0
  34. package/dist/unified-daemon.d.ts.map +1 -0
  35. package/dist/unified-daemon.js +309 -0
  36. package/dist/unified-daemon.js.map +1 -0
  37. package/dist/unified-hook.d.ts +24 -0
  38. package/dist/unified-hook.d.ts.map +1 -0
  39. package/dist/unified-hook.js +173 -0
  40. package/dist/unified-hook.js.map +1 -0
  41. package/package.json +34 -8
  42. package/src/daemon.ts +0 -329
  43. package/src/hook.ts +0 -200
  44. package/src/index.ts +0 -1
  45. package/src/session-manager.ts +0 -270
  46. package/tsconfig.json +0 -19
package/src/daemon.ts DELETED
@@ -1,329 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * Genbox Agent Daemon
4
- *
5
- * WebSocket client that runs on each genbox VM and:
6
- * 1. Connects to the backend monitoring service
7
- * 2. Receives remote commands (send_prompt, send_keystroke, create_session)
8
- * 3. Manages tmux sessions running Claude Code
9
- * 4. Streams terminal output back to the dashboard
10
- */
11
-
12
- import { io, Socket } from 'socket.io-client';
13
- import { SessionManager } from './session-manager';
14
-
15
- // Configuration from environment
16
- const GENBOX_ID = process.env.GENBOX_ID || '';
17
- const GENBOX_TOKEN = process.env.GENBOX_TOKEN || '';
18
- const MONITORING_WS_URL = process.env.MONITORING_WS_URL || 'http://localhost:3001';
19
-
20
- // Output polling interval (ms)
21
- const OUTPUT_POLL_INTERVAL = 2000;
22
-
23
- interface RequestData {
24
- sessionId?: string;
25
- prompt?: string;
26
- key?: string;
27
- projectPath?: string;
28
- lines?: number;
29
- requestId?: string;
30
- }
31
-
32
- class GenboxAgentDaemon {
33
- private socket: Socket | null = null;
34
- private sessionManager: SessionManager;
35
- private running = false;
36
- private outputPollers: Map<string, NodeJS.Timeout> = new Map();
37
-
38
- constructor(
39
- private readonly genboxId: string,
40
- private readonly token: string,
41
- private readonly wsUrl: string,
42
- ) {
43
- this.sessionManager = new SessionManager(genboxId);
44
- }
45
-
46
- async connect(): Promise<boolean> {
47
- return new Promise((resolve) => {
48
- console.log(`Connecting to ${this.wsUrl}/claude-ws...`);
49
-
50
- this.socket = io(`${this.wsUrl}/claude-ws`, {
51
- query: { genboxId: this.genboxId, type: 'genbox' },
52
- auth: { genboxId: this.genboxId, token: this.token },
53
- transports: ['websocket', 'polling'],
54
- reconnection: true,
55
- reconnectionAttempts: Infinity,
56
- reconnectionDelay: 1000,
57
- reconnectionDelayMax: 30000,
58
- });
59
-
60
- this.socket.on('connect', () => {
61
- console.log(`[${this.genboxId}] Connected to monitoring server`);
62
- this.running = true;
63
- this.onConnect();
64
- resolve(true);
65
- });
66
-
67
- this.socket.on('disconnect', (reason) => {
68
- console.log(`[${this.genboxId}] Disconnected: ${reason}`);
69
- this.running = false;
70
- });
71
-
72
- this.socket.on('connect_error', (error) => {
73
- console.error(`[${this.genboxId}] Connection error:`, error.message);
74
- });
75
-
76
- // Register event handlers
77
- this.socket.on('send_prompt', this.onSendPrompt.bind(this));
78
- this.socket.on('send_keystroke', this.onSendKeystroke.bind(this));
79
- this.socket.on('create_session', this.onCreateSession.bind(this));
80
- this.socket.on('get_output', this.onGetOutput.bind(this));
81
- this.socket.on('kill_session', this.onKillSession.bind(this));
82
- this.socket.on('list_sessions', this.onListSessions.bind(this));
83
- this.socket.on('error', this.onError.bind(this));
84
-
85
- // Timeout after 10 seconds
86
- setTimeout(() => {
87
- if (!this.socket?.connected) {
88
- console.error('Connection timeout');
89
- resolve(false);
90
- }
91
- }, 10000);
92
- });
93
- }
94
-
95
- private onConnect(): void {
96
- // Register as genbox agent
97
- this.socket?.emit('register_agent', {
98
- genboxId: this.genboxId,
99
- token: this.token,
100
- });
101
-
102
- // Send initial session list
103
- const sessions = this.sessionManager.listSessions();
104
- this.socket?.emit('agent_sessions', {
105
- genboxId: this.genboxId,
106
- sessions,
107
- });
108
- }
109
-
110
- private onSendPrompt(data: RequestData): void {
111
- const { sessionId, prompt, requestId } = data;
112
- if (!sessionId || !prompt) return;
113
-
114
- console.log(`[${this.genboxId}] Sending prompt to ${sessionId}: ${prompt.slice(0, 50)}...`);
115
-
116
- const success = this.sessionManager.sendPrompt(sessionId, prompt);
117
-
118
- this.socket?.emit('prompt_result', {
119
- genboxId: this.genboxId,
120
- sessionId,
121
- requestId,
122
- success,
123
- });
124
-
125
- // Capture output after a delay
126
- if (success) {
127
- setTimeout(() => this.captureAndSendOutput(sessionId), 1000);
128
- }
129
- }
130
-
131
- private onSendKeystroke(data: RequestData): void {
132
- const { sessionId, key, requestId } = data;
133
- if (!sessionId || !key) return;
134
-
135
- console.log(`[${this.genboxId}] Sending keystroke to ${sessionId}: ${key}`);
136
-
137
- const success = this.sessionManager.sendKeystroke(sessionId, key);
138
-
139
- this.socket?.emit('keystroke_result', {
140
- genboxId: this.genboxId,
141
- sessionId,
142
- requestId,
143
- success,
144
- });
145
- }
146
-
147
- private onCreateSession(data: RequestData): void {
148
- const { projectPath = '/home/dev', requestId } = data;
149
-
150
- console.log(`[${this.genboxId}] Creating new session at ${projectPath}`);
151
-
152
- const session = this.sessionManager.createSession(projectPath);
153
-
154
- if (session) {
155
- this.socket?.emit('session_created', {
156
- genboxId: this.genboxId,
157
- sessionId: session.sessionId,
158
- sessionName: session.sessionName,
159
- projectPath: session.projectPath,
160
- requestId,
161
- success: true,
162
- });
163
-
164
- // Start output polling for this session
165
- this.startOutputPolling(session.sessionId);
166
- } else {
167
- this.socket?.emit('session_created', {
168
- genboxId: this.genboxId,
169
- requestId,
170
- success: false,
171
- error: 'Failed to create session',
172
- });
173
- }
174
- }
175
-
176
- private onGetOutput(data: RequestData): void {
177
- const { sessionId, lines = 100, requestId } = data;
178
- if (!sessionId) return;
179
-
180
- const output = this.sessionManager.getOutput(sessionId, lines);
181
- const status = this.sessionManager.getSessionStatus(sessionId);
182
-
183
- this.socket?.emit('output_update', {
184
- genboxId: this.genboxId,
185
- sessionId,
186
- requestId,
187
- output: output || '',
188
- status,
189
- });
190
- }
191
-
192
- private onKillSession(data: RequestData): void {
193
- const { sessionId, requestId } = data;
194
- if (!sessionId) return;
195
-
196
- console.log(`[${this.genboxId}] Killing session ${sessionId}`);
197
-
198
- const success = this.sessionManager.killSession(sessionId);
199
- this.stopOutputPolling(sessionId);
200
-
201
- this.socket?.emit('session_killed', {
202
- genboxId: this.genboxId,
203
- sessionId,
204
- requestId,
205
- success,
206
- });
207
- }
208
-
209
- private onListSessions(data: RequestData): void {
210
- const { requestId } = data;
211
-
212
- const sessions = this.sessionManager.listSessions();
213
-
214
- this.socket?.emit('sessions_list', {
215
- genboxId: this.genboxId,
216
- requestId,
217
- sessions,
218
- });
219
- }
220
-
221
- private onError(data: any): void {
222
- console.error(`[${this.genboxId}] Error from server:`, data);
223
- }
224
-
225
- private startOutputPolling(sessionId: string): void {
226
- if (this.outputPollers.has(sessionId)) return;
227
-
228
- let lastOutput = '';
229
-
230
- const poller = setInterval(() => {
231
- if (!this.running) {
232
- this.stopOutputPolling(sessionId);
233
- return;
234
- }
235
-
236
- const output = this.sessionManager.getOutput(sessionId, 100);
237
- const status = this.sessionManager.getSessionStatus(sessionId);
238
-
239
- // Only send if output changed
240
- if (output && output !== lastOutput) {
241
- lastOutput = output;
242
- this.socket?.emit('output_update', {
243
- genboxId: this.genboxId,
244
- sessionId,
245
- output,
246
- status,
247
- });
248
- }
249
-
250
- // Stop polling if session ended
251
- if (status === 'ended') {
252
- this.stopOutputPolling(sessionId);
253
- }
254
- }, OUTPUT_POLL_INTERVAL);
255
-
256
- this.outputPollers.set(sessionId, poller);
257
- }
258
-
259
- private stopOutputPolling(sessionId: string): void {
260
- const poller = this.outputPollers.get(sessionId);
261
- if (poller) {
262
- clearInterval(poller);
263
- this.outputPollers.delete(sessionId);
264
- }
265
- }
266
-
267
- private captureAndSendOutput(sessionId: string): void {
268
- const output = this.sessionManager.getOutput(sessionId, 100);
269
- const status = this.sessionManager.getSessionStatus(sessionId);
270
-
271
- if (output && this.socket?.connected) {
272
- this.socket.emit('output_update', {
273
- genboxId: this.genboxId,
274
- sessionId,
275
- output,
276
- status,
277
- });
278
- }
279
- }
280
-
281
- async run(): Promise<void> {
282
- console.log(`Starting Genbox Agent Daemon for ${this.genboxId}`);
283
-
284
- const connected = await this.connect();
285
- if (!connected) {
286
- console.log('Initial connection failed, will keep retrying...');
287
- }
288
-
289
- // Keep running
290
- await new Promise<void>((resolve) => {
291
- const shutdown = () => {
292
- console.log('Shutting down...');
293
- this.running = false;
294
-
295
- // Stop all output pollers
296
- for (const sessionId of this.outputPollers.keys()) {
297
- this.stopOutputPolling(sessionId);
298
- }
299
-
300
- this.socket?.disconnect();
301
- resolve();
302
- };
303
-
304
- process.on('SIGTERM', shutdown);
305
- process.on('SIGINT', shutdown);
306
- });
307
- }
308
- }
309
-
310
- // Main entry point
311
- async function main(): Promise<void> {
312
- if (!GENBOX_ID) {
313
- console.error('Error: GENBOX_ID environment variable not set');
314
- process.exit(1);
315
- }
316
-
317
- if (!GENBOX_TOKEN) {
318
- console.error('Error: GENBOX_TOKEN environment variable not set');
319
- process.exit(1);
320
- }
321
-
322
- const daemon = new GenboxAgentDaemon(GENBOX_ID, GENBOX_TOKEN, MONITORING_WS_URL);
323
- await daemon.run();
324
- }
325
-
326
- main().catch((error) => {
327
- console.error('Fatal error:', error);
328
- process.exit(1);
329
- });
package/src/hook.ts DELETED
@@ -1,200 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * Claude Code Hook Script
4
- *
5
- * Called by Claude Code hooks to report all interactions to the backend.
6
- * This script reads from stdin (hook input) and POSTs to the monitoring API.
7
- *
8
- * Environment variables:
9
- * - GENBOX_ID: Unique identifier for this genbox
10
- * - GENBOX_TOKEN: Authentication token for the API
11
- * - MONITORING_API_URL: Base URL for the monitoring API
12
- * - CLAUDE_HOOK_TYPE: The type of hook being called (set by Claude Code)
13
- */
14
-
15
- import * as fs from 'fs';
16
- import * as path from 'path';
17
- import * as crypto from 'crypto';
18
- import * as http from 'http';
19
- import * as https from 'https';
20
-
21
- // Configuration
22
- const GENBOX_ID = process.env.GENBOX_ID || '';
23
- const GENBOX_TOKEN = process.env.GENBOX_TOKEN || '';
24
- const MONITORING_API_URL = process.env.MONITORING_API_URL || 'http://localhost:3001';
25
- const HOOK_TYPE = process.env.CLAUDE_HOOK_TYPE || 'Unknown';
26
-
27
- // Session file path
28
- const SESSION_FILE = path.join(process.env.HOME || '/tmp', '.claude-session-id');
29
-
30
- function getOrCreateSessionId(): string {
31
- try {
32
- if (fs.existsSync(SESSION_FILE)) {
33
- return fs.readFileSync(SESSION_FILE, 'utf-8').trim();
34
- }
35
- } catch {
36
- // Ignore read errors
37
- }
38
-
39
- // Generate new session ID
40
- const sessionId = crypto
41
- .createHash('sha256')
42
- .update(`${GENBOX_ID}-${Date.now()}-${process.pid}`)
43
- .digest('hex')
44
- .slice(0, 32);
45
-
46
- try {
47
- fs.writeFileSync(SESSION_FILE, sessionId);
48
- } catch {
49
- // Ignore write errors
50
- }
51
-
52
- return sessionId;
53
- }
54
-
55
- function clearSessionId(): void {
56
- try {
57
- if (fs.existsSync(SESSION_FILE)) {
58
- fs.unlinkSync(SESSION_FILE);
59
- }
60
- } catch {
61
- // Ignore errors
62
- }
63
- }
64
-
65
- function getTmuxSession(): string | undefined {
66
- if (process.env.TMUX) {
67
- const tmuxPane = process.env.TMUX_PANE || '';
68
- return tmuxPane.split(',')[0] || undefined;
69
- }
70
- return undefined;
71
- }
72
-
73
- async function sendEvent(eventData: Record<string, any>): Promise<boolean> {
74
- if (!GENBOX_ID || !GENBOX_TOKEN) {
75
- console.error('Warning: GENBOX_ID or GENBOX_TOKEN not set, skipping event');
76
- return false;
77
- }
78
-
79
- const url = new URL('/claude/events', MONITORING_API_URL);
80
-
81
- // Add metadata
82
- eventData.genboxId = GENBOX_ID;
83
- eventData.genboxToken = GENBOX_TOKEN;
84
- eventData.sessionId = getOrCreateSessionId();
85
- eventData.tmuxSession = getTmuxSession();
86
- eventData.projectPath = process.cwd();
87
- eventData.timestamp = new Date().toISOString();
88
-
89
- const data = JSON.stringify(eventData);
90
- const isHttps = url.protocol === 'https:';
91
-
92
- return new Promise((resolve) => {
93
- const options = {
94
- hostname: url.hostname,
95
- port: url.port || (isHttps ? 443 : 80),
96
- path: url.pathname,
97
- method: 'POST',
98
- headers: {
99
- 'Content-Type': 'application/json',
100
- 'Content-Length': Buffer.byteLength(data),
101
- 'X-Genbox-Token': GENBOX_TOKEN,
102
- },
103
- timeout: 5000,
104
- };
105
-
106
- const req = (isHttps ? https : http).request(options, (res) => {
107
- resolve(res.statusCode === 200 || res.statusCode === 201);
108
- });
109
-
110
- req.on('error', (error) => {
111
- console.error(`Warning: Failed to send event: ${error.message}`);
112
- resolve(false);
113
- });
114
-
115
- req.on('timeout', () => {
116
- req.destroy();
117
- console.error('Warning: Request timeout');
118
- resolve(false);
119
- });
120
-
121
- req.write(data);
122
- req.end();
123
- });
124
- }
125
-
126
- function parseHookInput(input: string): Record<string, any> {
127
- try {
128
- if (!input.trim()) {
129
- return {};
130
- }
131
- return JSON.parse(input);
132
- } catch {
133
- return { raw_input: input };
134
- }
135
- }
136
-
137
- async function main(): Promise<void> {
138
- // Read input from stdin
139
- let input = '';
140
- if (!process.stdin.isTTY) {
141
- input = fs.readFileSync(0, 'utf-8');
142
- }
143
-
144
- const hookInput = parseHookInput(input);
145
-
146
- // Build event data based on hook type
147
- const eventData: Record<string, any> = {
148
- eventType: HOOK_TYPE,
149
- };
150
-
151
- switch (HOOK_TYPE) {
152
- case 'PreToolUse':
153
- eventData.toolName = hookInput.tool_name || '';
154
- eventData.toolInput = hookInput.tool_input || {};
155
- break;
156
-
157
- case 'PostToolUse':
158
- eventData.toolName = hookInput.tool_name || '';
159
- eventData.toolInput = hookInput.tool_input || {};
160
- eventData.toolResponse = hookInput.tool_response || {};
161
- eventData.success = hookInput.tool_response?.success !== false;
162
- break;
163
-
164
- case 'UserPromptSubmit':
165
- const prompt = hookInput.prompt || '';
166
- eventData.prompt = prompt;
167
- eventData.promptLength = prompt.length;
168
- break;
169
-
170
- case 'Stop':
171
- eventData.stopReason = hookInput.stop_reason || '';
172
- break;
173
-
174
- case 'SubagentStop':
175
- eventData.subagentType = hookInput.subagent_type || '';
176
- eventData.stopReason = hookInput.stop_reason || '';
177
- break;
178
-
179
- case 'Notification':
180
- eventData.notification = hookInput.message || '';
181
- break;
182
-
183
- case 'SessionStart':
184
- eventData.cliVersion = hookInput.cli_version || '';
185
- break;
186
-
187
- case 'SessionEnd':
188
- clearSessionId();
189
- eventData.sessionDuration = hookInput.duration_ms || 0;
190
- break;
191
- }
192
-
193
- // Send the event (don't await - fire and forget)
194
- sendEvent(eventData).catch(() => {});
195
-
196
- // Exit immediately - hooks should not block Claude
197
- process.exit(0);
198
- }
199
-
200
- main().catch(() => process.exit(0));
package/src/index.ts DELETED
@@ -1 +0,0 @@
1
- export { SessionManager, ClaudeSession } from './session-manager';