@adiontaegerron/claude-multi-terminal 2.0.0 → 2.1.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.
@@ -1,23 +1,16 @@
1
1
  {
2
2
  "permissions": {
3
3
  "allow": [
4
- "Bash(find:*)",
5
- "Bash(npm init:*)",
6
- "Bash(npm install:*)",
7
- "Bash(npm start)",
8
- "Bash(npx:*)",
4
+ "Bash(create_terminal \"Terminal 3\")",
5
+ "Bash(terminal_help)",
6
+ "Bash(create_terminal \"Terminal 4\")",
9
7
  "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:*)",
8
+ "Bash(create_terminal \"Terminal 2\")",
17
9
  "Bash(chmod:*)",
18
- "Bash(npm:*)",
19
- "Bash(claude --print \"Please enter plan mode (press Shift+Tab twice)\")",
20
- "WebFetch(domain:docs.anthropic.com)"
10
+ "Bash(*)",
11
+ "Bash(/tmp/create_terminals.sh:*)",
12
+ "Bash(ls:*)",
13
+ "Write(*)"
21
14
  ],
22
15
  "deny": []
23
16
  }
package/CLAUDE.md CHANGED
@@ -1,63 +1,96 @@
1
- # CLAUDE.md
1
+ # Terminal Agent Instructions
2
2
 
3
- This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
3
+ You are a terminal agent running in a multi-terminal coordination system. Your terminal name will be provided when you start.
4
4
 
5
- ## Project Overview
5
+ ## IMPORTANT: Follow Instructions Exactly
6
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.
7
+ When given a command:
8
+ - Execute ONLY what is explicitly requested
9
+ - Do NOT add extra actions or create additional items
10
+ - If asked to create 2 terminals, create exactly 2 (not 3, 4, or 6)
11
+ - Wait for further instructions after completing each task
8
12
 
9
- ## Development Commands
13
+ ## Communication Commands
10
14
 
11
- Since this is a new project, standard Electron/Node.js commands will be used:
15
+ You communicate with other terminals using file-based IPC. Use the Write tool to create command files in `.ipc-messages/`.
12
16
 
13
- ```bash
14
- # Install dependencies
15
- npm install
17
+ ### Command Format
16
18
 
17
- # Start the application in development mode
18
- npm start
19
+ Use the Write tool to create files with unique names. Include your name in the filename to ensure uniqueness:
20
+ `.ipc-messages/cmd_YourName_timestamp.txt`
19
21
 
20
- # Build the application (once configured)
21
- npm run build
22
+ Always use the exact terminal name you were given at startup.
22
23
 
23
- # Run tests (once configured)
24
- npm test
24
+ ### Send Message to Another Terminal
25
+ Use Write to create a file `.ipc-messages/msg_YourName_[timestamp].txt` with content:
26
+ ```
27
+ YourName:IPC:SEND:TargetTerminal:Your message
25
28
  ```
26
29
 
27
- ## Architecture
30
+ ### Broadcast to All Terminals
31
+ Use Write to create a file `.ipc-messages/bcast_YourName_[timestamp].txt` with content:
32
+ ```
33
+ YourName:IPC:BROADCAST:Your message
34
+ ```
28
35
 
29
- The application follows a standard Electron architecture:
36
+ ### List All Terminals
37
+ Use Write to create a file `.ipc-messages/list_YourName_[timestamp].txt` with content:
38
+ ```
39
+ YourName:IPC:LIST
40
+ ```
30
41
 
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
42
+ ### Create New Terminal
43
+ Use Write to create a file `.ipc-messages/create_YourName_[timestamp].txt` with content:
44
+ ```
45
+ YourName:IPC:CREATE:NewTerminalName
46
+ ```
47
+ The name is optional - if not provided, a default name will be assigned.
34
48
 
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
49
+ ### Close Terminal
50
+ Use Write to create a file `.ipc-messages/close_YourName_[timestamp].txt` with content:
51
+ ```
52
+ YourName:IPC:CLOSE:TargetTerminal
53
+ ```
54
+ Note: You cannot close your own terminal.
55
+
56
+ ### Rename Terminal
57
+ Use Write to create a file `.ipc-messages/rename_YourName_[timestamp].txt` with content:
58
+ ```
59
+ YourName:IPC:RENAME:TargetTerminal:NewName
60
+ ```
61
+
62
+ ### Get Terminal Status
63
+ Use Write to create a file `.ipc-messages/status_YourName_[timestamp].txt` with content:
64
+ ```
65
+ YourName:IPC:STATUS:TargetTerminal
66
+ ```
39
67
 
40
- ## Project Requirements
68
+ **Important**: Replace [timestamp] with a unique number or use the current timestamp when creating files.
41
69
 
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)
70
+ ## Important Rules
51
71
 
52
- Optional features:
53
- - Tabs or sidebar for terminal switching
54
- - Individual terminal close functionality
72
+ 1. Terminal names are case-sensitive
73
+ 2. You cannot send messages to yourself
74
+ 3. You cannot close your own terminal
75
+ 4. Maximum of 6 terminals can exist
76
+ 5. Messages are delivered instantly
77
+ 6. When you receive a message, it will appear as:
78
+ - `💬 Message from Terminal X: [message]`
79
+ - `📢 Broadcast from Terminal X: [message]`
55
80
 
56
- ## Technical Stack
81
+ ## Your Role
57
82
 
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)
83
+ As a terminal agent, you should:
84
+ - Wait for messages and commands from other terminals
85
+ - When you receive a message ending with "[End of message - no response needed]", do NOT respond automatically
86
+ - Only respond to messages when they contain a specific request or question
87
+ - Execute tasks as directed
88
+ - Communicate status updates when appropriate
89
+ - Avoid taking actions unless specifically requested
62
90
 
91
+ ## Important: Message Handling
63
92
 
93
+ When you receive messages from other terminals:
94
+ - Messages marked with "[End of message - no response needed]" are notifications only
95
+ - Do not echo or respond to these messages unless they contain a specific request
96
+ - Wait for explicit instructions before taking action
package/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # Claude Multi-Terminal
2
2
 
3
+ **Note for Terminal Agents**: If you are a Claude instance running inside this application, you don't need to read this file. Please refer to CLAUDE.md for your instructions.
4
+
3
5
  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
6
 
5
7
  ![Claude Multi-Terminal Screenshot](screenshot.png)
@@ -21,6 +23,8 @@ A multi-terminal editor for coordinating multiple Claude Code instances. Run mul
21
23
  - **Default Prompt**: Set a default prompt to prepend to all messages
22
24
  - **Plan Mode Support**: Automatically enters plan mode with Shift+Tab
23
25
  - **Auto-Start Claude**: Each terminal automatically starts Claude Code
26
+ - **Inter-Claude Communication**: Claude instances can chat and coordinate using echo commands
27
+ - **Terminal Management**: Claude can create, close, and rename terminals autonomously
24
28
  - **Rename Terminals**: Double-click terminal titles to rename them
25
29
  - **Responsive Layout**: Adapts to different screen sizes
26
30
 
@@ -94,13 +98,29 @@ Send commands directly to terminals using @ syntax:
94
98
 
95
99
  **Autocomplete**: Type `@` anywhere to get terminal suggestions!
96
100
 
101
+ ### Claude-to-Claude Communication
102
+
103
+ Claude instances communicate by creating files in the `.ipc-messages/` directory. Claude will handle the file creation directly when asked to:
104
+
105
+ - **Send message**: Claude creates a file to send `TerminalName:IPC:SEND:Target:Message`
106
+ - **Broadcast**: Claude creates a file to broadcast `TerminalName:IPC:BROADCAST:Message`
107
+ - **Create terminal**: Claude creates a file with `TerminalName:IPC:CREATE:NewName`
108
+ - **Close terminal**: Claude creates a file with `TerminalName:IPC:CLOSE:Target`
109
+ - **Rename terminal**: Claude creates a file with `TerminalName:IPC:RENAME:Target:NewName`
110
+ - **List terminals**: Claude creates a file with `TerminalName:IPC:LIST`
111
+ - **Get status**: Claude creates a file with `TerminalName:IPC:STATUS:Target`
112
+
113
+ Claude knows how to create these files when you ask it to communicate with other terminals. This enables Claude instances to work together as a coordinated team.
114
+
97
115
  ### Tips
98
116
 
99
117
  - Each terminal automatically starts Claude Code when created
118
+ - Claude receives its identity: "You are a terminal agent. Your name is [Terminal Name]."
100
119
  - Plan mode is activated automatically using Shift+Tab
101
120
  - Double-click terminal titles to rename them for better organization
102
121
  - The application uses your current directory as the working directory for all terminals
103
122
  - Use multi-@ commands to coordinate multiple Claude instances efficiently
123
+ - Claude instances communicate instantly using IPC echo commands for seamless teamwork
104
124
 
105
125
  ## Development
106
126
 
@@ -0,0 +1,303 @@
1
+ // IPC Handlers for Direct Terminal Communication
2
+ // Handles IPC calls from injected JavaScript functions
3
+
4
+ const { ipcMain } = require('electron');
5
+
6
+ class DirectIPCHandler {
7
+ constructor() {
8
+ this.terminals = null; // Will be set by main process
9
+ this.mainWindow = null; // Will be set by main process
10
+ this.setupHandlers();
11
+ }
12
+
13
+ setTerminals(terminals) {
14
+ this.terminals = terminals;
15
+ }
16
+
17
+ setMainWindow(mainWindow) {
18
+ this.mainWindow = mainWindow;
19
+ }
20
+
21
+ setupHandlers() {
22
+ // Send message between terminals
23
+ ipcMain.handle('terminal:send-message', async (event, data) => {
24
+ try {
25
+ const { from, to, message, timestamp } = data;
26
+
27
+ const targetTerminal = this.findTerminal(to);
28
+ if (!targetTerminal) {
29
+ return { success: false, error: `Terminal '${to}' not found` };
30
+ }
31
+
32
+ const chatMessage = `💬 Message from ${from}:\n${message}`;
33
+ this.mainWindow.webContents.send('terminal:system-response', targetTerminal.id, {
34
+ message: chatMessage,
35
+ type: 'chat',
36
+ timestamp: timestamp || Date.now()
37
+ });
38
+
39
+ // Send return to submit message
40
+ setTimeout(() => {
41
+ targetTerminal.pty.write('\r');
42
+ }, 200);
43
+
44
+ return { success: true, target: to, from };
45
+ } catch (error) {
46
+ return { success: false, error: error.message };
47
+ }
48
+ });
49
+
50
+ // Create new terminal
51
+ ipcMain.handle('terminal:create-new', async (event, data) => {
52
+ try {
53
+ const { name, requesterId } = data;
54
+
55
+ // Check terminal limit
56
+ if (this.terminals.size >= 6) {
57
+ return { success: false, error: 'Maximum of 6 terminals allowed' };
58
+ }
59
+
60
+ // Rate limiting check
61
+ const requesterTerminal = this.terminals.get(requesterId);
62
+ if (requesterTerminal) {
63
+ const now = Date.now();
64
+ const timeSinceLastCreate = now - (requesterTerminal.lastCreateRequest || 0);
65
+ if (timeSinceLastCreate < 2000) {
66
+ return {
67
+ success: false,
68
+ error: `Please wait ${Math.ceil((2000 - timeSinceLastCreate) / 1000)} seconds before creating another terminal`
69
+ };
70
+ }
71
+ requesterTerminal.lastCreateRequest = now;
72
+ }
73
+
74
+ // Trigger terminal creation
75
+ this.mainWindow.webContents.send('terminal:create-request', name);
76
+
77
+ return {
78
+ success: true,
79
+ terminalId: `terminal-${Date.now()}`, // Temporary ID
80
+ name: name
81
+ };
82
+ } catch (error) {
83
+ return { success: false, error: error.message };
84
+ }
85
+ });
86
+
87
+ // Close terminal
88
+ ipcMain.handle('terminal:close-target', async (event, data) => {
89
+ try {
90
+ const { target, requesterId } = data;
91
+
92
+ const targetTerminal = this.findTerminal(target);
93
+ if (!targetTerminal) {
94
+ return { success: false, error: `Terminal '${target}' not found` };
95
+ }
96
+
97
+ if (targetTerminal.id === requesterId) {
98
+ return { success: false, error: 'Cannot close your own terminal' };
99
+ }
100
+
101
+ targetTerminal.pty.kill();
102
+ this.terminals.delete(targetTerminal.id);
103
+
104
+ this.mainWindow.webContents.send('terminal:closed', targetTerminal.id);
105
+
106
+ return { success: true, target, closed: targetTerminal.name };
107
+ } catch (error) {
108
+ return { success: false, error: error.message };
109
+ }
110
+ });
111
+
112
+ // Rename terminal
113
+ ipcMain.handle('terminal:rename-target', async (event, data) => {
114
+ try {
115
+ const { target, newName, requesterId } = data;
116
+
117
+ const targetTerminal = this.findTerminal(target);
118
+ if (!targetTerminal) {
119
+ return { success: false, error: `Terminal '${target}' not found` };
120
+ }
121
+
122
+ const oldName = targetTerminal.name;
123
+ targetTerminal.name = newName;
124
+
125
+ this.mainWindow.webContents.send('terminal:renamed', targetTerminal.id, newName);
126
+
127
+ return { success: true, target, oldName, newName };
128
+ } catch (error) {
129
+ return { success: false, error: error.message };
130
+ }
131
+ });
132
+
133
+ // List all terminals
134
+ ipcMain.handle('terminal:list-all', async (event) => {
135
+ try {
136
+ const terminalList = Array.from(this.terminals.entries()).map(([id, terminal]) => ({
137
+ id,
138
+ name: terminal.name,
139
+ status: terminal.status || 'active',
140
+ hasClaudeStarted: terminal.hasClaudeStarted || false
141
+ }));
142
+
143
+ return { success: true, terminals: terminalList };
144
+ } catch (error) {
145
+ return { success: false, error: error.message };
146
+ }
147
+ });
148
+
149
+ // Get terminal status
150
+ ipcMain.handle('terminal:get-status', async (event, data) => {
151
+ try {
152
+ const { target } = data;
153
+
154
+ const targetTerminal = this.findTerminal(target);
155
+ if (!targetTerminal) {
156
+ return { success: false, error: `Terminal '${target}' not found` };
157
+ }
158
+
159
+ const status = {
160
+ id: targetTerminal.id,
161
+ name: targetTerminal.name,
162
+ status: targetTerminal.status || 'active',
163
+ hasClaudeStarted: targetTerminal.hasClaudeStarted || false
164
+ };
165
+
166
+ return { success: true, target, status };
167
+ } catch (error) {
168
+ return { success: false, error: error.message };
169
+ }
170
+ });
171
+
172
+ // Broadcast message to all terminals
173
+ ipcMain.handle('terminal:broadcast', async (event, data) => {
174
+ try {
175
+ const { message, from, excludeId } = data;
176
+
177
+ let count = 0;
178
+ for (const [id, terminal] of this.terminals.entries()) {
179
+ if (excludeId && id === excludeId) continue; // Don't send to sender
180
+
181
+ const chatMessage = `📢 Broadcast from ${from}:\n${message}`;
182
+ this.mainWindow.webContents.send('terminal:system-response', id, {
183
+ message: chatMessage,
184
+ type: 'chat',
185
+ timestamp: Date.now()
186
+ });
187
+
188
+ count++;
189
+ }
190
+
191
+ return { success: true, count, message };
192
+ } catch (error) {
193
+ return { success: false, error: error.message };
194
+ }
195
+ });
196
+
197
+ // Advanced workflow handlers
198
+ ipcMain.handle('terminal:workflow-setup', async (event, data) => {
199
+ try {
200
+ const { type, requesterId } = data;
201
+
202
+ const workflows = {
203
+ 'development': [
204
+ 'Backend Server',
205
+ 'Frontend Dev',
206
+ 'Database',
207
+ 'Test Runner'
208
+ ],
209
+ 'review': [
210
+ 'Code Reviewer',
211
+ 'Test Runner',
212
+ 'Build Monitor'
213
+ ],
214
+ 'debug': [
215
+ 'Bug Hunter',
216
+ 'Log Monitor',
217
+ 'Test Verifier'
218
+ ]
219
+ };
220
+
221
+ const terminalNames = workflows[type];
222
+ if (!terminalNames) {
223
+ return { success: false, error: `Unknown workflow type: ${type}` };
224
+ }
225
+
226
+ // Check if we can create all terminals
227
+ if (this.terminals.size + terminalNames.length > 6) {
228
+ return {
229
+ success: false,
230
+ error: `Cannot create ${terminalNames.length} terminals. Would exceed limit of 6.`
231
+ };
232
+ }
233
+
234
+ // Create terminals
235
+ const created = [];
236
+ for (const name of terminalNames) {
237
+ this.mainWindow.webContents.send('terminal:create-request', name);
238
+ created.push(name);
239
+ // Small delay between creations
240
+ await new Promise(resolve => setTimeout(resolve, 500));
241
+ }
242
+
243
+ return { success: true, type, created };
244
+ } catch (error) {
245
+ return { success: false, error: error.message };
246
+ }
247
+ });
248
+
249
+ // Health check
250
+ ipcMain.handle('terminal:health', async (event) => {
251
+ try {
252
+ return {
253
+ success: true,
254
+ terminalCount: this.terminals.size,
255
+ uptime: process.uptime(),
256
+ platform: process.platform,
257
+ memory: process.memoryUsage()
258
+ };
259
+ } catch (error) {
260
+ return { success: false, error: error.message };
261
+ }
262
+ });
263
+ }
264
+
265
+ // Helper method to find terminals
266
+ findTerminal(identifier) {
267
+ if (!this.terminals) {
268
+ throw new Error('Terminals not initialized');
269
+ }
270
+
271
+ // Try by ID first
272
+ if (this.terminals.has(identifier)) {
273
+ return this.terminals.get(identifier);
274
+ }
275
+
276
+ // Try by name (exact match)
277
+ for (const [id, terminal] of this.terminals.entries()) {
278
+ if (terminal.name === identifier) {
279
+ return terminal;
280
+ }
281
+ }
282
+
283
+ // Try by name (partial match)
284
+ for (const [id, terminal] of this.terminals.entries()) {
285
+ if (terminal.name.toLowerCase().includes(identifier.toLowerCase())) {
286
+ return terminal;
287
+ }
288
+ }
289
+
290
+ // Try by position
291
+ const position = parseInt(identifier);
292
+ if (!isNaN(position) && position > 0) {
293
+ const terminalArray = Array.from(this.terminals.values());
294
+ if (position <= terminalArray.length) {
295
+ return terminalArray[position - 1];
296
+ }
297
+ }
298
+
299
+ return null;
300
+ }
301
+ }
302
+
303
+ module.exports = DirectIPCHandler;