@mjasano/devtunnel 1.2.0 → 1.5.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 +6 -1
- package/.prettierignore +2 -0
- package/.prettierrc +8 -0
- package/CHANGELOG.md +47 -0
- package/README.md +138 -0
- package/bin/cli.js +40 -11
- package/eslint.config.js +32 -0
- package/package.json +12 -3
- package/public/app.js +849 -0
- package/public/index.html +13 -1501
- package/public/login.html +274 -0
- package/public/styles.css +1212 -0
- package/server.js +276 -7
- package/test/server.test.js +204 -0
|
@@ -12,7 +12,12 @@
|
|
|
12
12
|
"Bash(npm whoami:*)",
|
|
13
13
|
"Bash(npm config set:*)",
|
|
14
14
|
"Bash(npm view:*)",
|
|
15
|
-
"Bash(npm publish:*)"
|
|
15
|
+
"Bash(npm publish:*)",
|
|
16
|
+
"Bash(git add:*)",
|
|
17
|
+
"Bash(git commit:*)",
|
|
18
|
+
"Bash(git push:*)",
|
|
19
|
+
"Bash(gh release create:*)",
|
|
20
|
+
"Bash(PORT=3099 node:*)"
|
|
16
21
|
]
|
|
17
22
|
}
|
|
18
23
|
}
|
package/.prettierignore
ADDED
package/.prettierrc
ADDED
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,53 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.5.0] - 2026-01-02
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- Mobile-responsive UI with touch-friendly design
|
|
12
|
+
- Sidebar close button for mobile devices
|
|
13
|
+
- Full-width sidebar on small screens (480px and below)
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
- Improved touch targets (minimum 44px for buttons/tabs)
|
|
17
|
+
- Dynamic terminal height using flex layout
|
|
18
|
+
- Input font-size set to 16px to prevent iOS auto-zoom
|
|
19
|
+
- Smoother sidebar slide animation with overlay
|
|
20
|
+
|
|
21
|
+
## [1.4.0] - 2026-01-02
|
|
22
|
+
|
|
23
|
+
### Added
|
|
24
|
+
- Passcode authentication system
|
|
25
|
+
- Login page with passcode entry
|
|
26
|
+
- Logout button in header
|
|
27
|
+
- CLI `--passcode` option (auto-generate or custom)
|
|
28
|
+
- Auth API endpoints (`/api/auth/status`, `/api/auth/login`, `/api/auth/logout`)
|
|
29
|
+
|
|
30
|
+
### Security
|
|
31
|
+
- Token-based session authentication (24h expiry)
|
|
32
|
+
- WebSocket connection authentication
|
|
33
|
+
- HTTP-only secure cookies for auth tokens
|
|
34
|
+
|
|
35
|
+
## [1.3.0] - 2026-01-02
|
|
36
|
+
|
|
37
|
+
### Added
|
|
38
|
+
- File delete and rename functionality (right-click context menu)
|
|
39
|
+
- File search filter in file explorer
|
|
40
|
+
- WebSocket security (message size limit, rate limiting)
|
|
41
|
+
- README.md documentation with API reference
|
|
42
|
+
- ESLint and Prettier configuration
|
|
43
|
+
- Test suite using Node.js built-in test runner
|
|
44
|
+
|
|
45
|
+
### Changed
|
|
46
|
+
- Express upgraded from 4.x to 5.x
|
|
47
|
+
- Frontend split into separate files (styles.css, app.js)
|
|
48
|
+
- CLI version now reads from package.json instead of hardcoded value
|
|
49
|
+
|
|
50
|
+
### Security
|
|
51
|
+
- Added 1MB max message size for WebSocket
|
|
52
|
+
- Added rate limiting (100 messages/second per connection)
|
|
53
|
+
- Added request body size limit (5MB) for JSON payloads
|
|
54
|
+
|
|
8
55
|
## [1.2.0] - 2025-01-02
|
|
9
56
|
|
|
10
57
|
### Added
|
package/README.md
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# DevTunnel
|
|
2
|
+
|
|
3
|
+
Web-based terminal with integrated code editor and Cloudflare tunnel management. Access your development environment from anywhere.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Web Terminal**: Full-featured terminal with xterm.js
|
|
8
|
+
- Multiple terminal sessions with tabs
|
|
9
|
+
- Session persistence (24h timeout)
|
|
10
|
+
- tmux session integration
|
|
11
|
+
- **Code Editor**: Monaco Editor (VS Code engine)
|
|
12
|
+
- File explorer with breadcrumb navigation
|
|
13
|
+
- 20+ language syntax highlighting
|
|
14
|
+
- Multi-file tabs with unsaved change indicators
|
|
15
|
+
- Keyboard shortcuts (Ctrl+S to save)
|
|
16
|
+
- **Tunnel Manager**: Expose local ports via Cloudflare
|
|
17
|
+
- Quick tunnel creation
|
|
18
|
+
- Real-time status updates
|
|
19
|
+
- Copy-to-clipboard functionality
|
|
20
|
+
- **System Monitor**: Real-time resource monitoring
|
|
21
|
+
- CPU and memory usage
|
|
22
|
+
- Uptime and load average
|
|
23
|
+
|
|
24
|
+
## Installation
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npm install -g @mjasano/devtunnel
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### Prerequisites
|
|
31
|
+
|
|
32
|
+
- Node.js 18+
|
|
33
|
+
- cloudflared (auto-installed on macOS/Linux)
|
|
34
|
+
|
|
35
|
+
## Usage
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
# Start DevTunnel with tunnel
|
|
39
|
+
devtunnel
|
|
40
|
+
|
|
41
|
+
# Use custom port
|
|
42
|
+
devtunnel --port 8080
|
|
43
|
+
|
|
44
|
+
# Enable passcode authentication (auto-generated)
|
|
45
|
+
devtunnel --passcode
|
|
46
|
+
|
|
47
|
+
# Enable passcode authentication (custom)
|
|
48
|
+
devtunnel --passcode=MYCODE
|
|
49
|
+
|
|
50
|
+
# Show help
|
|
51
|
+
devtunnel --help
|
|
52
|
+
|
|
53
|
+
# Show version
|
|
54
|
+
devtunnel --version
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Authentication
|
|
58
|
+
|
|
59
|
+
When running with `--passcode`, DevTunnel requires authentication:
|
|
60
|
+
|
|
61
|
+
- A login page is shown before accessing the terminal
|
|
62
|
+
- Passcode is displayed in the CLI output when auto-generated
|
|
63
|
+
- Sessions remain authenticated for 24 hours
|
|
64
|
+
- Use the Logout button in the header to end your session
|
|
65
|
+
|
|
66
|
+
## Development
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
# Clone the repository
|
|
70
|
+
git clone https://github.com/mjasano/web-terminal.git
|
|
71
|
+
cd web-terminal
|
|
72
|
+
|
|
73
|
+
# Install dependencies
|
|
74
|
+
npm install
|
|
75
|
+
|
|
76
|
+
# Start development server
|
|
77
|
+
npm start
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Docker
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
# Build and run
|
|
84
|
+
docker-compose up -d
|
|
85
|
+
|
|
86
|
+
# Or with Docker
|
|
87
|
+
docker build -t devtunnel .
|
|
88
|
+
docker run -p 3000:3000 -v $(pwd):/workspace devtunnel
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Environment Variables
|
|
92
|
+
|
|
93
|
+
| Variable | Description | Default |
|
|
94
|
+
|----------|-------------|---------|
|
|
95
|
+
| `PORT` | Server port | 3000 |
|
|
96
|
+
| `WORKSPACE` | Root directory for file browser | Home directory |
|
|
97
|
+
| `PASSCODE` | Authentication passcode | None (no auth) |
|
|
98
|
+
|
|
99
|
+
## API Endpoints
|
|
100
|
+
|
|
101
|
+
### REST API
|
|
102
|
+
|
|
103
|
+
| Method | Endpoint | Description |
|
|
104
|
+
|--------|----------|-------------|
|
|
105
|
+
| GET | `/api/auth/status` | Check auth status |
|
|
106
|
+
| POST | `/api/auth/login` | Login with passcode |
|
|
107
|
+
| POST | `/api/auth/logout` | Logout |
|
|
108
|
+
| GET | `/api/system` | System information |
|
|
109
|
+
| GET | `/api/files` | File listing |
|
|
110
|
+
| GET | `/api/files/read` | Read file content |
|
|
111
|
+
| POST | `/api/files/write` | Write file content |
|
|
112
|
+
| POST | `/api/files/delete` | Delete file/directory |
|
|
113
|
+
| POST | `/api/files/rename` | Rename file/directory |
|
|
114
|
+
| GET | `/api/sessions` | Terminal sessions |
|
|
115
|
+
| DELETE | `/api/sessions/:id` | Kill session |
|
|
116
|
+
| GET | `/api/tunnels` | Active tunnels |
|
|
117
|
+
| POST | `/api/tunnels` | Create tunnel |
|
|
118
|
+
| DELETE | `/api/tunnels/:id` | Stop tunnel |
|
|
119
|
+
| GET | `/health` | Health check |
|
|
120
|
+
|
|
121
|
+
### WebSocket Messages
|
|
122
|
+
|
|
123
|
+
| Type | Direction | Description |
|
|
124
|
+
|------|-----------|-------------|
|
|
125
|
+
| `attach` | Client | Attach to session |
|
|
126
|
+
| `detach` | Client | Detach from session |
|
|
127
|
+
| `input` | Client | Terminal input |
|
|
128
|
+
| `resize` | Client | Terminal resize |
|
|
129
|
+
| `create-tunnel` | Client | Create tunnel |
|
|
130
|
+
| `stop-tunnel` | Client | Stop tunnel |
|
|
131
|
+
| `output` | Server | Terminal output |
|
|
132
|
+
| `attached` | Server | Session attached |
|
|
133
|
+
| `sessions` | Server | Session list |
|
|
134
|
+
| `tunnels` | Server | Tunnel list |
|
|
135
|
+
|
|
136
|
+
## License
|
|
137
|
+
|
|
138
|
+
MIT
|
package/bin/cli.js
CHANGED
|
@@ -3,8 +3,14 @@
|
|
|
3
3
|
const { spawn, execSync } = require('child_process');
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const http = require('http');
|
|
6
|
+
const crypto = require('crypto');
|
|
6
7
|
|
|
7
|
-
const VERSION = '
|
|
8
|
+
const { version: VERSION } = require('../package.json');
|
|
9
|
+
|
|
10
|
+
// Generate random passcode
|
|
11
|
+
function generatePasscode(length = 6) {
|
|
12
|
+
return crypto.randomBytes(length).toString('hex').slice(0, length).toUpperCase();
|
|
13
|
+
}
|
|
8
14
|
|
|
9
15
|
// Colors
|
|
10
16
|
const c = {
|
|
@@ -97,11 +103,16 @@ function waitForServer(port, timeout = 10000) {
|
|
|
97
103
|
});
|
|
98
104
|
}
|
|
99
105
|
|
|
100
|
-
function startServer(port) {
|
|
106
|
+
function startServer(port, passcode = null) {
|
|
101
107
|
const serverPath = path.join(__dirname, '..', 'server.js');
|
|
102
108
|
|
|
109
|
+
const env = { ...process.env, PORT: port };
|
|
110
|
+
if (passcode) {
|
|
111
|
+
env.PASSCODE = passcode;
|
|
112
|
+
}
|
|
113
|
+
|
|
103
114
|
const server = spawn('node', [serverPath], {
|
|
104
|
-
env
|
|
115
|
+
env,
|
|
105
116
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
106
117
|
detached: false
|
|
107
118
|
});
|
|
@@ -151,12 +162,17 @@ function startTunnel(port) {
|
|
|
151
162
|
});
|
|
152
163
|
}
|
|
153
164
|
|
|
154
|
-
function showSuccess(url) {
|
|
165
|
+
function showSuccess(url, passcode = null) {
|
|
155
166
|
log('');
|
|
156
167
|
log(`${c.green}${c.bold} Ready!${c.reset}`);
|
|
157
168
|
log('');
|
|
158
169
|
log(`${c.bold} Access from anywhere:${c.reset}`);
|
|
159
170
|
log(` ${c.cyan}${c.bold}${url}${c.reset}`);
|
|
171
|
+
if (passcode) {
|
|
172
|
+
log('');
|
|
173
|
+
log(`${c.bold} Passcode:${c.reset}`);
|
|
174
|
+
log(` ${c.yellow}${c.bold}${passcode}${c.reset}`);
|
|
175
|
+
}
|
|
160
176
|
log('');
|
|
161
177
|
log(`${c.dim} Features:${c.reset}`);
|
|
162
178
|
log(`${c.dim} - Web terminal with session persistence${c.reset}`);
|
|
@@ -174,11 +190,13 @@ async function main() {
|
|
|
174
190
|
if (command === '--help' || command === '-h') {
|
|
175
191
|
banner();
|
|
176
192
|
log(`${c.bold}Usage:${c.reset}`);
|
|
177
|
-
log(` devtunnel
|
|
178
|
-
log(` devtunnel start
|
|
179
|
-
log(` devtunnel --port 8080
|
|
180
|
-
log(` devtunnel --
|
|
181
|
-
log(` devtunnel --
|
|
193
|
+
log(` devtunnel Start DevTunnel`);
|
|
194
|
+
log(` devtunnel start Start DevTunnel`);
|
|
195
|
+
log(` devtunnel --port 8080 Use custom port`);
|
|
196
|
+
log(` devtunnel --passcode Enable auth with auto-generated passcode`);
|
|
197
|
+
log(` devtunnel --passcode=MYCODE Enable auth with custom passcode`);
|
|
198
|
+
log(` devtunnel --help Show this help`);
|
|
199
|
+
log(` devtunnel --version Show version`);
|
|
182
200
|
log('');
|
|
183
201
|
return;
|
|
184
202
|
}
|
|
@@ -195,6 +213,17 @@ async function main() {
|
|
|
195
213
|
port = parseInt(args[portIndex + 1]);
|
|
196
214
|
}
|
|
197
215
|
|
|
216
|
+
// Parse passcode
|
|
217
|
+
let passcode = null;
|
|
218
|
+
const passcodeArg = args.find(arg => arg.startsWith('--passcode'));
|
|
219
|
+
if (passcodeArg) {
|
|
220
|
+
if (passcodeArg.includes('=')) {
|
|
221
|
+
passcode = passcodeArg.split('=')[1];
|
|
222
|
+
} else {
|
|
223
|
+
passcode = generatePasscode();
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
198
227
|
banner();
|
|
199
228
|
|
|
200
229
|
// Check cloudflared
|
|
@@ -206,7 +235,7 @@ async function main() {
|
|
|
206
235
|
|
|
207
236
|
// Start server
|
|
208
237
|
log(`${c.dim}Starting server on port ${port}...${c.reset}`);
|
|
209
|
-
const server = startServer(port);
|
|
238
|
+
const server = startServer(port, passcode);
|
|
210
239
|
|
|
211
240
|
try {
|
|
212
241
|
await waitForServer(port);
|
|
@@ -223,7 +252,7 @@ async function main() {
|
|
|
223
252
|
const { tunnel, url } = await startTunnel(port);
|
|
224
253
|
log(`${c.green}✓${c.reset} Tunnel created`);
|
|
225
254
|
|
|
226
|
-
showSuccess(url);
|
|
255
|
+
showSuccess(url, passcode);
|
|
227
256
|
|
|
228
257
|
// Handle shutdown
|
|
229
258
|
const cleanup = () => {
|
package/eslint.config.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
module.exports = [
|
|
2
|
+
{
|
|
3
|
+
ignores: ['node_modules/**', 'public/app.js'],
|
|
4
|
+
},
|
|
5
|
+
{
|
|
6
|
+
languageOptions: {
|
|
7
|
+
ecmaVersion: 2022,
|
|
8
|
+
sourceType: 'commonjs',
|
|
9
|
+
globals: {
|
|
10
|
+
console: 'readonly',
|
|
11
|
+
process: 'readonly',
|
|
12
|
+
Buffer: 'readonly',
|
|
13
|
+
__dirname: 'readonly',
|
|
14
|
+
require: 'readonly',
|
|
15
|
+
module: 'readonly',
|
|
16
|
+
exports: 'readonly',
|
|
17
|
+
setInterval: 'readonly',
|
|
18
|
+
clearInterval: 'readonly',
|
|
19
|
+
setTimeout: 'readonly',
|
|
20
|
+
clearTimeout: 'readonly',
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
rules: {
|
|
24
|
+
'no-unused-vars': ['error', { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }],
|
|
25
|
+
'no-console': 'off',
|
|
26
|
+
'prefer-const': 'error',
|
|
27
|
+
'no-var': 'error',
|
|
28
|
+
eqeqeq: ['error', 'always'],
|
|
29
|
+
curly: ['error', 'multi-line'],
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mjasano/devtunnel",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"description": "Web terminal with code editor and tunnel manager - access your dev environment from anywhere",
|
|
5
5
|
"main": "server.js",
|
|
6
6
|
"bin": {
|
|
@@ -8,7 +8,12 @@
|
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
10
|
"start": "node server.js",
|
|
11
|
-
"dev": "node server.js"
|
|
11
|
+
"dev": "node server.js",
|
|
12
|
+
"lint": "eslint .",
|
|
13
|
+
"lint:fix": "eslint . --fix",
|
|
14
|
+
"format": "prettier --write .",
|
|
15
|
+
"format:check": "prettier --check .",
|
|
16
|
+
"test": "node --test"
|
|
12
17
|
},
|
|
13
18
|
"keywords": [
|
|
14
19
|
"terminal",
|
|
@@ -21,7 +26,11 @@
|
|
|
21
26
|
"license": "MIT",
|
|
22
27
|
"dependencies": {
|
|
23
28
|
"@homebridge/node-pty-prebuilt-multiarch": "^0.13.1",
|
|
24
|
-
"express": "^
|
|
29
|
+
"express": "^5.0.1",
|
|
25
30
|
"ws": "^8.14.2"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"eslint": "^9.17.0",
|
|
34
|
+
"prettier": "^3.4.2"
|
|
26
35
|
}
|
|
27
36
|
}
|