web-terminal-agent 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +114 -0
  2. package/index.js +314 -0
  3. package/package.json +45 -0
package/README.md ADDED
@@ -0,0 +1,114 @@
1
+ # web-terminal-agent
2
+
3
+ A lightweight local agent that enables browser-based terminal access to your computer. Use CLI tools like Claude Code through a friendly web interface - no terminal experience required!
4
+
5
+ ## Quick Start
6
+
7
+ ```bash
8
+ npx web-terminal-agent
9
+ ```
10
+
11
+ That's it! The agent will start and you can connect from the web interface.
12
+
13
+ ## What is this?
14
+
15
+ This agent runs on your computer and creates a secure bridge between your browser and your local terminal. It allows web applications to:
16
+
17
+ - Execute shell commands on your machine
18
+ - Run CLI tools like Claude Code, Clawdbot, etc.
19
+ - Navigate your file system
20
+ - Install development tools
21
+
22
+ ## Security
23
+
24
+ **Your security is our priority:**
25
+
26
+ - ✅ **Localhost only** - The agent only listens on `127.0.0.1` (your computer). It cannot be accessed from the internet or other devices.
27
+ - ✅ **You're in control** - You start and stop the agent whenever you want. Just press `Ctrl+C` to stop.
28
+ - ✅ **Transparent** - All commands are logged in your terminal so you can see exactly what's happening.
29
+ - ✅ **Open source** - The code is fully visible and auditable.
30
+
31
+ ## Installation Options
32
+
33
+ ### Option 1: Run without installing (Recommended)
34
+
35
+ ```bash
36
+ npx web-terminal-agent
37
+ ```
38
+
39
+ ### Option 2: Install globally
40
+
41
+ ```bash
42
+ npm install -g web-terminal-agent
43
+ web-terminal-agent
44
+ ```
45
+
46
+ ### Option 3: Clone and run
47
+
48
+ ```bash
49
+ git clone https://github.com/user/web-terminal-agent.git
50
+ cd web-terminal-agent
51
+ npm install
52
+ node index.js
53
+ ```
54
+
55
+ ## Configuration
56
+
57
+ By default, the agent runs on port `3456`. You can change this:
58
+
59
+ ```bash
60
+ PORT=8080 npx web-terminal-agent
61
+ ```
62
+
63
+ ## Requirements
64
+
65
+ - Node.js 18 or higher
66
+ - Works on Windows, macOS, and Linux
67
+
68
+ ## How it works
69
+
70
+ 1. You run the agent on your computer
71
+ 2. The agent starts a local server on `http://127.0.0.1:3456`
72
+ 3. The web interface connects to this local server
73
+ 4. Commands from the web interface are executed on your machine
74
+ 5. Output is streamed back to the web interface in real-time
75
+
76
+ ## API Endpoints
77
+
78
+ If you want to integrate with the agent programmatically:
79
+
80
+ - `GET /health` - Check if agent is running
81
+ - `GET /cwd` - Get current working directory
82
+ - `POST /execute` - Execute a command (body: `{ "command": "ls -la" }`)
83
+ - `GET /tools/:tool` - Check if a tool is installed
84
+ - `POST /tools/:tool/install` - Install a tool
85
+ - `WebSocket /ws` - Real-time command execution
86
+
87
+ ## Troubleshooting
88
+
89
+ ### Port already in use
90
+
91
+ If port 3456 is busy, use a different port:
92
+
93
+ ```bash
94
+ PORT=3457 npx web-terminal-agent
95
+ ```
96
+
97
+ ### Permission denied
98
+
99
+ On macOS/Linux, you might need to make the script executable:
100
+
101
+ ```bash
102
+ chmod +x ./node_modules/.bin/web-terminal-agent
103
+ ```
104
+
105
+ ### Connection refused in browser
106
+
107
+ Make sure:
108
+ 1. The agent is running (you should see the banner in your terminal)
109
+ 2. You're connecting to `http://localhost:3456` (not `127.0.0.1` in some browsers)
110
+ 3. No firewall is blocking localhost connections
111
+
112
+ ## License
113
+
114
+ MIT
package/index.js ADDED
@@ -0,0 +1,314 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Web Terminal Agent
5
+ *
6
+ * A lightweight local server that enables browser-based terminal access.
7
+ * Runs on localhost only for security.
8
+ *
9
+ * Usage: npx web-terminal-agent
10
+ * node index.js
11
+ * web-terminal-agent (if installed globally)
12
+ */
13
+
14
+ const express = require('express');
15
+ const cors = require('cors');
16
+ const { WebSocketServer } = require('ws');
17
+ const { spawn, exec } = require('child_process');
18
+ const http = require('http');
19
+ const os = require('os');
20
+ const path = require('path');
21
+
22
+ // Configuration
23
+ const PORT = process.env.PORT || 3456;
24
+ const HOST = '127.0.0.1'; // Only listen on localhost for security
25
+
26
+ // ANSI color codes for terminal output
27
+ const colors = {
28
+ reset: '\x1b[0m',
29
+ green: '\x1b[32m',
30
+ yellow: '\x1b[33m',
31
+ cyan: '\x1b[36m',
32
+ red: '\x1b[31m',
33
+ dim: '\x1b[2m',
34
+ };
35
+
36
+ // Print banner
37
+ function printBanner() {
38
+ console.log(`
39
+ ${colors.green}╔═══════════════════════════════════════════════════════════╗
40
+ ║ ║
41
+ ║ ${colors.cyan}██╗ ██╗███████╗██████╗ ████████╗███████╗██████╗ ███╗ ███╗${colors.green} ║
42
+ ║ ${colors.cyan}██║ ██║██╔════╝██╔══██╗ ╚══██╔══╝██╔════╝██╔══██╗████╗ ████║${colors.green} ║
43
+ ║ ${colors.cyan}██║ █╗ ██║█████╗ ██████╔╝ ██║ █████╗ ██████╔╝██╔████╔██║${colors.green} ║
44
+ ║ ${colors.cyan}██║███╗██║██╔══╝ ██╔══██╗ ██║ ██╔══╝ ██╔══██╗██║╚██╔╝██║${colors.green} ║
45
+ ║ ${colors.cyan}╚███╔███╔╝███████╗██████╔╝ ██║ ███████╗██║ ██║██║ ╚═╝ ██║${colors.green} ║
46
+ ║ ${colors.cyan} ╚══╝╚══╝ ╚══════╝╚═════╝ ╚═╝ ╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝${colors.green} ║
47
+ ║ ║
48
+ ║ ${colors.yellow}LOCAL AGENT v1.0.0${colors.green} ║
49
+ ║ ║
50
+ ╚═══════════════════════════════════════════════════════════════╝${colors.reset}
51
+ `);
52
+ }
53
+
54
+ // Get current working directory display
55
+ let currentDir = process.cwd();
56
+
57
+ function getShortDir(dir) {
58
+ const home = os.homedir();
59
+ if (dir.startsWith(home)) {
60
+ return '~' + dir.slice(home.length);
61
+ }
62
+ return dir;
63
+ }
64
+
65
+ // Detect platform
66
+ const isWindows = process.platform === 'win32';
67
+ const shell = isWindows ? 'cmd.exe' : (process.env.SHELL || '/bin/bash');
68
+ const shellArgs = isWindows ? ['/c'] : ['-c'];
69
+
70
+ // Check if a command/tool is installed
71
+ async function checkToolInstalled(command) {
72
+ return new Promise((resolve) => {
73
+ const checkCmd = isWindows ? 'where' : 'which';
74
+ exec(`${checkCmd} ${command}`, (error) => {
75
+ resolve(!error);
76
+ });
77
+ });
78
+ }
79
+
80
+ // Get tool version
81
+ async function getToolVersion(command) {
82
+ return new Promise((resolve) => {
83
+ exec(`${command} --version`, (error, stdout) => {
84
+ if (error) {
85
+ resolve(null);
86
+ } else {
87
+ resolve(stdout.trim().split('\n')[0]);
88
+ }
89
+ });
90
+ });
91
+ }
92
+
93
+ // Install a tool
94
+ async function installTool(tool, ws) {
95
+ const installCommands = {
96
+ 'claude-code': 'npm install -g @anthropic-ai/claude-code',
97
+ 'clawdbot': 'npm install -g clawdbot',
98
+ };
99
+
100
+ const cmd = installCommands[tool];
101
+ if (!cmd) {
102
+ return { success: false, error: 'Unknown tool' };
103
+ }
104
+
105
+ return new Promise((resolve) => {
106
+ const proc = spawn(shell, [...shellArgs, cmd], {
107
+ cwd: currentDir,
108
+ env: process.env,
109
+ });
110
+
111
+ proc.stdout.on('data', (data) => {
112
+ if (ws && ws.readyState === 1) {
113
+ ws.send(JSON.stringify({ type: 'output', content: data.toString() }));
114
+ }
115
+ });
116
+
117
+ proc.stderr.on('data', (data) => {
118
+ if (ws && ws.readyState === 1) {
119
+ ws.send(JSON.stringify({ type: 'output', content: data.toString() }));
120
+ }
121
+ });
122
+
123
+ proc.on('close', (code) => {
124
+ resolve({ success: code === 0 });
125
+ });
126
+ });
127
+ }
128
+
129
+ // Execute a command
130
+ function executeCommand(command, ws) {
131
+ return new Promise((resolve) => {
132
+ // Handle cd command specially
133
+ if (command.trim().startsWith('cd ')) {
134
+ const newDir = command.trim().slice(3).trim();
135
+ const targetDir = path.resolve(currentDir, newDir.replace(/^~/, os.homedir()));
136
+
137
+ try {
138
+ process.chdir(targetDir);
139
+ currentDir = process.cwd();
140
+
141
+ if (ws && ws.readyState === 1) {
142
+ ws.send(JSON.stringify({ type: 'cwd', content: getShortDir(currentDir) }));
143
+ }
144
+
145
+ resolve({ success: true, cwd: getShortDir(currentDir) });
146
+ } catch (err) {
147
+ if (ws && ws.readyState === 1) {
148
+ ws.send(JSON.stringify({ type: 'error', content: `cd: ${err.message}` }));
149
+ }
150
+ resolve({ success: false, error: err.message });
151
+ }
152
+ return;
153
+ }
154
+
155
+ const proc = spawn(shell, [...shellArgs, command], {
156
+ cwd: currentDir,
157
+ env: { ...process.env, FORCE_COLOR: '1' },
158
+ });
159
+
160
+ let stdout = '';
161
+ let stderr = '';
162
+
163
+ proc.stdout.on('data', (data) => {
164
+ const text = data.toString();
165
+ stdout += text;
166
+ if (ws && ws.readyState === 1) {
167
+ ws.send(JSON.stringify({ type: 'output', content: text }));
168
+ }
169
+ });
170
+
171
+ proc.stderr.on('data', (data) => {
172
+ const text = data.toString();
173
+ stderr += text;
174
+ if (ws && ws.readyState === 1) {
175
+ ws.send(JSON.stringify({ type: 'error', content: text }));
176
+ }
177
+ });
178
+
179
+ proc.on('close', (code) => {
180
+ resolve({
181
+ success: code === 0,
182
+ stdout,
183
+ stderr,
184
+ exitCode: code,
185
+ cwd: getShortDir(currentDir),
186
+ });
187
+ });
188
+
189
+ proc.on('error', (err) => {
190
+ if (ws && ws.readyState === 1) {
191
+ ws.send(JSON.stringify({ type: 'error', content: err.message }));
192
+ }
193
+ resolve({ success: false, error: err.message });
194
+ });
195
+ });
196
+ }
197
+
198
+ // Create Express app
199
+ const app = express();
200
+ app.use(cors({
201
+ origin: '*', // Allow all origins since we're on localhost
202
+ methods: ['GET', 'POST'],
203
+ }));
204
+ app.use(express.json());
205
+
206
+ // Health check endpoint
207
+ app.get('/health', (req, res) => {
208
+ res.json({
209
+ status: 'ok',
210
+ version: '1.0.0',
211
+ platform: process.platform,
212
+ cwd: getShortDir(currentDir),
213
+ });
214
+ });
215
+
216
+ // Check tool status
217
+ app.get('/tools/:tool', async (req, res) => {
218
+ const { tool } = req.params;
219
+ const installed = await checkToolInstalled(tool);
220
+ const version = installed ? await getToolVersion(tool) : null;
221
+
222
+ res.json({
223
+ tool,
224
+ installed,
225
+ version,
226
+ });
227
+ });
228
+
229
+ // Install tool
230
+ app.post('/tools/:tool/install', async (req, res) => {
231
+ const { tool } = req.params;
232
+ const result = await installTool(tool, null);
233
+ res.json(result);
234
+ });
235
+
236
+ // Execute command (REST fallback)
237
+ app.post('/execute', async (req, res) => {
238
+ const { command } = req.body;
239
+
240
+ if (!command) {
241
+ return res.status(400).json({ error: 'Command is required' });
242
+ }
243
+
244
+ console.log(`${colors.dim}[CMD]${colors.reset} ${command}`);
245
+
246
+ const result = await executeCommand(command, null);
247
+ res.json(result);
248
+ });
249
+
250
+ // Get current directory
251
+ app.get('/cwd', (req, res) => {
252
+ res.json({ cwd: getShortDir(currentDir) });
253
+ });
254
+
255
+ // Create HTTP server
256
+ const server = http.createServer(app);
257
+
258
+ // Create WebSocket server
259
+ const wss = new WebSocketServer({ server, path: '/ws' });
260
+
261
+ wss.on('connection', (ws) => {
262
+ console.log(`${colors.green}✓${colors.reset} Client connected via WebSocket`);
263
+
264
+ // Send initial state
265
+ ws.send(JSON.stringify({ type: 'cwd', content: getShortDir(currentDir) }));
266
+
267
+ ws.on('message', async (message) => {
268
+ try {
269
+ const data = JSON.parse(message.toString());
270
+
271
+ if (data.type === 'command') {
272
+ console.log(`${colors.dim}[CMD]${colors.reset} ${data.content}`);
273
+ await executeCommand(data.content, ws);
274
+ } else if (data.type === 'install') {
275
+ console.log(`${colors.dim}[INSTALL]${colors.reset} ${data.tool}`);
276
+ await installTool(data.tool, ws);
277
+ }
278
+ } catch (err) {
279
+ console.error(`${colors.red}Error:${colors.reset}`, err.message);
280
+ ws.send(JSON.stringify({ type: 'error', content: err.message }));
281
+ }
282
+ });
283
+
284
+ ws.on('close', () => {
285
+ console.log(`${colors.yellow}○${colors.reset} Client disconnected`);
286
+ });
287
+ });
288
+
289
+ // Start server
290
+ server.listen(PORT, HOST, () => {
291
+ printBanner();
292
+
293
+ console.log(`${colors.green}✓${colors.reset} Agent running on ${colors.cyan}http://${HOST}:${PORT}${colors.reset}`);
294
+ console.log(`${colors.green}✓${colors.reset} WebSocket available at ${colors.cyan}ws://${HOST}:${PORT}/ws${colors.reset}`);
295
+ console.log(`${colors.green}✓${colors.reset} Working directory: ${colors.cyan}${getShortDir(currentDir)}${colors.reset}`);
296
+ console.log('');
297
+ console.log(`${colors.dim}Press Ctrl+C to stop the agent${colors.reset}`);
298
+ console.log('');
299
+ });
300
+
301
+ // Handle graceful shutdown
302
+ process.on('SIGINT', () => {
303
+ console.log(`\n${colors.yellow}Shutting down agent...${colors.reset}`);
304
+ server.close(() => {
305
+ console.log(`${colors.green}✓${colors.reset} Agent stopped`);
306
+ process.exit(0);
307
+ });
308
+ });
309
+
310
+ process.on('SIGTERM', () => {
311
+ server.close(() => {
312
+ process.exit(0);
313
+ });
314
+ });
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "web-terminal-agent",
3
+ "version": "1.0.0",
4
+ "description": "Local agent for Web Terminal - enables browser-based terminal access to your computer. Run CLI tools like Claude Code through a friendly web interface.",
5
+ "main": "index.js",
6
+ "bin": {
7
+ "web-terminal-agent": "index.js"
8
+ },
9
+ "scripts": {
10
+ "start": "node index.js"
11
+ },
12
+ "keywords": [
13
+ "terminal",
14
+ "web",
15
+ "agent",
16
+ "cli",
17
+ "shell",
18
+ "claude-code",
19
+ "browser-terminal",
20
+ "remote-terminal",
21
+ "web-terminal"
22
+ ],
23
+ "author": "",
24
+ "license": "MIT",
25
+ "repository": {
26
+ "type": "git",
27
+ "url": ""
28
+ },
29
+ "homepage": "",
30
+ "bugs": {
31
+ "url": ""
32
+ },
33
+ "dependencies": {
34
+ "cors": "^2.8.5",
35
+ "express": "^4.18.2",
36
+ "ws": "^8.14.2"
37
+ },
38
+ "engines": {
39
+ "node": ">=18.0.0"
40
+ },
41
+ "files": [
42
+ "index.js",
43
+ "README.md"
44
+ ]
45
+ }