vibekit-agent 1.0.0

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/agent.js ADDED
@@ -0,0 +1,741 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.AgentClient = void 0;
40
+ const ws_1 = __importDefault(require("ws"));
41
+ const child_process_1 = require("child_process");
42
+ const fs = __importStar(require("fs"));
43
+ const os = __importStar(require("os"));
44
+ const path = __importStar(require("path"));
45
+ /**
46
+ * Find the claude binary by checking common installation locations
47
+ */
48
+ function findClaudeBinary() {
49
+ // Check for CLAUDE_PATH environment variable first (allows user override)
50
+ if (process.env.CLAUDE_PATH && fs.existsSync(process.env.CLAUDE_PATH)) {
51
+ return process.env.CLAUDE_PATH;
52
+ }
53
+ const homedir = os.homedir();
54
+ // Build list of possible paths
55
+ const possiblePaths = [
56
+ // Homebrew on macOS (Apple Silicon and Intel)
57
+ '/opt/homebrew/bin/claude',
58
+ '/usr/local/bin/claude',
59
+ // npm global installs with common prefixes
60
+ path.join(homedir, '.npm-global', 'bin', 'claude'),
61
+ '/usr/local/lib/node_modules/.bin/claude',
62
+ // Claude desktop app location on macOS
63
+ '/Applications/Claude.app/Contents/Resources/claude',
64
+ ];
65
+ // Try to get npm global bin directory dynamically
66
+ try {
67
+ const npmBin = (0, child_process_1.execSync)('npm bin -g 2>/dev/null', {
68
+ encoding: 'utf-8',
69
+ env: { ...process.env, PATH: '/usr/local/bin:/opt/homebrew/bin:/usr/bin:' + (process.env.PATH || '') }
70
+ }).trim();
71
+ if (npmBin) {
72
+ possiblePaths.unshift(path.join(npmBin, 'claude'));
73
+ }
74
+ }
75
+ catch {
76
+ // Ignore
77
+ }
78
+ // Check nvm paths
79
+ const nvmDir = process.env.NVM_DIR || path.join(homedir, '.nvm');
80
+ if (fs.existsSync(nvmDir)) {
81
+ // Check common node versions
82
+ const versionsDir = path.join(nvmDir, 'versions', 'node');
83
+ if (fs.existsSync(versionsDir)) {
84
+ try {
85
+ const versions = fs.readdirSync(versionsDir);
86
+ for (const v of versions) {
87
+ possiblePaths.push(path.join(versionsDir, v, 'bin', 'claude'));
88
+ }
89
+ }
90
+ catch {
91
+ // Ignore
92
+ }
93
+ }
94
+ }
95
+ // Also check fnm (Fast Node Manager) paths
96
+ const fnmDir = process.env.FNM_MULTISHELL_PATH || path.join(homedir, '.fnm');
97
+ if (fs.existsSync(fnmDir)) {
98
+ possiblePaths.push(path.join(fnmDir, 'bin', 'claude'));
99
+ }
100
+ // Check each path
101
+ for (const p of possiblePaths) {
102
+ try {
103
+ if (fs.existsSync(p)) {
104
+ // Verify it's executable
105
+ fs.accessSync(p, fs.constants.X_OK);
106
+ console.log(`Found claude at: ${p}`);
107
+ return p;
108
+ }
109
+ }
110
+ catch {
111
+ // Not accessible, continue
112
+ }
113
+ }
114
+ // Last resort: try which command with enhanced PATH
115
+ try {
116
+ const enhancedPath = [
117
+ '/opt/homebrew/bin',
118
+ '/usr/local/bin',
119
+ path.join(homedir, '.npm-global', 'bin'),
120
+ process.env.PATH || ''
121
+ ].join(':');
122
+ const result = (0, child_process_1.execSync)('which claude', {
123
+ encoding: 'utf-8',
124
+ env: { ...process.env, PATH: enhancedPath }
125
+ }).trim();
126
+ if (result && fs.existsSync(result)) {
127
+ console.log(`Found claude via which: ${result}`);
128
+ return result;
129
+ }
130
+ }
131
+ catch {
132
+ // Not found
133
+ }
134
+ return null;
135
+ }
136
+ // Strip ANSI escape codes
137
+ function stripAnsi(str) {
138
+ // eslint-disable-next-line no-control-regex
139
+ return str.replace(/\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g, '');
140
+ }
141
+ class AgentClient {
142
+ config;
143
+ ws = null;
144
+ claudeProcess = null;
145
+ reconnectTimeout = null;
146
+ reconnectDelay = 1000;
147
+ isConnected = false;
148
+ currentChatId = null;
149
+ currentTelegramId = null;
150
+ currentReplyToMessageId = null;
151
+ outputBuffer = '';
152
+ streamingInterval = null;
153
+ lastFlushedLength = 0;
154
+ currentMessageId = null;
155
+ workingDirectory = process.cwd();
156
+ // Conversation memory: track if we should continue the previous conversation
157
+ hasActiveConversation = false;
158
+ constructor(config) {
159
+ this.config = config;
160
+ }
161
+ /**
162
+ * Link this computer to a Telegram account
163
+ */
164
+ async link() {
165
+ console.log('Connecting to VibeKit...\n');
166
+ // Request a link from the server
167
+ const serverUrl = process.env.VIBEKIT_SERVER || 'https://vibekit.bot';
168
+ try {
169
+ // Generate a temporary link code request
170
+ console.log('To link your computer:');
171
+ console.log('');
172
+ console.log('1. Go to @the_vibe_kit_bot on Telegram');
173
+ console.log('2. Send: /remote');
174
+ console.log('3. Copy the 6-character code it gives you');
175
+ console.log('');
176
+ // Wait for user to enter the code
177
+ const readline = await Promise.resolve().then(() => __importStar(require('readline')));
178
+ const rl = readline.createInterface({
179
+ input: process.stdin,
180
+ output: process.stdout,
181
+ });
182
+ const code = await new Promise((resolve) => {
183
+ rl.question('Enter the code from Telegram: ', (answer) => {
184
+ rl.close();
185
+ resolve(answer.trim().toUpperCase());
186
+ });
187
+ });
188
+ if (!code || code.length !== 6) {
189
+ console.error('Invalid code. Please try again.');
190
+ process.exit(1);
191
+ }
192
+ // Submit the code to the server
193
+ console.log('\nValidating code...');
194
+ const response = await fetch(`${serverUrl}/api/agent/link`, {
195
+ method: 'POST',
196
+ headers: { 'Content-Type': 'application/json' },
197
+ body: JSON.stringify({ code }),
198
+ });
199
+ if (!response.ok) {
200
+ const errorData = (await response.json().catch(() => ({ error: 'Unknown error' })));
201
+ console.error(`Link failed: ${errorData.error || 'Invalid code'}`);
202
+ process.exit(1);
203
+ }
204
+ const result = (await response.json());
205
+ this.config.setCredentials(result.token, result.wsUrl);
206
+ console.log('\nLinked successfully!');
207
+ console.log('');
208
+ console.log('Start the agent with:');
209
+ console.log(' npx vibekit-agent start');
210
+ console.log('');
211
+ }
212
+ catch (error) {
213
+ const message = error instanceof Error ? error.message : String(error);
214
+ console.error('Link failed:', message);
215
+ process.exit(1);
216
+ }
217
+ }
218
+ /**
219
+ * Start the agent and connect to VibeKit
220
+ */
221
+ async start(directory, autoMode = false) {
222
+ this.workingDirectory = path.resolve(directory);
223
+ // Don't persist directory in auto mode (ephemeral container)
224
+ if (!autoMode) {
225
+ this.config.setLastDirectory(this.workingDirectory);
226
+ }
227
+ console.log(`Starting VibeKit Remote Agent...`);
228
+ console.log(`Working directory: ${this.workingDirectory}`);
229
+ // In auto mode, set up Claude credentials from the credentials file
230
+ if (autoMode) {
231
+ await this.setupAutoModeCredentials();
232
+ }
233
+ // Check for claude binary at startup
234
+ const claudePath = findClaudeBinary();
235
+ if (claudePath) {
236
+ console.log(`Claude Code found: ${claudePath}`);
237
+ }
238
+ else {
239
+ console.error('\nClaude Code not found!');
240
+ console.error('Please install it: npm install -g @anthropic-ai/claude-code');
241
+ console.error('Then restart this agent.\n');
242
+ }
243
+ console.log('');
244
+ // Connect to VibeKit WebSocket
245
+ this.connect();
246
+ // Handle process signals
247
+ process.on('SIGINT', () => this.shutdown());
248
+ process.on('SIGTERM', () => this.shutdown());
249
+ // Keep the process running
250
+ await new Promise(() => { });
251
+ }
252
+ /**
253
+ * Run claude command with the given prompt
254
+ */
255
+ runClaude(prompt) {
256
+ // Find the claude binary
257
+ const claudeBinary = findClaudeBinary();
258
+ if (!claudeBinary) {
259
+ console.error('\nClaude Code not found!');
260
+ console.error('Searched in common locations but could not find the claude binary.');
261
+ console.error('\nPlease ensure Claude Code is installed:');
262
+ console.error(' npm install -g @anthropic-ai/claude-code');
263
+ console.error('\nOr specify the path by running:');
264
+ console.error(' which claude');
265
+ if (this.currentChatId && this.currentTelegramId) {
266
+ this.sendResponse(this.currentChatId, `Error: Claude Code not found on this computer.\n\nPlease install it:\n\`npm install -g @anthropic-ai/claude-code\`\n\nThen restart the agent.`, 'complete');
267
+ }
268
+ return;
269
+ }
270
+ // Kill any existing process
271
+ if (this.claudeProcess) {
272
+ this.claudeProcess.kill();
273
+ }
274
+ // Clear streaming state
275
+ this.stopStreaming();
276
+ this.outputBuffer = '';
277
+ this.lastFlushedLength = 0;
278
+ this.currentMessageId = null;
279
+ // Build command arguments
280
+ const allowedTools = this.config.getAllowedTools();
281
+ const args = ['-p', prompt];
282
+ // Use --continue flag if we have an active conversation (for context memory)
283
+ if (this.hasActiveConversation) {
284
+ args.push('--continue');
285
+ }
286
+ if (allowedTools.length === 0) {
287
+ // No restrictions - allow everything
288
+ args.push('--dangerously-skip-permissions');
289
+ console.log(`\nRunning: ${claudeBinary} -p "..."${this.hasActiveConversation ? ' --continue' : ''} --dangerously-skip-permissions`);
290
+ }
291
+ else {
292
+ // Use specific allowed tools
293
+ args.push('--allowedTools', allowedTools.join(','));
294
+ console.log(`\nRunning: ${claudeBinary} -p "..."${this.hasActiveConversation ? ' --continue' : ''} --allowedTools ${allowedTools.join(',')}`);
295
+ }
296
+ // Spawn claude directly with the full path (no shell needed)
297
+ this.claudeProcess = (0, child_process_1.spawn)(claudeBinary, args, {
298
+ cwd: this.workingDirectory,
299
+ env: process.env,
300
+ stdio: ['ignore', 'pipe', 'pipe'],
301
+ });
302
+ // Start streaming interval to send partial output periodically
303
+ this.startStreaming();
304
+ this.claudeProcess.stdout?.on('data', (data) => {
305
+ const text = data.toString();
306
+ process.stdout.write(text);
307
+ this.outputBuffer += text;
308
+ });
309
+ this.claudeProcess.stderr?.on('data', (data) => {
310
+ const text = data.toString();
311
+ process.stderr.write(text);
312
+ this.outputBuffer += text;
313
+ });
314
+ this.claudeProcess.on('close', (code) => {
315
+ console.log(`\nClaude exited with code ${code}`);
316
+ this.stopStreaming();
317
+ this.flushOutput();
318
+ this.claudeProcess = null;
319
+ // Mark that we now have an active conversation for --continue on next message
320
+ if (code === 0) {
321
+ this.hasActiveConversation = true;
322
+ }
323
+ });
324
+ this.claudeProcess.on('error', (err) => {
325
+ console.error('Failed to start claude:', err.message);
326
+ this.stopStreaming();
327
+ if (this.currentChatId && this.currentTelegramId) {
328
+ this.sendResponse(this.currentChatId, `Error: Could not start Claude Code.\n\nPath: ${claudeBinary}\nError: ${err.message}`, 'complete');
329
+ }
330
+ });
331
+ }
332
+ /**
333
+ * Start streaming interval to send partial output
334
+ */
335
+ startStreaming() {
336
+ // Clear any existing interval
337
+ this.stopStreaming();
338
+ // Send updates every 500ms
339
+ this.streamingInterval = setInterval(() => {
340
+ this.flushPartialOutput();
341
+ }, 500);
342
+ }
343
+ /**
344
+ * Stop streaming interval
345
+ */
346
+ stopStreaming() {
347
+ if (this.streamingInterval) {
348
+ clearInterval(this.streamingInterval);
349
+ this.streamingInterval = null;
350
+ }
351
+ }
352
+ /**
353
+ * Flush partial output if buffer has grown since last flush
354
+ */
355
+ flushPartialOutput() {
356
+ if (!this.currentChatId || !this.currentTelegramId) {
357
+ return;
358
+ }
359
+ // Check if buffer has new content
360
+ if (this.outputBuffer.length <= this.lastFlushedLength) {
361
+ return;
362
+ }
363
+ // Update last flushed length
364
+ this.lastFlushedLength = this.outputBuffer.length;
365
+ // Clean the output
366
+ let cleaned = stripAnsi(this.outputBuffer);
367
+ // Truncate if too long for Telegram
368
+ if (cleaned.length > 4000) {
369
+ cleaned = '...\n' + cleaned.slice(-3900);
370
+ }
371
+ if (cleaned.trim().length > 0) {
372
+ this.sendStreaming(this.currentChatId, cleaned.trim());
373
+ }
374
+ }
375
+ /**
376
+ * Flush final buffered output to Telegram
377
+ */
378
+ flushOutput() {
379
+ if (!this.outputBuffer.trim() || !this.currentChatId || !this.currentTelegramId) {
380
+ this.outputBuffer = '';
381
+ return;
382
+ }
383
+ // Clean the output
384
+ let cleaned = stripAnsi(this.outputBuffer);
385
+ // Truncate if too long for Telegram
386
+ if (cleaned.length > 4000) {
387
+ cleaned = cleaned.slice(-4000) + '\n\n(output truncated)';
388
+ }
389
+ if (cleaned.trim().length > 0) {
390
+ this.sendResponse(this.currentChatId, cleaned.trim(), 'complete');
391
+ }
392
+ this.outputBuffer = '';
393
+ this.lastFlushedLength = 0;
394
+ this.currentMessageId = null;
395
+ }
396
+ /**
397
+ * Connect to VibeKit WebSocket server
398
+ */
399
+ connect() {
400
+ const wsUrl = this.config.getWsUrl();
401
+ const token = this.config.getToken();
402
+ if (!wsUrl || !token) {
403
+ console.error('No credentials found. Run "vibekit-agent link" first.');
404
+ process.exit(1);
405
+ }
406
+ console.log('Connecting to VibeKit...');
407
+ this.ws = new ws_1.default(wsUrl);
408
+ this.ws.on('open', () => {
409
+ console.log('Connected to VibeKit');
410
+ this.isConnected = true;
411
+ this.reconnectDelay = 1000;
412
+ // Authenticate
413
+ this.send({
414
+ type: 'auth',
415
+ payload: {
416
+ token,
417
+ agentVersion: '1.0.8',
418
+ platform: os.platform(),
419
+ workingDirectory: this.workingDirectory,
420
+ },
421
+ timestamp: Date.now(),
422
+ messageId: this.generateId(),
423
+ });
424
+ });
425
+ this.ws.on('message', (data) => {
426
+ try {
427
+ const message = JSON.parse(data.toString());
428
+ this.handleMessage(message);
429
+ }
430
+ catch (error) {
431
+ console.error('Error parsing message:', error);
432
+ }
433
+ });
434
+ this.ws.on('close', () => {
435
+ console.log('Disconnected from VibeKit');
436
+ this.isConnected = false;
437
+ this.scheduleReconnect();
438
+ });
439
+ this.ws.on('error', (error) => {
440
+ console.error('WebSocket error:', error.message);
441
+ this.isConnected = false;
442
+ });
443
+ }
444
+ /**
445
+ * Handle incoming WebSocket messages
446
+ */
447
+ handleMessage(message) {
448
+ switch (message.type) {
449
+ case 'auth_success':
450
+ console.log('Authenticated successfully');
451
+ console.log('');
452
+ console.log('Ready! Send messages to @the_vibe_kit_bot on Telegram.');
453
+ console.log('Press Ctrl+C to stop.');
454
+ console.log('');
455
+ this.sendStatus('idle');
456
+ break;
457
+ case 'auth_error':
458
+ console.error('Authentication failed:', message.payload.message);
459
+ this.config.clear();
460
+ process.exit(1);
461
+ break;
462
+ case 'message':
463
+ this.handleUserMessage(message);
464
+ break;
465
+ case 'ping':
466
+ this.send({ type: 'pong', timestamp: Date.now(), messageId: this.generateId() });
467
+ break;
468
+ case 'cd':
469
+ this.handleCdCommand(message.payload.path);
470
+ break;
471
+ case 'new_conversation':
472
+ this.handleNewConversation();
473
+ break;
474
+ case 'streaming_ack':
475
+ // Server acknowledged streaming message and sent back Telegram messageId
476
+ const ack = message.payload;
477
+ if (ack.messageId) {
478
+ this.currentMessageId = ack.messageId;
479
+ }
480
+ break;
481
+ default:
482
+ // Ignore unknown messages
483
+ break;
484
+ }
485
+ }
486
+ /**
487
+ * Handle user message from Telegram
488
+ */
489
+ handleUserMessage(message) {
490
+ const { chatId, text, telegramId, messageId, attachments } = message.payload;
491
+ this.currentChatId = chatId;
492
+ this.currentTelegramId = telegramId;
493
+ this.currentReplyToMessageId = messageId;
494
+ let prompt = text || '';
495
+ const attachmentInfo = attachments?.length ? ` with ${attachments.length} attachment(s)` : '';
496
+ console.log(`\nReceived from Telegram: ${text || '(no text)'}${attachmentInfo}`);
497
+ // Process attachments if present
498
+ if (attachments && attachments.length > 0) {
499
+ const savedFiles = this.saveAttachments(attachments);
500
+ if (savedFiles.length > 0) {
501
+ // Build prompt with attachment references
502
+ for (const file of savedFiles) {
503
+ if (file.type === 'voice') {
504
+ // For voice, ask Claude to listen and respond
505
+ prompt = `[Voice message saved to: ${file.path}]\n\nPlease listen to this voice message and respond to it.\n\n${prompt}`.trim();
506
+ }
507
+ else if (file.type === 'image') {
508
+ // For images, ask Claude to analyze
509
+ prompt = `[Image saved to: ${file.path}]\n\nPlease analyze this image.\n\n${prompt}`.trim();
510
+ }
511
+ else {
512
+ // For documents, mention the file
513
+ prompt = `[File saved to: ${file.path}]\n\n${prompt}`.trim();
514
+ }
515
+ }
516
+ }
517
+ }
518
+ // Ensure we have something to process
519
+ if (!prompt.trim()) {
520
+ this.sendResponse(chatId, 'No message content received.', 'complete');
521
+ return;
522
+ }
523
+ this.sendStatus('busy');
524
+ // Send initial streaming status to trigger loading animation
525
+ this.sendResponse(chatId, '', 'streaming');
526
+ // Run claude with the prompt (streaming will send updates)
527
+ this.runClaude(prompt);
528
+ }
529
+ /**
530
+ * Save attachments to temp directory and return file paths
531
+ */
532
+ saveAttachments(attachments) {
533
+ const savedFiles = [];
534
+ // Create temp directory in working directory
535
+ const attachmentsDir = path.join(this.workingDirectory, '.vibekit-attachments');
536
+ if (!fs.existsSync(attachmentsDir)) {
537
+ fs.mkdirSync(attachmentsDir, { recursive: true });
538
+ }
539
+ for (const attachment of attachments) {
540
+ try {
541
+ // Decode base64 data
542
+ const buffer = Buffer.from(attachment.data, 'base64');
543
+ // Generate unique filename
544
+ const timestamp = Date.now();
545
+ const filename = `${timestamp}-${attachment.filename}`;
546
+ const filePath = path.join(attachmentsDir, filename);
547
+ // Write file
548
+ fs.writeFileSync(filePath, buffer);
549
+ console.log(`Saved ${attachment.type}: ${filePath}`);
550
+ savedFiles.push({
551
+ type: attachment.type,
552
+ path: filePath,
553
+ });
554
+ }
555
+ catch (error) {
556
+ console.error(`Failed to save attachment ${attachment.filename}:`, error);
557
+ }
558
+ }
559
+ return savedFiles;
560
+ }
561
+ /**
562
+ * Handle new conversation command - reset conversation memory
563
+ */
564
+ handleNewConversation() {
565
+ this.hasActiveConversation = false;
566
+ console.log('Conversation reset - next message will start fresh');
567
+ }
568
+ /**
569
+ * Handle change directory command
570
+ */
571
+ handleCdCommand(newPath) {
572
+ const resolved = path.resolve(this.workingDirectory, newPath);
573
+ try {
574
+ const fs = require('fs');
575
+ if (!fs.existsSync(resolved) || !fs.statSync(resolved).isDirectory()) {
576
+ this.send({
577
+ type: 'cd_result',
578
+ payload: { success: false, error: 'Directory not found' },
579
+ timestamp: Date.now(),
580
+ messageId: this.generateId(),
581
+ });
582
+ return;
583
+ }
584
+ this.workingDirectory = resolved;
585
+ this.config.setLastDirectory(resolved);
586
+ this.send({
587
+ type: 'cd_result',
588
+ payload: { success: true, path: resolved },
589
+ timestamp: Date.now(),
590
+ messageId: this.generateId(),
591
+ });
592
+ console.log(`Changed directory to: ${resolved}`);
593
+ }
594
+ catch (error) {
595
+ this.send({
596
+ type: 'cd_result',
597
+ payload: { success: false, error: 'Failed to change directory' },
598
+ timestamp: Date.now(),
599
+ messageId: this.generateId(),
600
+ });
601
+ }
602
+ }
603
+ /**
604
+ * Send streaming update to Telegram
605
+ */
606
+ sendStreaming(chatId, text) {
607
+ this.send({
608
+ type: 'streaming',
609
+ payload: {
610
+ chatId,
611
+ messageId: this.currentMessageId,
612
+ text,
613
+ workingDirectory: this.workingDirectory,
614
+ },
615
+ timestamp: Date.now(),
616
+ messageId: this.generateId(),
617
+ });
618
+ }
619
+ /**
620
+ * Send a response back to Telegram
621
+ */
622
+ sendResponse(chatId, text, status) {
623
+ if (!this.currentTelegramId) {
624
+ console.error('Cannot send response: no telegram ID');
625
+ return;
626
+ }
627
+ this.send({
628
+ type: 'response',
629
+ payload: {
630
+ telegramId: this.currentTelegramId,
631
+ chatId,
632
+ text,
633
+ status,
634
+ replyToMessageId: this.currentReplyToMessageId || undefined,
635
+ },
636
+ timestamp: Date.now(),
637
+ messageId: this.generateId(),
638
+ });
639
+ if (status === 'complete') {
640
+ this.sendStatus('idle');
641
+ }
642
+ }
643
+ /**
644
+ * Send status update
645
+ */
646
+ sendStatus(status) {
647
+ this.send({
648
+ type: 'status',
649
+ payload: { status, workingDirectory: this.workingDirectory },
650
+ timestamp: Date.now(),
651
+ messageId: this.generateId(),
652
+ });
653
+ }
654
+ /**
655
+ * Send a message to the WebSocket
656
+ */
657
+ send(message) {
658
+ if (this.ws?.readyState === ws_1.default.OPEN) {
659
+ this.ws.send(JSON.stringify(message));
660
+ }
661
+ }
662
+ /**
663
+ * Schedule reconnection
664
+ */
665
+ scheduleReconnect() {
666
+ if (this.reconnectTimeout)
667
+ return;
668
+ console.log(`Reconnecting in ${this.reconnectDelay / 1000}s...`);
669
+ this.reconnectTimeout = setTimeout(() => {
670
+ this.reconnectTimeout = null;
671
+ this.reconnectDelay = Math.min(this.reconnectDelay * 2, 30000);
672
+ this.connect();
673
+ }, this.reconnectDelay);
674
+ }
675
+ /**
676
+ * Generate a unique message ID
677
+ */
678
+ generateId() {
679
+ return `${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
680
+ }
681
+ /**
682
+ * Shutdown the agent
683
+ */
684
+ shutdown() {
685
+ console.log('\nShutting down...');
686
+ this.stopStreaming();
687
+ if (this.claudeProcess) {
688
+ this.claudeProcess.kill();
689
+ }
690
+ if (this.ws) {
691
+ this.ws.close();
692
+ }
693
+ process.exit(0);
694
+ }
695
+ /**
696
+ * Set up Claude credentials for auto mode
697
+ * In auto mode, credentials come from CLAUDE_OAUTH_JSON env var or credentials file
698
+ */
699
+ async setupAutoModeCredentials() {
700
+ const credentialsFile = this.config.getCredentialsFile();
701
+ const oauthJson = process.env.CLAUDE_OAUTH_JSON;
702
+ // Determine where to get credentials from
703
+ let credentialsData = null;
704
+ if (credentialsFile && fs.existsSync(credentialsFile)) {
705
+ // Read from file
706
+ console.log(`[AutoMode] Loading credentials from ${credentialsFile}`);
707
+ credentialsData = fs.readFileSync(credentialsFile, 'utf-8');
708
+ }
709
+ else if (oauthJson) {
710
+ // Decode from environment variable (base64)
711
+ console.log('[AutoMode] Loading credentials from CLAUDE_OAUTH_JSON env var');
712
+ credentialsData = Buffer.from(oauthJson, 'base64').toString('utf-8');
713
+ }
714
+ if (!credentialsData) {
715
+ console.error('[AutoMode] No credentials found. Set CLAUDE_OAUTH_JSON or --credentials-file');
716
+ process.exit(1);
717
+ }
718
+ // Write credentials to Claude's expected location
719
+ const homeDir = process.env.HOME || '/home/agent';
720
+ const claudeDir = path.join(homeDir, '.claude');
721
+ if (!fs.existsSync(claudeDir)) {
722
+ fs.mkdirSync(claudeDir, { recursive: true });
723
+ }
724
+ // Write credentials
725
+ const credentialsPath = path.join(claudeDir, 'credentials.json');
726
+ fs.writeFileSync(credentialsPath, credentialsData, { mode: 0o600 });
727
+ // Also write settings to indicate onboarding is complete
728
+ const settingsPath = path.join(claudeDir, 'settings.json');
729
+ if (!fs.existsSync(settingsPath)) {
730
+ const settings = {
731
+ hasCompletedOnboarding: true,
732
+ acceptedTerms: true,
733
+ permissions: { defaultMode: 'acceptEdits' }
734
+ };
735
+ fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), { mode: 0o600 });
736
+ }
737
+ console.log('[AutoMode] Claude credentials configured');
738
+ }
739
+ }
740
+ exports.AgentClient = AgentClient;
741
+ //# sourceMappingURL=agent.js.map