claude-code-templates 1.10.0 → 1.11.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/README.md +6 -0
- package/bin/create-claude-config.js +1 -0
- package/package.json +2 -2
- package/src/analytics/core/ConversationAnalyzer.js +94 -20
- package/src/analytics/core/FileWatcher.js +146 -11
- package/src/analytics/data/DataCache.js +124 -19
- package/src/analytics/notifications/NotificationManager.js +37 -0
- package/src/analytics/notifications/WebSocketServer.js +1 -1
- package/src/analytics-web/FRONT_ARCHITECTURE.md +46 -0
- package/src/analytics-web/assets/js/{main.js → main.js.deprecated} +32 -3
- package/src/analytics-web/components/AgentsPage.js +2535 -0
- package/src/analytics-web/components/App.js +430 -0
- package/src/analytics-web/components/{Dashboard.js → Dashboard.js.deprecated} +23 -7
- package/src/analytics-web/components/DashboardPage.js +1527 -0
- package/src/analytics-web/components/Sidebar.js +197 -0
- package/src/analytics-web/components/ToolDisplay.js +539 -0
- package/src/analytics-web/index.html +3275 -1792
- package/src/analytics-web/services/DataService.js +89 -16
- package/src/analytics-web/services/StateService.js +9 -0
- package/src/analytics-web/services/WebSocketService.js +17 -5
- package/src/analytics.js +323 -35
- package/src/console-bridge.js +610 -0
- package/src/file-operations.js +143 -23
- package/src/hook-scanner.js +21 -1
- package/src/index.js +24 -1
- package/src/templates.js +28 -0
- package/src/test-console-bridge.js +67 -0
- package/src/utils.js +46 -0
- package/templates/ruby/.claude/commands/model.md +360 -0
- package/templates/ruby/.claude/commands/test.md +480 -0
- package/templates/ruby/.claude/settings.json +146 -0
- package/templates/ruby/.mcp.json +83 -0
- package/templates/ruby/CLAUDE.md +284 -0
- package/templates/ruby/examples/rails-app/.claude/commands/authentication.md +490 -0
- package/templates/ruby/examples/rails-app/CLAUDE.md +376 -0
|
@@ -0,0 +1,610 @@
|
|
|
1
|
+
const chalk = require('chalk');
|
|
2
|
+
const { spawn, exec } = require('child_process');
|
|
3
|
+
const WebSocket = require('ws');
|
|
4
|
+
const EventEmitter = require('events');
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* ConsoleBridge - Bridges Claude Code console interactions with WebSocket
|
|
9
|
+
* Intercepts stdin/stdout to enable web-based interactions
|
|
10
|
+
*/
|
|
11
|
+
class ConsoleBridge extends EventEmitter {
|
|
12
|
+
constructor(options = {}) {
|
|
13
|
+
super();
|
|
14
|
+
this.options = {
|
|
15
|
+
port: options.port || 3334,
|
|
16
|
+
debug: options.debug || false,
|
|
17
|
+
...options
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
this.wss = null;
|
|
21
|
+
this.clients = new Set();
|
|
22
|
+
this.currentInteraction = null;
|
|
23
|
+
this.interactionQueue = [];
|
|
24
|
+
this.isProcessingInteraction = false;
|
|
25
|
+
|
|
26
|
+
// Pattern recognition for Claude Code prompts
|
|
27
|
+
this.promptPatterns = [
|
|
28
|
+
/Do you want to proceed\?/,
|
|
29
|
+
/\s*❯\s*1\.\s*Yes/,
|
|
30
|
+
/\s*2\.\s*Yes,\s*and\s*(add|don't ask)/,
|
|
31
|
+
/\s*3\.\s*No,\s*and\s*tell\s*Claude/,
|
|
32
|
+
/Choose an option \(1-\d+\):/,
|
|
33
|
+
/Enter your choice:/,
|
|
34
|
+
/Please provide input:/
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
// Track multi-line prompt building
|
|
38
|
+
this.promptBuffer = [];
|
|
39
|
+
this.isCapturingPrompt = false;
|
|
40
|
+
this.promptTimeout = null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Initialize the console bridge
|
|
45
|
+
*/
|
|
46
|
+
async initialize() {
|
|
47
|
+
console.log(chalk.blue('🌉 Initializing Console Bridge...'));
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
await this.setupWebSocketServer();
|
|
51
|
+
this.setupProcessMonitoring();
|
|
52
|
+
|
|
53
|
+
console.log(chalk.green('✅ Console Bridge initialized successfully'));
|
|
54
|
+
console.log(chalk.cyan(`🔌 WebSocket server running on port ${this.options.port}`));
|
|
55
|
+
|
|
56
|
+
return true;
|
|
57
|
+
} catch (error) {
|
|
58
|
+
console.error(chalk.red('❌ Failed to initialize Console Bridge:'), error);
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Setup WebSocket server for web interface communication
|
|
65
|
+
*/
|
|
66
|
+
async setupWebSocketServer() {
|
|
67
|
+
return new Promise((resolve, reject) => {
|
|
68
|
+
this.wss = new WebSocket.Server({
|
|
69
|
+
port: this.options.port,
|
|
70
|
+
host: 'localhost'
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
this.wss.on('connection', (ws) => {
|
|
74
|
+
console.log(chalk.blue('🔌 Web interface connected to Console Bridge'));
|
|
75
|
+
this.clients.add(ws);
|
|
76
|
+
|
|
77
|
+
// Send current interaction if any
|
|
78
|
+
if (this.currentInteraction) {
|
|
79
|
+
ws.send(JSON.stringify({
|
|
80
|
+
type: 'console_interaction',
|
|
81
|
+
data: this.currentInteraction
|
|
82
|
+
}));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
ws.on('message', (message) => {
|
|
86
|
+
try {
|
|
87
|
+
const data = JSON.parse(message);
|
|
88
|
+
this.handleWebMessage(data);
|
|
89
|
+
} catch (error) {
|
|
90
|
+
console.error(chalk.red('❌ Invalid message from web interface:'), error);
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
ws.on('close', () => {
|
|
95
|
+
console.log(chalk.yellow('🔌 Web interface disconnected from Console Bridge'));
|
|
96
|
+
this.clients.delete(ws);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
ws.on('error', (error) => {
|
|
100
|
+
console.error(chalk.red('❌ WebSocket error:'), error);
|
|
101
|
+
this.clients.delete(ws);
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
this.wss.on('listening', resolve);
|
|
106
|
+
this.wss.on('error', reject);
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Setup monitoring of Claude Code processes
|
|
112
|
+
*/
|
|
113
|
+
setupProcessMonitoring() {
|
|
114
|
+
// Monitor for new Claude Code processes
|
|
115
|
+
setInterval(() => {
|
|
116
|
+
this.scanForClaudeProcesses();
|
|
117
|
+
}, 5000);
|
|
118
|
+
|
|
119
|
+
// Initial scan
|
|
120
|
+
this.scanForClaudeProcesses();
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Scan for running Claude Code processes
|
|
125
|
+
*/
|
|
126
|
+
async scanForClaudeProcesses() {
|
|
127
|
+
try {
|
|
128
|
+
const { exec } = require('child_process');
|
|
129
|
+
|
|
130
|
+
exec('ps aux | grep -E "claude[^-]|Claude" | grep -v grep', (error, stdout) => {
|
|
131
|
+
if (error) return;
|
|
132
|
+
|
|
133
|
+
const processes = stdout.split('\n')
|
|
134
|
+
.filter(line => line.trim())
|
|
135
|
+
.map(line => {
|
|
136
|
+
const parts = line.trim().split(/\s+/);
|
|
137
|
+
return {
|
|
138
|
+
pid: parts[1],
|
|
139
|
+
command: parts.slice(10).join(' '),
|
|
140
|
+
user: parts[0]
|
|
141
|
+
};
|
|
142
|
+
})
|
|
143
|
+
.filter(proc =>
|
|
144
|
+
proc.command.includes('claude') &&
|
|
145
|
+
!proc.command.includes('claude-code-templates') &&
|
|
146
|
+
!proc.command.includes('grep')
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
if (processes.length > 0) {
|
|
150
|
+
this.debug('Found Claude processes:', processes);
|
|
151
|
+
|
|
152
|
+
// Attempt to attach to the most likely Claude Code process
|
|
153
|
+
const claudeProcess = processes.find(p =>
|
|
154
|
+
p.command.includes('claude') &&
|
|
155
|
+
!p.command.includes('Helper') &&
|
|
156
|
+
!p.command.includes('.app')
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
if (claudeProcess && claudeProcess.pid !== this.attachedPid) {
|
|
160
|
+
this.attemptProcessAttachment(claudeProcess.pid);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
} catch (error) {
|
|
165
|
+
this.debug('Error scanning for Claude processes:', error);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Attempt to attach to a Claude Code process
|
|
171
|
+
*/
|
|
172
|
+
async attemptProcessAttachment(pid) {
|
|
173
|
+
try {
|
|
174
|
+
this.debug(`Attempting to attach to Claude process ${pid}`);
|
|
175
|
+
|
|
176
|
+
// Get terminal device for this process
|
|
177
|
+
const terminalInfo = await this.getProcessTerminal(pid);
|
|
178
|
+
if (!terminalInfo) {
|
|
179
|
+
console.warn(chalk.yellow(`⚠️ Could not determine terminal for process ${pid}`));
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
this.attachedPid = pid;
|
|
184
|
+
this.terminalDevice = terminalInfo.tty;
|
|
185
|
+
|
|
186
|
+
console.log(chalk.green(`✅ Attached to Claude Code process ${pid} on ${terminalInfo.tty}`));
|
|
187
|
+
|
|
188
|
+
// Start monitoring terminal output
|
|
189
|
+
await this.startTerminalMonitoring(terminalInfo.tty);
|
|
190
|
+
|
|
191
|
+
// For development, also simulate some prompts
|
|
192
|
+
this.simulatePromptDetection();
|
|
193
|
+
|
|
194
|
+
} catch (error) {
|
|
195
|
+
console.error(chalk.red(`❌ Failed to attach to process ${pid}:`), error);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Get terminal device information for a process
|
|
201
|
+
* @param {string} pid - Process ID
|
|
202
|
+
* @returns {Promise<Object|null>} Terminal information
|
|
203
|
+
*/
|
|
204
|
+
async getProcessTerminal(pid) {
|
|
205
|
+
return new Promise((resolve) => {
|
|
206
|
+
exec(`lsof -p ${pid} | grep -E "(tty|pts)" | head -1`, (error, stdout) => {
|
|
207
|
+
if (error || !stdout.trim()) {
|
|
208
|
+
resolve(null);
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const parts = stdout.trim().split(/\s+/);
|
|
213
|
+
const ttyPath = parts[parts.length - 1]; // Last part is the device path
|
|
214
|
+
|
|
215
|
+
if (ttyPath.startsWith('/dev/')) {
|
|
216
|
+
resolve({
|
|
217
|
+
tty: ttyPath,
|
|
218
|
+
pid: pid
|
|
219
|
+
});
|
|
220
|
+
} else {
|
|
221
|
+
resolve(null);
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Start monitoring terminal output for prompts
|
|
229
|
+
* @param {string} ttyPath - Path to terminal device
|
|
230
|
+
*/
|
|
231
|
+
async startTerminalMonitoring(ttyPath) {
|
|
232
|
+
try {
|
|
233
|
+
console.log(chalk.blue(`📡 Starting terminal monitoring for ${ttyPath}`));
|
|
234
|
+
|
|
235
|
+
// Use script command to capture terminal output
|
|
236
|
+
// This creates a typescript of the terminal session
|
|
237
|
+
const logFile = `/tmp/claude-terminal-${this.attachedPid}.log`;
|
|
238
|
+
|
|
239
|
+
// Monitor using tail -f approach on the terminal device (if readable)
|
|
240
|
+
this.startTerminalPolling(ttyPath, logFile);
|
|
241
|
+
|
|
242
|
+
} catch (error) {
|
|
243
|
+
console.error(chalk.red('❌ Error starting terminal monitoring:'), error);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Poll terminal for new output
|
|
249
|
+
* @param {string} ttyPath - Terminal device path
|
|
250
|
+
* @param {string} logFile - Log file path
|
|
251
|
+
*/
|
|
252
|
+
startTerminalPolling(ttyPath, logFile) {
|
|
253
|
+
// Try to read directly from terminal device (may need permissions)
|
|
254
|
+
if (this.terminalPollingInterval) {
|
|
255
|
+
clearInterval(this.terminalPollingInterval);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
let lastPosition = 0;
|
|
259
|
+
let outputBuffer = '';
|
|
260
|
+
|
|
261
|
+
this.terminalPollingInterval = setInterval(async () => {
|
|
262
|
+
try {
|
|
263
|
+
// Try to read the screen content using a more accessible approach
|
|
264
|
+
// Use script to capture current terminal state
|
|
265
|
+
exec(`script -q /dev/null tail -n 50 ${ttyPath}`, { timeout: 1000 }, (error, stdout, stderr) => {
|
|
266
|
+
if (!error && stdout) {
|
|
267
|
+
// Look for prompt patterns in the output
|
|
268
|
+
this.analyzeTerminalOutput(stdout);
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
// Alternative: monitor process output through ps
|
|
273
|
+
this.monitorProcessStatus();
|
|
274
|
+
|
|
275
|
+
} catch (error) {
|
|
276
|
+
this.debug('Terminal polling error:', error.message);
|
|
277
|
+
}
|
|
278
|
+
}, 2000); // Check every 2 seconds
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Monitor the Claude Code process status for changes
|
|
283
|
+
*/
|
|
284
|
+
monitorProcessStatus() {
|
|
285
|
+
if (!this.attachedPid) return;
|
|
286
|
+
|
|
287
|
+
exec(`ps -p ${this.attachedPid} -o state,time,command`, (error, stdout) => {
|
|
288
|
+
if (error) {
|
|
289
|
+
// Process might have ended
|
|
290
|
+
console.log(chalk.yellow('🔄 Monitored Claude Code process ended'));
|
|
291
|
+
this.attachedPid = null;
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const lines = stdout.trim().split('\n');
|
|
296
|
+
if (lines.length > 1) {
|
|
297
|
+
const processLine = lines[1];
|
|
298
|
+
const state = processLine.split(/\s+/)[0];
|
|
299
|
+
|
|
300
|
+
// Check if process is waiting for input (state: T, S+)
|
|
301
|
+
if (state.includes('T') || state.includes('S+')) {
|
|
302
|
+
this.debug('Process appears to be waiting for input');
|
|
303
|
+
// This might indicate a prompt is active
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Analyze terminal output for prompts
|
|
311
|
+
* @param {string} output - Terminal output to analyze
|
|
312
|
+
*/
|
|
313
|
+
analyzeTerminalOutput(output) {
|
|
314
|
+
const lines = output.split('\n');
|
|
315
|
+
const recentLines = lines.slice(-10); // Last 10 lines
|
|
316
|
+
const fullText = recentLines.join('\n');
|
|
317
|
+
|
|
318
|
+
// Check for Claude Code prompt patterns
|
|
319
|
+
for (const pattern of this.promptPatterns) {
|
|
320
|
+
if (pattern.test(fullText)) {
|
|
321
|
+
console.log(chalk.yellow('🎯 Potential prompt detected in terminal output!'));
|
|
322
|
+
console.log(chalk.gray('Lines:', recentLines.slice(-5).join(' | ')));
|
|
323
|
+
|
|
324
|
+
// Try to extract and parse the prompt
|
|
325
|
+
this.handleDetectedPrompt(fullText);
|
|
326
|
+
break;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Simulate prompt detection (placeholder for actual implementation)
|
|
333
|
+
* In reality, this would intercept actual Claude Code output
|
|
334
|
+
*/
|
|
335
|
+
simulatePromptDetection() {
|
|
336
|
+
// This is a simulation - in reality we'd parse actual output
|
|
337
|
+
setTimeout(() => {
|
|
338
|
+
if (Math.random() > 0.7) { // Simulate occasional prompts
|
|
339
|
+
this.handleDetectedPrompt(`Read file
|
|
340
|
+
|
|
341
|
+
Search(pattern: "(?:Yes|No|yes|no)(?:,\\s*and\\s*don't\\s*ask\\s*again)?", path:
|
|
342
|
+
"../../../../../../../.claude/projects/-Users-danipower-Proyectos-Github-claude-code-templates", include: "*.jsonl")
|
|
343
|
+
|
|
344
|
+
Do you want to proceed?
|
|
345
|
+
❯ 1. Yes
|
|
346
|
+
2. Yes, and add /Users/danipower/.claude/projects/-Users-danipower-Proyectos-Github-claude-code-templates as a working directory for this session
|
|
347
|
+
3. No, and tell Claude what to do differently (esc)`);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// Continue simulation
|
|
351
|
+
setTimeout(() => this.simulatePromptDetection(), 10000 + Math.random() * 20000);
|
|
352
|
+
}, 5000);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Handle detected prompt from Claude Code
|
|
357
|
+
*/
|
|
358
|
+
handleDetectedPrompt(promptText) {
|
|
359
|
+
//console.log(chalk.yellow('🤖 Claude Code prompt detected:'));
|
|
360
|
+
//console.log(chalk.gray(promptText));
|
|
361
|
+
|
|
362
|
+
const interaction = this.parsePrompt(promptText);
|
|
363
|
+
|
|
364
|
+
if (interaction) {
|
|
365
|
+
this.currentInteraction = {
|
|
366
|
+
...interaction,
|
|
367
|
+
id: 'claude-prompt-' + Date.now(),
|
|
368
|
+
timestamp: new Date().toISOString()
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
// Send to web interface
|
|
372
|
+
this.broadcastToClients({
|
|
373
|
+
type: 'console_interaction',
|
|
374
|
+
data: this.currentInteraction
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
//console.log(chalk.blue('📡 Sent prompt to web interface'));
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* Parse Claude Code prompt text into structured interaction
|
|
383
|
+
*/
|
|
384
|
+
parsePrompt(promptText) {
|
|
385
|
+
const lines = promptText.split('\n').map(line => line.trim());
|
|
386
|
+
|
|
387
|
+
// Look for the main prompt question
|
|
388
|
+
const promptLine = lines.find(line =>
|
|
389
|
+
line.includes('Do you want to proceed?') ||
|
|
390
|
+
line.includes('Choose an option') ||
|
|
391
|
+
line.includes('Enter your choice') ||
|
|
392
|
+
line.includes('Please provide input')
|
|
393
|
+
);
|
|
394
|
+
|
|
395
|
+
if (!promptLine) return null;
|
|
396
|
+
|
|
397
|
+
// Look for numbered options
|
|
398
|
+
const optionLines = lines.filter(line =>
|
|
399
|
+
/^\s*❯?\s*\d+\.\s*/.test(line) ||
|
|
400
|
+
/^\s*\d+\.\s*/.test(line)
|
|
401
|
+
);
|
|
402
|
+
|
|
403
|
+
if (optionLines.length > 0) {
|
|
404
|
+
// Choice-based prompt
|
|
405
|
+
const options = optionLines.map(line =>
|
|
406
|
+
line.replace(/^\s*❯?\s*\d+\.\s*/, '').trim()
|
|
407
|
+
);
|
|
408
|
+
|
|
409
|
+
// Extract tool description (usually the first few lines)
|
|
410
|
+
const descriptionLines = lines.slice(0, lines.indexOf(lines.find(l => l.includes('Do you want'))) || 3);
|
|
411
|
+
const description = descriptionLines.join('\n').trim();
|
|
412
|
+
|
|
413
|
+
return {
|
|
414
|
+
type: 'choice',
|
|
415
|
+
tool: this.extractToolName(description),
|
|
416
|
+
description,
|
|
417
|
+
prompt: promptLine,
|
|
418
|
+
options
|
|
419
|
+
};
|
|
420
|
+
} else {
|
|
421
|
+
// Text input prompt
|
|
422
|
+
return {
|
|
423
|
+
type: 'text',
|
|
424
|
+
tool: 'Console Input',
|
|
425
|
+
description: lines.slice(0, -1).join('\n').trim(),
|
|
426
|
+
prompt: promptLine
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* Extract tool name from description
|
|
433
|
+
*/
|
|
434
|
+
extractToolName(description) {
|
|
435
|
+
const toolMatch = description.match(/^([A-Za-z]+)(\(|$)/);
|
|
436
|
+
return toolMatch ? toolMatch[1] : 'Tool';
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/**
|
|
440
|
+
* Handle message from web interface
|
|
441
|
+
*/
|
|
442
|
+
handleWebMessage(data) {
|
|
443
|
+
if (data.type === 'console_response' && this.currentInteraction) {
|
|
444
|
+
console.log(chalk.green('📱 Received response from web interface:'), data.data);
|
|
445
|
+
|
|
446
|
+
// In a real implementation, this would send the response to Claude Code
|
|
447
|
+
this.sendResponseToClaudeCode(data.data);
|
|
448
|
+
|
|
449
|
+
this.currentInteraction = null;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
/**
|
|
454
|
+
* Send response back to Claude Code process
|
|
455
|
+
* This attempts to write directly to the terminal device
|
|
456
|
+
*/
|
|
457
|
+
sendResponseToClaudeCode(response) {
|
|
458
|
+
console.log(chalk.blue('🔄 Sending response to Claude Code...'));
|
|
459
|
+
|
|
460
|
+
if (!this.attachedPid || !this.terminalDevice) {
|
|
461
|
+
console.warn(chalk.yellow('⚠️ No attached process - falling back to simulation'));
|
|
462
|
+
this.simulateResponse(response);
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
if (response.type === 'choice') {
|
|
467
|
+
// Send the choice number (1-indexed)
|
|
468
|
+
const choiceNumber = response.value + 1;
|
|
469
|
+
console.log(chalk.green(`✅ Choice selected: ${choiceNumber} - ${response.text}`));
|
|
470
|
+
|
|
471
|
+
this.writeToTerminal(choiceNumber.toString() + '\n');
|
|
472
|
+
|
|
473
|
+
} else if (response.type === 'text') {
|
|
474
|
+
console.log(chalk.green(`✅ Text input: "${response.value}"`));
|
|
475
|
+
|
|
476
|
+
this.writeToTerminal(response.value + '\n');
|
|
477
|
+
|
|
478
|
+
} else if (response.type === 'cancel') {
|
|
479
|
+
console.log(chalk.yellow('🚫 User cancelled interaction'));
|
|
480
|
+
|
|
481
|
+
// Send ESC key or Ctrl+C
|
|
482
|
+
this.writeToTerminal('\x1b'); // ESC key
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
/**
|
|
487
|
+
* Write text to the terminal device
|
|
488
|
+
* @param {string} text - Text to write
|
|
489
|
+
*/
|
|
490
|
+
writeToTerminal(text) {
|
|
491
|
+
if (!this.terminalDevice) {
|
|
492
|
+
console.warn(chalk.yellow('⚠️ No terminal device available'));
|
|
493
|
+
return;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
try {
|
|
497
|
+
// Try different approaches to send input to the terminal
|
|
498
|
+
|
|
499
|
+
// Method 1: Use expect script to send input
|
|
500
|
+
const expectScript = `
|
|
501
|
+
spawn -open [open ${this.terminalDevice} w]
|
|
502
|
+
send "${text.replace(/"/g, '\\"')}"
|
|
503
|
+
close
|
|
504
|
+
`;
|
|
505
|
+
|
|
506
|
+
exec(`expect -c '${expectScript}'`, (error, stdout, stderr) => {
|
|
507
|
+
if (error) {
|
|
508
|
+
console.log(chalk.yellow('⚠️ Expect method failed, trying alternative...'));
|
|
509
|
+
this.tryAlternativeInput(text);
|
|
510
|
+
} else {
|
|
511
|
+
console.log(chalk.green('✅ Input sent via expect'));
|
|
512
|
+
}
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
} catch (error) {
|
|
516
|
+
console.error(chalk.red('❌ Error writing to terminal:'), error);
|
|
517
|
+
this.tryAlternativeInput(text);
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
/**
|
|
522
|
+
* Try alternative method to send input
|
|
523
|
+
* @param {string} text - Text to send
|
|
524
|
+
*/
|
|
525
|
+
tryAlternativeInput(text) {
|
|
526
|
+
// Method 2: Try using osascript (AppleScript on macOS) to send keystrokes
|
|
527
|
+
if (process.platform === 'darwin') {
|
|
528
|
+
const script = `
|
|
529
|
+
tell application "Terminal"
|
|
530
|
+
do script "${text.replace(/"/g, '\\"').replace(/\n/g, '\\n')}" in front window
|
|
531
|
+
end tell
|
|
532
|
+
`;
|
|
533
|
+
|
|
534
|
+
exec(`osascript -e '${script}'`, (error) => {
|
|
535
|
+
if (error) {
|
|
536
|
+
console.log(chalk.yellow('⚠️ AppleScript method failed, falling back to simulation'));
|
|
537
|
+
this.simulateResponse({ type: 'choice', value: parseInt(text) - 1, text: text.trim() });
|
|
538
|
+
} else {
|
|
539
|
+
console.log(chalk.green('✅ Input sent via AppleScript'));
|
|
540
|
+
}
|
|
541
|
+
});
|
|
542
|
+
} else {
|
|
543
|
+
// On Linux, try using xdotool or similar
|
|
544
|
+
console.log(chalk.yellow('⚠️ Non-macOS platform - input simulation not implemented'));
|
|
545
|
+
this.simulateResponse({ type: 'choice', value: parseInt(text) - 1, text: text.trim() });
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
/**
|
|
550
|
+
* Simulate response (fallback when real interaction fails)
|
|
551
|
+
* @param {Object} response - Response object
|
|
552
|
+
*/
|
|
553
|
+
simulateResponse(response) {
|
|
554
|
+
if (response.type === 'choice') {
|
|
555
|
+
const choiceNumber = response.value + 1;
|
|
556
|
+
console.log(chalk.gray(`[Simulated] Sending "${choiceNumber}" to Claude Code stdin`));
|
|
557
|
+
} else if (response.type === 'text') {
|
|
558
|
+
console.log(chalk.gray(`[Simulated] Sending "${response.value}" to Claude Code stdin`));
|
|
559
|
+
} else if (response.type === 'cancel') {
|
|
560
|
+
console.log(chalk.gray('[Simulated] Sending ESC to Claude Code stdin'));
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
/**
|
|
565
|
+
* Broadcast message to all connected web clients
|
|
566
|
+
*/
|
|
567
|
+
broadcastToClients(message) {
|
|
568
|
+
const messageStr = JSON.stringify(message);
|
|
569
|
+
|
|
570
|
+
this.clients.forEach(client => {
|
|
571
|
+
if (client.readyState === WebSocket.OPEN) {
|
|
572
|
+
client.send(messageStr);
|
|
573
|
+
}
|
|
574
|
+
});
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
/**
|
|
578
|
+
* Debug logging
|
|
579
|
+
*/
|
|
580
|
+
debug(...args) {
|
|
581
|
+
if (this.options.debug) {
|
|
582
|
+
console.log(chalk.gray('[ConsoleBridge Debug]'), ...args);
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
/**
|
|
587
|
+
* Cleanup and shutdown
|
|
588
|
+
*/
|
|
589
|
+
async shutdown() {
|
|
590
|
+
console.log(chalk.yellow('🛑 Shutting down Console Bridge...'));
|
|
591
|
+
|
|
592
|
+
// Stop terminal monitoring
|
|
593
|
+
if (this.terminalPollingInterval) {
|
|
594
|
+
clearInterval(this.terminalPollingInterval);
|
|
595
|
+
this.terminalPollingInterval = null;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
if (this.wss) {
|
|
599
|
+
this.wss.close();
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
this.clients.clear();
|
|
603
|
+
this.attachedPid = null;
|
|
604
|
+
this.terminalDevice = null;
|
|
605
|
+
|
|
606
|
+
console.log(chalk.green('✅ Console Bridge shutdown complete'));
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
module.exports = ConsoleBridge;
|