@adiontaegerron/claude-multi-terminal 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.
@@ -0,0 +1,22 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(find:*)",
5
+ "Bash(npm init:*)",
6
+ "Bash(npm install:*)",
7
+ "Bash(npm start)",
8
+ "Bash(npx:*)",
9
+ "Bash(rm:*)",
10
+ "Bash(npm run rebuild:*)",
11
+ "Bash(git init:*)",
12
+ "Bash(git remote add:*)",
13
+ "Bash(git add:*)",
14
+ "Bash(git commit:*)",
15
+ "Bash(git push:*)",
16
+ "Bash(mkdir:*)",
17
+ "Bash(chmod:*)",
18
+ "Bash(npm:*)"
19
+ ],
20
+ "deny": []
21
+ }
22
+ }
package/CLAUDE.md ADDED
@@ -0,0 +1,63 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## Project Overview
6
+
7
+ This is a multi-terminal desktop application built with Electron, xterm.js, and node-pty. It allows users to manage multiple terminal sessions with individual UI controls for each terminal.
8
+
9
+ ## Development Commands
10
+
11
+ Since this is a new project, standard Electron/Node.js commands will be used:
12
+
13
+ ```bash
14
+ # Install dependencies
15
+ npm install
16
+
17
+ # Start the application in development mode
18
+ npm start
19
+
20
+ # Build the application (once configured)
21
+ npm run build
22
+
23
+ # Run tests (once configured)
24
+ npm test
25
+ ```
26
+
27
+ ## Architecture
28
+
29
+ The application follows a standard Electron architecture:
30
+
31
+ - **Main Process**: Manages window creation, native OS interactions, and spawns terminal processes using node-pty
32
+ - **Renderer Process**: Handles the UI, terminal rendering with xterm.js, and user interactions
33
+ - **IPC Communication**: Main and renderer processes communicate via Electron's IPC for terminal operations
34
+
35
+ Key components:
36
+ - Terminal instances are managed by node-pty in the main process
37
+ - Each terminal UI is rendered by xterm.js in the renderer
38
+ - Commands are sent from text inputs to terminals via IPC
39
+
40
+ ## Project Requirements
41
+
42
+ The application must support:
43
+ - Multiple terminal sessions (2+ terminals)
44
+ - Each terminal displays:
45
+ - xterm.js terminal area for output and keyboard input
46
+ - Text input box for sending commands
47
+ - Send button (Enter key also submits)
48
+ - "New Terminal" button for creating sessions
49
+ - Real-time output display
50
+ - Local execution only (no cloud dependencies)
51
+
52
+ Optional features:
53
+ - Tabs or sidebar for terminal switching
54
+ - Individual terminal close functionality
55
+
56
+ ## Technical Stack
57
+
58
+ - **Electron**: Desktop application framework
59
+ - **xterm.js**: Terminal UI rendering
60
+ - **node-pty**: Terminal process management
61
+ - **JavaScript/TypeScript**: Implementation language (choose based on simplicity)
62
+
63
+
package/README.md ADDED
@@ -0,0 +1,115 @@
1
+ # Claude Multi-Terminal
2
+
3
+ A multi-terminal editor for coordinating multiple Claude Code instances. Run multiple Claude sessions side-by-side with a unified chat interface to send commands to selected terminals.
4
+
5
+ ![Claude Multi-Terminal Screenshot](screenshot.png)
6
+
7
+ ## Features
8
+
9
+ - **Multi-Terminal Support**: Run multiple Claude Code instances in a 2x2 grid layout
10
+ - **Unified Chat Interface**: Send commands to selected terminals from a single chat interface
11
+ - **Terminal Selection**: Choose which terminals receive your commands with checkboxes
12
+ - **Default Prompt**: Set a default prompt to prepend to all messages
13
+ - **Auto-Start Claude**: Each terminal automatically starts Claude Code
14
+ - **Rename Terminals**: Double-click terminal titles to rename them
15
+ - **Responsive Layout**: Adapts to different screen sizes
16
+
17
+ ## Installation
18
+
19
+ ### From npm (when published)
20
+
21
+ ```bash
22
+ npm install -g claude-multi-terminal
23
+ ```
24
+
25
+ ### From GitHub
26
+
27
+ ```bash
28
+ git clone https://github.com/yourusername/multi-editor.git
29
+ cd multi-editor
30
+ npm install
31
+ npm link
32
+ ```
33
+
34
+ ## Usage
35
+
36
+ Navigate to your project directory and run:
37
+
38
+ ```bash
39
+ claude-multi
40
+ ```
41
+
42
+ This will open the multi-terminal editor with all terminals starting in your current directory.
43
+
44
+ ### Basic Workflow
45
+
46
+ 1. **Start the Application**: Run `claude-multi` in your project directory
47
+ 2. **Create Terminals**: Click "New Terminal" to add more Claude instances (starts with one)
48
+ 3. **Select Terminals**: Use checkboxes in the chat sidebar to select which terminals receive commands
49
+ 4. **Send Commands**: Type in the chat input and click "Send" or press Enter
50
+ 5. **Use Default Prompt**: Expand the "Default Prompt" section to set a prompt that's prepended to all messages
51
+
52
+ ### Tips
53
+
54
+ - Each terminal automatically starts Claude Code when created
55
+ - Messages include a plan mode instruction by default
56
+ - Double-click terminal titles to rename them for better organization
57
+ - The application uses your current directory as the working directory for all terminals
58
+
59
+ ## Development
60
+
61
+ ### Prerequisites
62
+
63
+ - Node.js (v14 or higher)
64
+ - npm
65
+
66
+ ### Local Development
67
+
68
+ ```bash
69
+ # Clone the repository
70
+ git clone https://github.com/yourusername/multi-editor.git
71
+ cd multi-editor
72
+
73
+ # Install dependencies
74
+ npm install
75
+
76
+ # Start the application
77
+ npm start
78
+ ```
79
+
80
+ ### Building
81
+
82
+ ```bash
83
+ # Rebuild native modules
84
+ npm run rebuild
85
+ ```
86
+
87
+ ## Requirements
88
+
89
+ - **Claude Code**: You must have Claude Code installed and accessible via the `claude` command
90
+ - **Operating System**: Works on macOS, Windows, and Linux
91
+ - **Node.js**: Requires Node.js 14 or higher
92
+
93
+ ## License
94
+
95
+ MIT
96
+
97
+ ## Contributing
98
+
99
+ Contributions are welcome! Please feel free to submit a Pull Request.
100
+
101
+ 1. Fork the repository
102
+ 2. Create your feature branch (`git checkout -b feature/AmazingFeature`)
103
+ 3. Commit your changes (`git commit -m 'Add some AmazingFeature'`)
104
+ 4. Push to the branch (`git push origin feature/AmazingFeature`)
105
+ 5. Open a Pull Request
106
+
107
+ ## Issues
108
+
109
+ If you find a bug or have a feature request, please open an issue on [GitHub](https://github.com/yourusername/multi-editor/issues).
110
+
111
+ ## Acknowledgments
112
+
113
+ - Built with [Electron](https://www.electronjs.org/)
114
+ - Terminal emulation by [xterm.js](https://xtermjs.org/)
115
+ - PTY support from [node-pty](https://github.com/microsoft/node-pty)
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { spawn } = require('child_process');
4
+ const path = require('path');
5
+
6
+ // Get the directory where the package is installed
7
+ const appPath = path.join(__dirname, '..');
8
+
9
+ // Get the current working directory where the command was run
10
+ const cwd = process.cwd();
11
+
12
+ // Pass the current directory as an argument to the Electron app
13
+ const electron = require('electron');
14
+ const child = spawn(electron, [appPath, '--cwd', cwd], {
15
+ stdio: 'inherit',
16
+ env: {
17
+ ...process.env,
18
+ ELECTRON_NO_ATTACH_CONSOLE: 'true'
19
+ }
20
+ });
21
+
22
+ child.on('close', (code) => {
23
+ process.exit(code);
24
+ });
25
+
26
+ // Handle ctrl+c
27
+ process.on('SIGINT', () => {
28
+ child.kill('SIGINT');
29
+ });
package/index.html ADDED
@@ -0,0 +1,64 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <title>Multi Terminal Editor</title>
6
+ <link rel="stylesheet" href="style.css">
7
+ <link rel="stylesheet" href="node_modules/xterm/css/xterm.css">
8
+ </head>
9
+ <body>
10
+ <div id="app">
11
+ <div id="toolbar">
12
+ <button id="new-terminal-btn">New Terminal</button>
13
+ </div>
14
+
15
+ <div id="main-layout">
16
+ <div id="terminals-area">
17
+ <div id="terminals-container">
18
+ <!-- Terminal instances will be added here dynamically -->
19
+ </div>
20
+ </div>
21
+
22
+ <div id="chat-sidebar">
23
+ <div class="chat-header">
24
+ <h2>Chat</h2>
25
+ </div>
26
+
27
+ <div id="chat-messages">
28
+ <!-- Chat messages will appear here -->
29
+ </div>
30
+
31
+ <div id="chat-controls">
32
+ <div id="terminal-selector">
33
+ <label>Send to:</label>
34
+ <div id="terminal-checkboxes">
35
+ <!-- Terminal checkboxes will be added here dynamically -->
36
+ </div>
37
+ </div>
38
+
39
+ <div id="default-prompt-section" class="collapsible">
40
+ <div class="collapsible-header">
41
+ <button class="collapse-toggle">▶</button>
42
+ <span>Default Prompt</span>
43
+ </div>
44
+ <div class="collapsible-content" style="display: none;">
45
+ <textarea id="default-prompt-text" placeholder="Enter default prompt to prepend to all messages..."></textarea>
46
+ <label>
47
+ <input type="checkbox" id="use-default-prompt" />
48
+ <span>Always prepend to messages</span>
49
+ </label>
50
+ </div>
51
+ </div>
52
+
53
+ <div class="message-input-section">
54
+ <textarea id="chat-input" placeholder="Type your message..."></textarea>
55
+ <button id="send-message-btn">Send</button>
56
+ </div>
57
+ </div>
58
+ </div>
59
+ </div>
60
+ </div>
61
+
62
+ <script src="renderer.js"></script>
63
+ </body>
64
+ </html>
package/main.js ADDED
@@ -0,0 +1,116 @@
1
+ const { app, BrowserWindow, ipcMain } = require('electron');
2
+ const path = require('path');
3
+ const pty = require('node-pty');
4
+
5
+ // Store terminal instances
6
+ const terminals = new Map();
7
+ let mainWindow;
8
+
9
+ // Get the current working directory from command line args or use current directory
10
+ let workingDirectory = process.cwd();
11
+ const cwdIndex = process.argv.indexOf('--cwd');
12
+ if (cwdIndex !== -1 && process.argv[cwdIndex + 1]) {
13
+ workingDirectory = process.argv[cwdIndex + 1];
14
+ }
15
+
16
+ // Create the main application window
17
+ function createWindow() {
18
+ mainWindow = new BrowserWindow({
19
+ width: 1200,
20
+ height: 800,
21
+ webPreferences: {
22
+ nodeIntegration: true,
23
+ contextIsolation: false,
24
+ // Enable webSecurity in production
25
+ webSecurity: false
26
+ }
27
+ });
28
+
29
+ mainWindow.loadFile('index.html');
30
+
31
+ // Open DevTools in development
32
+ mainWindow.webContents.openDevTools();
33
+
34
+ mainWindow.on('closed', () => {
35
+ // Clean up all terminals on window close
36
+ terminals.forEach(term => term.kill());
37
+ terminals.clear();
38
+ mainWindow = null;
39
+ });
40
+ }
41
+
42
+ // Initialize the app
43
+ app.whenReady().then(createWindow);
44
+
45
+ app.on('window-all-closed', () => {
46
+ if (process.platform !== 'darwin') {
47
+ app.quit();
48
+ }
49
+ });
50
+
51
+ app.on('activate', () => {
52
+ if (BrowserWindow.getAllWindows().length === 0) {
53
+ createWindow();
54
+ }
55
+ });
56
+
57
+ // IPC handlers for terminal operations
58
+
59
+ // Create a new terminal
60
+ ipcMain.handle('terminal:create', (event, terminalId) => {
61
+ const shell = process.platform === 'win32' ? 'powershell.exe' : process.env.SHELL || '/bin/bash';
62
+
63
+ const ptyProcess = pty.spawn(shell, [], {
64
+ name: 'xterm-color',
65
+ cols: 80,
66
+ rows: 30,
67
+ cwd: workingDirectory, // Use the directory where the command was run
68
+ env: process.env
69
+ });
70
+
71
+ terminals.set(terminalId, ptyProcess);
72
+
73
+ // Forward terminal output to renderer
74
+ ptyProcess.onData((data) => {
75
+ mainWindow.webContents.send('terminal:data', terminalId, data);
76
+ });
77
+
78
+ // Handle terminal exit
79
+ ptyProcess.onExit(({ exitCode }) => {
80
+ mainWindow.webContents.send('terminal:exit', terminalId, exitCode);
81
+ terminals.delete(terminalId);
82
+ });
83
+
84
+ return { success: true };
85
+ });
86
+
87
+ // Write data to terminal
88
+ ipcMain.handle('terminal:write', (event, terminalId, data) => {
89
+ const terminal = terminals.get(terminalId);
90
+ if (terminal) {
91
+ terminal.write(data);
92
+ return { success: true };
93
+ }
94
+ return { success: false, error: 'Terminal not found' };
95
+ });
96
+
97
+ // Resize terminal
98
+ ipcMain.handle('terminal:resize', (event, terminalId, cols, rows) => {
99
+ const terminal = terminals.get(terminalId);
100
+ if (terminal) {
101
+ terminal.resize(cols, rows);
102
+ return { success: true };
103
+ }
104
+ return { success: false, error: 'Terminal not found' };
105
+ });
106
+
107
+ // Close terminal
108
+ ipcMain.handle('terminal:close', (event, terminalId) => {
109
+ const terminal = terminals.get(terminalId);
110
+ if (terminal) {
111
+ terminal.kill();
112
+ terminals.delete(terminalId);
113
+ return { success: true };
114
+ }
115
+ return { success: false, error: 'Terminal not found' };
116
+ });
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "@adiontaegerron/claude-multi-terminal",
3
+ "version": "1.0.0",
4
+ "description": "Multi-terminal editor for coordinating multiple Claude Code instances",
5
+ "main": "main.js",
6
+ "bin": {
7
+ "claude-multi": "./bin/claude-multi.js"
8
+ },
9
+ "scripts": {
10
+ "start": "electron .",
11
+ "postinstall": "electron-rebuild",
12
+ "rebuild": "electron-rebuild",
13
+ "test": "echo \"Error: no test specified\" && exit 1"
14
+ },
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "git+https://github.com/adiontae-tp/multi-terminal.git"
18
+ },
19
+ "keywords": [
20
+ "claude",
21
+ "terminal",
22
+ "multi-terminal",
23
+ "electron",
24
+ "xterm",
25
+ "claude-code",
26
+ "ai",
27
+ "development-tools"
28
+ ],
29
+ "author": "",
30
+ "license": "MIT",
31
+ "bugs": {
32
+ "url": "https://github.com/adiontae-tp/multi-terminal/issues"
33
+ },
34
+ "homepage": "https://github.com/adiontae-tp/multi-terminal#readme",
35
+ "dependencies": {
36
+ "electron": "^37.2.5",
37
+ "node-pty": "^1.0.0",
38
+ "xterm": "^5.3.0",
39
+ "xterm-addon-fit": "^0.8.0"
40
+ },
41
+ "devDependencies": {
42
+ "@electron/rebuild": "^3.7.2"
43
+ }
44
+ }
package/renderer.js ADDED
@@ -0,0 +1,344 @@
1
+ const { ipcRenderer } = require('electron');
2
+ const { Terminal } = require('xterm');
3
+ const { FitAddon } = require('xterm-addon-fit');
4
+
5
+ // Track all terminal instances
6
+ const terminals = new Map();
7
+ let terminalCounter = 0;
8
+
9
+ // Create a new terminal instance
10
+ async function createTerminal() {
11
+ terminalCounter++;
12
+ const terminalId = `terminal-${terminalCounter}`;
13
+
14
+ // Create terminal container
15
+ const terminalContainer = document.createElement('div');
16
+ terminalContainer.className = 'terminal-container';
17
+ terminalContainer.id = terminalId;
18
+
19
+ // Create terminal header with close button (no checkbox)
20
+ const header = document.createElement('div');
21
+ header.className = 'terminal-header';
22
+
23
+ const title = document.createElement('span');
24
+ title.textContent = `Terminal ${terminalCounter}`;
25
+ title.className = 'terminal-title';
26
+ title.style.cursor = 'pointer';
27
+
28
+ // Create rename input (hidden by default)
29
+ const renameInput = document.createElement('input');
30
+ renameInput.type = 'text';
31
+ renameInput.className = 'rename-input';
32
+ renameInput.style.display = 'none';
33
+ renameInput.value = title.textContent;
34
+
35
+ const closeBtn = document.createElement('button');
36
+ closeBtn.textContent = '×';
37
+ closeBtn.className = 'close-btn';
38
+ closeBtn.onclick = () => closeTerminal(terminalId);
39
+
40
+ header.appendChild(title);
41
+ header.appendChild(renameInput);
42
+ header.appendChild(closeBtn);
43
+
44
+ // Create terminal element
45
+ const terminalElement = document.createElement('div');
46
+ terminalElement.className = 'terminal';
47
+
48
+ // Assemble the container
49
+ terminalContainer.appendChild(header);
50
+ terminalContainer.appendChild(terminalElement);
51
+
52
+ // Add to DOM
53
+ document.getElementById('terminals-container').appendChild(terminalContainer);
54
+
55
+ // Initialize xterm.js
56
+ const xterm = new Terminal({
57
+ cursorBlink: true,
58
+ fontSize: 14,
59
+ fontFamily: 'Consolas, "Courier New", monospace',
60
+ theme: {
61
+ background: '#1e1e1e',
62
+ foreground: '#d4d4d4'
63
+ },
64
+ convertEol: true,
65
+ allowTransparency: false,
66
+ scrollback: 1000
67
+ });
68
+
69
+ // Add fit addon
70
+ const fitAddon = new FitAddon();
71
+ xterm.loadAddon(fitAddon);
72
+
73
+ // Open terminal in the DOM element
74
+ xterm.open(terminalElement);
75
+ fitAddon.fit();
76
+
77
+ // Store terminal info
78
+ terminals.set(terminalId, {
79
+ xterm,
80
+ fitAddon,
81
+ element: terminalContainer,
82
+ titleElement: title,
83
+ name: `Terminal ${terminalCounter}`
84
+ });
85
+
86
+ // Create terminal in main process
87
+ await ipcRenderer.invoke('terminal:create', terminalId);
88
+
89
+ // Add checkbox to chat sidebar
90
+ addTerminalCheckbox(terminalId, `Terminal ${terminalCounter}`);
91
+
92
+ // Auto-start Claude Code
93
+ setTimeout(() => {
94
+ ipcRenderer.invoke('terminal:write', terminalId, 'claude\n');
95
+ }, 1000);
96
+
97
+ // Ensure terminal is ready and focused
98
+ setTimeout(() => {
99
+ xterm.focus();
100
+ }, 100);
101
+
102
+ // Handle terminal data input
103
+ xterm.onData(data => {
104
+ ipcRenderer.invoke('terminal:write', terminalId, data);
105
+ });
106
+
107
+ // Handle resize
108
+ xterm.onResize(({ cols, rows }) => {
109
+ ipcRenderer.invoke('terminal:resize', terminalId, cols, rows);
110
+ });
111
+
112
+ // Handle rename functionality
113
+ const startRename = () => {
114
+ title.style.display = 'none';
115
+ renameInput.style.display = 'inline-block';
116
+ renameInput.focus();
117
+ renameInput.select();
118
+ };
119
+
120
+ const finishRename = () => {
121
+ const newName = renameInput.value.trim();
122
+ if (newName && newName !== title.textContent) {
123
+ title.textContent = newName;
124
+ terminals.get(terminalId).name = newName;
125
+ updateTerminalCheckbox(terminalId, newName);
126
+ }
127
+ title.style.display = 'inline';
128
+ renameInput.style.display = 'none';
129
+ };
130
+
131
+ // Double-click to rename
132
+ title.addEventListener('dblclick', (e) => {
133
+ e.stopPropagation();
134
+ startRename();
135
+ });
136
+
137
+ // Finish rename on Enter or blur
138
+ renameInput.addEventListener('keydown', (e) => {
139
+ if (e.key === 'Enter') {
140
+ finishRename();
141
+ } else if (e.key === 'Escape') {
142
+ renameInput.value = title.textContent;
143
+ finishRename();
144
+ }
145
+ });
146
+
147
+ renameInput.addEventListener('blur', finishRename);
148
+
149
+ // Handle terminal click to focus
150
+ terminalElement.addEventListener('click', () => {
151
+ xterm.focus();
152
+ });
153
+
154
+ // Also focus when clicking the container (but not header)
155
+ terminalContainer.addEventListener('click', (e) => {
156
+ // Don't focus if clicking on header elements
157
+ if (!e.target.closest('.terminal-header')) {
158
+ xterm.focus();
159
+ }
160
+ });
161
+
162
+ // Focus on the terminal initially
163
+ xterm.focus();
164
+
165
+ // Handle window resize
166
+ const resizeObserver = new ResizeObserver(() => {
167
+ fitAddon.fit();
168
+ });
169
+ resizeObserver.observe(terminalElement);
170
+ }
171
+
172
+ // Close a terminal
173
+ async function closeTerminal(terminalId) {
174
+ const terminal = terminals.get(terminalId);
175
+ if (terminal) {
176
+ // Close in main process
177
+ await ipcRenderer.invoke('terminal:close', terminalId);
178
+
179
+ // Clean up
180
+ terminal.xterm.dispose();
181
+ terminal.element.remove();
182
+ terminals.delete(terminalId);
183
+
184
+ // Remove checkbox from chat
185
+ removeTerminalCheckbox(terminalId);
186
+ }
187
+ }
188
+
189
+ // Handle IPC messages from main process
190
+ ipcRenderer.on('terminal:data', (event, terminalId, data) => {
191
+ const terminal = terminals.get(terminalId);
192
+ if (terminal) {
193
+ terminal.xterm.write(data);
194
+ }
195
+ });
196
+
197
+ ipcRenderer.on('terminal:exit', (event, terminalId, exitCode) => {
198
+ const terminal = terminals.get(terminalId);
199
+ if (terminal) {
200
+ terminal.xterm.write(`\r\n[Process exited with code ${exitCode}]\r\n`);
201
+ }
202
+ });
203
+
204
+ // Chat functionality
205
+ function addTerminalCheckbox(terminalId, name) {
206
+ const container = document.getElementById('terminal-checkboxes');
207
+
208
+ const label = document.createElement('label');
209
+ label.className = 'terminal-checkbox-label';
210
+ label.setAttribute('data-terminal-id', terminalId);
211
+
212
+ const checkbox = document.createElement('input');
213
+ checkbox.type = 'checkbox';
214
+ checkbox.className = 'terminal-selector-checkbox';
215
+ checkbox.value = terminalId;
216
+ checkbox.checked = true; // Default to checked
217
+
218
+ const span = document.createElement('span');
219
+ span.textContent = name;
220
+
221
+ label.appendChild(checkbox);
222
+ label.appendChild(span);
223
+ container.appendChild(label);
224
+ }
225
+
226
+ function updateTerminalCheckbox(terminalId, newName) {
227
+ const label = document.querySelector(`[data-terminal-id="${terminalId}"]`);
228
+ if (label) {
229
+ const span = label.querySelector('span');
230
+ span.textContent = newName;
231
+ }
232
+ }
233
+
234
+ function removeTerminalCheckbox(terminalId) {
235
+ const label = document.querySelector(`[data-terminal-id="${terminalId}"]`);
236
+ if (label) {
237
+ label.remove();
238
+ }
239
+ }
240
+
241
+ function getSelectedTerminals() {
242
+ const checkboxes = document.querySelectorAll('.terminal-selector-checkbox:checked');
243
+ return Array.from(checkboxes).map(cb => cb.value);
244
+ }
245
+
246
+ function sendMessage() {
247
+ const input = document.getElementById('chat-input');
248
+ const message = input.value.trim();
249
+ if (!message) return;
250
+
251
+ // Check if default prompt should be prepended
252
+ let fullMessage = message;
253
+ const useDefaultPrompt = document.getElementById('use-default-prompt').checked;
254
+ const defaultPrompt = document.getElementById('default-prompt-text').value.trim();
255
+
256
+ if (useDefaultPrompt && defaultPrompt) {
257
+ fullMessage = defaultPrompt + ' ' + message;
258
+ }
259
+
260
+ // Get selected terminals
261
+ const selectedTerminals = getSelectedTerminals();
262
+ if (selectedTerminals.length === 0) {
263
+ alert('Please select at least one terminal');
264
+ return;
265
+ }
266
+
267
+ // Add message to chat display
268
+ addChatMessage('You', message);
269
+
270
+ // Send to selected terminals
271
+ selectedTerminals.forEach(terminalId => {
272
+ // Send both the plan mode instruction and message
273
+ const messageToSend = `Please enter plan mode (press Shift+Tab twice)\n\n${fullMessage}`;
274
+ ipcRenderer.invoke('terminal:write', terminalId, messageToSend + '\r\n');
275
+ });
276
+
277
+ // Clear input
278
+ input.value = '';
279
+ }
280
+
281
+ function addChatMessage(sender, message) {
282
+ const messagesContainer = document.getElementById('chat-messages');
283
+ const messageDiv = document.createElement('div');
284
+ messageDiv.className = 'chat-message';
285
+
286
+ const senderSpan = document.createElement('span');
287
+ senderSpan.className = 'message-sender';
288
+ senderSpan.textContent = sender + ':';
289
+
290
+ const messageSpan = document.createElement('span');
291
+ messageSpan.className = 'message-text';
292
+ messageSpan.textContent = message;
293
+
294
+ messageDiv.appendChild(senderSpan);
295
+ messageDiv.appendChild(messageSpan);
296
+ messagesContainer.appendChild(messageDiv);
297
+
298
+ // Scroll to bottom
299
+ messagesContainer.scrollTop = messagesContainer.scrollHeight;
300
+ }
301
+
302
+ // Initialize collapsible default prompt section
303
+ function initCollapsible() {
304
+ const toggle = document.querySelector('.collapse-toggle');
305
+ const content = document.querySelector('.collapsible-content');
306
+
307
+ toggle.addEventListener('click', () => {
308
+ const isExpanded = content.style.display !== 'none';
309
+ content.style.display = isExpanded ? 'none' : 'block';
310
+ toggle.textContent = isExpanded ? '▶' : '▼';
311
+ });
312
+ }
313
+
314
+ // Initialize app
315
+ document.addEventListener('DOMContentLoaded', () => {
316
+ // New terminal button
317
+ document.getElementById('new-terminal-btn').addEventListener('click', createTerminal);
318
+
319
+ // Send message button
320
+ document.getElementById('send-message-btn').addEventListener('click', sendMessage);
321
+
322
+ // Chat input - Enter key (Shift+Enter for new line)
323
+ document.getElementById('chat-input').addEventListener('keydown', (e) => {
324
+ if (e.key === 'Enter' && !e.shiftKey) {
325
+ e.preventDefault();
326
+ sendMessage();
327
+ }
328
+ });
329
+
330
+ // Initialize collapsible
331
+ initCollapsible();
332
+
333
+ // Create first terminal automatically
334
+ createTerminal();
335
+
336
+ // Resize terminals on window resize
337
+ window.addEventListener('resize', () => {
338
+ terminals.forEach(terminal => {
339
+ if (terminal.fitAddon) {
340
+ terminal.fitAddon.fit();
341
+ }
342
+ });
343
+ });
344
+ });
package/style.css ADDED
@@ -0,0 +1,387 @@
1
+ * {
2
+ box-sizing: border-box;
3
+ margin: 0;
4
+ padding: 0;
5
+ }
6
+
7
+ body {
8
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
9
+ background-color: #2b2b2b;
10
+ color: #ffffff;
11
+ height: 100vh;
12
+ overflow: hidden;
13
+ }
14
+
15
+ #app {
16
+ display: flex;
17
+ flex-direction: column;
18
+ height: 100vh;
19
+ }
20
+
21
+ #toolbar {
22
+ background-color: #1e1e1e;
23
+ padding: 10px;
24
+ border-bottom: 1px solid #444;
25
+ }
26
+
27
+ #new-terminal-btn {
28
+ background-color: #0e639c;
29
+ color: white;
30
+ border: none;
31
+ padding: 8px 16px;
32
+ border-radius: 4px;
33
+ cursor: pointer;
34
+ font-size: 14px;
35
+ }
36
+
37
+ #new-terminal-btn:hover {
38
+ background-color: #1177bb;
39
+ }
40
+
41
+ #main-layout {
42
+ flex: 1;
43
+ display: flex;
44
+ overflow: hidden;
45
+ }
46
+
47
+ /* Terminals Area - Left Side */
48
+ #terminals-area {
49
+ flex: 1;
50
+ background-color: #252526;
51
+ display: flex;
52
+ overflow: hidden;
53
+ }
54
+
55
+ #terminals-container {
56
+ flex: 1;
57
+ display: grid;
58
+ grid-template-columns: repeat(2, 1fr);
59
+ grid-auto-rows: 1fr;
60
+ padding: 10px;
61
+ gap: 10px;
62
+ overflow-y: auto;
63
+ }
64
+
65
+ .terminal-container {
66
+ background-color: #1e1e1e;
67
+ border: 1px solid #444;
68
+ border-radius: 4px;
69
+ display: flex;
70
+ flex-direction: column;
71
+ min-height: 300px;
72
+ overflow: hidden;
73
+ }
74
+
75
+ .terminal-header {
76
+ display: flex;
77
+ justify-content: space-between;
78
+ align-items: center;
79
+ padding: 8px 12px;
80
+ background-color: #252526;
81
+ border-bottom: 1px solid #444;
82
+ border-radius: 4px 4px 0 0;
83
+ }
84
+
85
+ .terminal-header span {
86
+ font-size: 14px;
87
+ font-weight: 500;
88
+ }
89
+
90
+ .terminal-title {
91
+ user-select: none;
92
+ padding: 2px 4px;
93
+ border-radius: 3px;
94
+ position: relative;
95
+ }
96
+
97
+ .terminal-title:hover {
98
+ background-color: rgba(255, 255, 255, 0.1);
99
+ }
100
+
101
+ .terminal-title:hover::after {
102
+ content: ' ✏️';
103
+ font-size: 12px;
104
+ opacity: 0.6;
105
+ }
106
+
107
+ .rename-input {
108
+ background-color: #3c3c3c;
109
+ border: 1px solid #0e639c;
110
+ color: white;
111
+ padding: 2px 6px;
112
+ font-size: 14px;
113
+ font-weight: 500;
114
+ border-radius: 3px;
115
+ outline: none;
116
+ width: 150px;
117
+ }
118
+
119
+ .close-btn {
120
+ background: none;
121
+ border: none;
122
+ color: #cccccc;
123
+ font-size: 20px;
124
+ cursor: pointer;
125
+ padding: 0 8px;
126
+ line-height: 1;
127
+ }
128
+
129
+ .close-btn:hover {
130
+ color: #ff6b6b;
131
+ }
132
+
133
+ .terminal {
134
+ flex: 1;
135
+ padding: 8px;
136
+ overflow: hidden;
137
+ cursor: text;
138
+ }
139
+
140
+ /* Chat Sidebar - Right Side */
141
+ #chat-sidebar {
142
+ width: 400px;
143
+ background-color: #1e1e1e;
144
+ border-left: 1px solid #444;
145
+ display: flex;
146
+ flex-direction: column;
147
+ }
148
+
149
+ .chat-header {
150
+ padding: 15px;
151
+ background-color: #252526;
152
+ border-bottom: 1px solid #444;
153
+ }
154
+
155
+ .chat-header h2 {
156
+ margin: 0;
157
+ font-size: 18px;
158
+ color: #cccccc;
159
+ }
160
+
161
+ #chat-messages {
162
+ flex: 1;
163
+ padding: 15px;
164
+ overflow-y: auto;
165
+ display: flex;
166
+ flex-direction: column;
167
+ gap: 10px;
168
+ }
169
+
170
+ .chat-message {
171
+ display: flex;
172
+ flex-direction: column;
173
+ gap: 5px;
174
+ background-color: #252526;
175
+ padding: 10px;
176
+ border-radius: 6px;
177
+ }
178
+
179
+ .message-sender {
180
+ font-weight: bold;
181
+ color: #4da6ff;
182
+ font-size: 14px;
183
+ }
184
+
185
+ .message-text {
186
+ color: #cccccc;
187
+ font-size: 14px;
188
+ line-height: 1.5;
189
+ white-space: pre-wrap;
190
+ }
191
+
192
+ #chat-controls {
193
+ background-color: #252526;
194
+ border-top: 1px solid #444;
195
+ padding: 15px;
196
+ }
197
+
198
+ /* Terminal Selection */
199
+ #terminal-selector {
200
+ margin-bottom: 15px;
201
+ }
202
+
203
+ #terminal-selector label {
204
+ display: block;
205
+ font-size: 14px;
206
+ color: #cccccc;
207
+ margin-bottom: 8px;
208
+ }
209
+
210
+ #terminal-checkboxes {
211
+ display: flex;
212
+ flex-direction: column;
213
+ gap: 8px;
214
+ }
215
+
216
+ .terminal-checkbox-label {
217
+ display: flex;
218
+ align-items: center;
219
+ gap: 8px;
220
+ font-size: 14px;
221
+ color: #cccccc;
222
+ cursor: pointer;
223
+ }
224
+
225
+ .terminal-checkbox-label:hover {
226
+ color: #ffffff;
227
+ }
228
+
229
+ .terminal-selector-checkbox {
230
+ width: 16px;
231
+ height: 16px;
232
+ cursor: pointer;
233
+ }
234
+
235
+ /* Collapsible Default Prompt */
236
+ #default-prompt-section {
237
+ margin-bottom: 15px;
238
+ }
239
+
240
+ .collapsible-header {
241
+ display: flex;
242
+ align-items: center;
243
+ gap: 8px;
244
+ cursor: pointer;
245
+ user-select: none;
246
+ padding: 8px;
247
+ background-color: #3c3c3c;
248
+ border-radius: 4px;
249
+ margin-bottom: 8px;
250
+ }
251
+
252
+ .collapsible-header:hover {
253
+ background-color: #444;
254
+ }
255
+
256
+ .collapse-toggle {
257
+ background: none;
258
+ border: none;
259
+ color: #cccccc;
260
+ font-size: 12px;
261
+ cursor: pointer;
262
+ padding: 0;
263
+ }
264
+
265
+ .collapsible-content {
266
+ padding: 0 8px;
267
+ }
268
+
269
+ #default-prompt-text {
270
+ width: 100%;
271
+ min-height: 80px;
272
+ background-color: #3c3c3c;
273
+ border: 1px solid #555;
274
+ color: white;
275
+ padding: 8px;
276
+ border-radius: 4px;
277
+ font-family: 'Consolas', 'Courier New', monospace;
278
+ font-size: 13px;
279
+ resize: vertical;
280
+ margin-bottom: 8px;
281
+ }
282
+
283
+ #default-prompt-text:focus {
284
+ outline: none;
285
+ border-color: #0e639c;
286
+ }
287
+
288
+ .collapsible-content label {
289
+ display: flex;
290
+ align-items: center;
291
+ gap: 8px;
292
+ font-size: 13px;
293
+ color: #cccccc;
294
+ }
295
+
296
+ /* Message Input */
297
+ .message-input-section {
298
+ display: flex;
299
+ flex-direction: column;
300
+ gap: 10px;
301
+ }
302
+
303
+ #chat-input {
304
+ width: 100%;
305
+ min-height: 80px;
306
+ background-color: #3c3c3c;
307
+ border: 1px solid #555;
308
+ color: white;
309
+ padding: 8px;
310
+ border-radius: 4px;
311
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
312
+ font-size: 14px;
313
+ resize: vertical;
314
+ }
315
+
316
+ #chat-input:focus {
317
+ outline: none;
318
+ border-color: #0e639c;
319
+ }
320
+
321
+ #send-message-btn {
322
+ background-color: #0e639c;
323
+ color: white;
324
+ border: none;
325
+ padding: 10px 20px;
326
+ border-radius: 4px;
327
+ cursor: pointer;
328
+ font-size: 14px;
329
+ align-self: flex-end;
330
+ }
331
+
332
+ #send-message-btn:hover {
333
+ background-color: #1177bb;
334
+ }
335
+
336
+ /* Responsive adjustments */
337
+ @media (max-width: 1200px) {
338
+ #chat-sidebar {
339
+ width: 350px;
340
+ }
341
+ }
342
+
343
+ @media (max-width: 1000px) {
344
+ #terminals-container {
345
+ grid-template-columns: 1fr;
346
+ }
347
+ }
348
+
349
+ @media (max-width: 768px) {
350
+ #main-layout {
351
+ flex-direction: column;
352
+ }
353
+
354
+ #terminals-area {
355
+ flex: 1;
356
+ }
357
+
358
+ #chat-sidebar {
359
+ width: 100%;
360
+ height: 300px;
361
+ border-left: none;
362
+ border-top: 1px solid #444;
363
+ }
364
+
365
+ #terminals-container {
366
+ grid-template-columns: 1fr;
367
+ }
368
+ }
369
+
370
+ /* Scrollbar styling */
371
+ ::-webkit-scrollbar {
372
+ width: 10px;
373
+ height: 10px;
374
+ }
375
+
376
+ ::-webkit-scrollbar-track {
377
+ background: #1e1e1e;
378
+ }
379
+
380
+ ::-webkit-scrollbar-thumb {
381
+ background: #444;
382
+ border-radius: 5px;
383
+ }
384
+
385
+ ::-webkit-scrollbar-thumb:hover {
386
+ background: #555;
387
+ }
package/test-app.js ADDED
@@ -0,0 +1,16 @@
1
+ // Simple test script to verify the app works
2
+ const { spawn } = require('child_process');
3
+
4
+ console.log('Starting Electron app test...');
5
+
6
+ const electron = spawn('npm', ['start'], {
7
+ shell: true,
8
+ stdio: 'inherit'
9
+ });
10
+
11
+ // Give it 5 seconds to start and then kill it
12
+ setTimeout(() => {
13
+ console.log('\nTest completed - app started successfully!');
14
+ electron.kill();
15
+ process.exit(0);
16
+ }, 5000);