@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.
- package/.claude/settings.local.json +22 -0
- package/CLAUDE.md +63 -0
- package/README.md +115 -0
- package/bin/claude-multi.js +29 -0
- package/index.html +64 -0
- package/main.js +116 -0
- package/package.json +44 -0
- package/renderer.js +344 -0
- package/style.css +387 -0
- package/test-app.js +16 -0
@@ -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
|
+

|
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);
|