ccwebtty 1.0.3 → 1.0.5
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/README.md +164 -26
- package/dist/index.js +49 -13
- package/dist/index.js.map +1 -1
- package/package.json +9 -3
package/README.md
CHANGED
|
@@ -1,61 +1,199 @@
|
|
|
1
1
|
# ccweb
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**随时随地远程操控你的电脑命令行 — 让 AI 编程工具不受设备限制。**
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
**Remote-control your computer's terminal from anywhere — unleash AI coding tools beyond device boundaries.**
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
在手机、平板或任意设备的浏览器中打开你家里或办公室电脑的完整终端。配合 Cloudflare Tunnel,无需公网 IP、无需端口映射,一条命令即可安全地将终端暴露到公网。
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
Open a full terminal to your home or office computer from any device's browser. With Cloudflare Tunnel, no public IP or port forwarding needed — one command to securely expose your terminal to the internet.
|
|
10
|
+
|
|
11
|
+
## 为什么用 ccweb? / Why ccweb?
|
|
12
|
+
|
|
13
|
+
**在手机上也能用 [Claude Code](https://docs.anthropic.com/en/docs/agents-and-tools/claude-code/overview)、[Cursor Agent](https://www.cursor.com/)、[Aider](https://aider.chat/)** — 这些 AI 编程工具都运行在终端中。ccweb 让你通过浏览器远程访问电脑终端,这意味着你可以:
|
|
14
|
+
|
|
15
|
+
**Run [Claude Code](https://docs.anthropic.com/en/docs/agents-and-tools/claude-code/overview), [Cursor Agent](https://www.cursor.com/), [Aider](https://aider.chat/) from your phone** — these AI coding tools all run in the terminal. ccweb gives you browser-based remote terminal access, which means you can:
|
|
16
|
+
|
|
17
|
+
- 📱 **手机/平板远程编程** — 躺在床上、出门在外,打开浏览器就能让 AI 帮你写代码
|
|
18
|
+
- 📱 **Code from phone/tablet** — lying in bed or on the go, open a browser and let AI write code for you
|
|
19
|
+
- 🖥️ **远程操控任意电脑** — 在公司操作家里的电脑,或反过来
|
|
20
|
+
- 🖥️ **Control any computer remotely** — operate your home PC from office, or vice versa
|
|
21
|
+
- 🤖 **运行 AI 命令行工具** — Claude Code、Aider、GitHub Copilot CLI 等工具在浏览器终端中完美运行
|
|
22
|
+
- 🤖 **Run AI CLI tools** — Claude Code, Aider, GitHub Copilot CLI, etc. work perfectly in the browser terminal
|
|
23
|
+
- 🌍 **一条命令公网访问** — `ccweb --tunnel`,无需配置路由器、无需公网 IP
|
|
24
|
+
- 🌍 **One command for public access** — `ccweb --tunnel`, no router config or public IP needed
|
|
25
|
+
- 🔒 **内置安全认证** — 用户名/密码保护,防止未授权访问
|
|
26
|
+
- 🔒 **Built-in auth** — username/password protection against unauthorized access
|
|
27
|
+
|
|
28
|
+
## 特性 / Features
|
|
29
|
+
|
|
30
|
+
- **node-pty** — 在服务端创建真实伪终端 / Spawns real pseudo-terminals on the server
|
|
31
|
+
- **xterm.js** — 在浏览器中渲染完整终端体验 / Full terminal experience rendered in the browser
|
|
32
|
+
- **WebSocket** — 实时双向通信,低延迟 / Real-time bidirectional communication with low latency
|
|
33
|
+
- 每个浏览器标签页拥有独立的 Shell 会话 / Each browser tab gets its own independent shell session
|
|
34
|
+
- 内置用户名/密码认证 / Built-in authentication (username/password)
|
|
35
|
+
- 可选 [Cloudflare Tunnel](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/) 支持,一条命令公网访问 / Optional Cloudflare Tunnel — one command for public access
|
|
36
|
+
- 自动防止系统休眠,保持后台长时间运行 / Automatic sleep prevention to keep the server running
|
|
37
|
+
|
|
38
|
+
## 快速开始 / Quick Start
|
|
39
|
+
|
|
40
|
+
> 环境要求 / Prerequisites: [Node.js](https://nodejs.org/) >= 18
|
|
10
41
|
|
|
11
42
|
```bash
|
|
43
|
+
# 1. 安装 / Install
|
|
12
44
|
npm install -g ccwebtty
|
|
45
|
+
|
|
46
|
+
# 2. 启动并开启公网访问 / Start with public access
|
|
47
|
+
ccweb --tunnel
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
启动后终端会输出一个公网 URL,在任意设备浏览器中打开即可操控你的电脑命令行。
|
|
51
|
+
|
|
52
|
+
After starting, a public URL will be printed — open it in any device's browser to control your computer's terminal.
|
|
53
|
+
|
|
54
|
+
## 使用场景 / Use Cases
|
|
55
|
+
|
|
56
|
+
### 在手机上运行 Claude Code / Run Claude Code from your phone
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
# 在电脑上启动 ccweb / On your computer
|
|
60
|
+
ccweb --tunnel
|
|
61
|
+
|
|
62
|
+
# 在手机浏览器打开输出的 URL,然后执行 / Open the URL on your phone, then run
|
|
63
|
+
claude
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### 远程使用 AI 编程工具 / Remote AI coding tools
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
# 在手机浏览器终端中运行 Aider / Run Aider in mobile browser terminal
|
|
70
|
+
aider --model claude-3.5-sonnet
|
|
71
|
+
|
|
72
|
+
# 或者使用 GitHub Copilot CLI / Or use GitHub Copilot CLI
|
|
73
|
+
gh copilot suggest "create a REST API"
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### 远程管理服务器 / Remote server management
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
# 远程执行 git 操作 / Remote git operations
|
|
80
|
+
git pull && npm run deploy
|
|
81
|
+
|
|
82
|
+
# 远程查看日志 / Remote log monitoring
|
|
83
|
+
tail -f /var/log/app.log
|
|
13
84
|
```
|
|
14
85
|
|
|
15
|
-
## Usage
|
|
86
|
+
## 更多用法 / More Usage
|
|
16
87
|
|
|
17
88
|
```bash
|
|
18
|
-
#
|
|
89
|
+
# 使用默认设置启动(端口 1989,监听所有网络接口)
|
|
90
|
+
# Start with default settings (port 1989, all interfaces)
|
|
19
91
|
ccweb
|
|
20
92
|
|
|
93
|
+
# 自定义端口和主机地址
|
|
21
94
|
# Custom port and host
|
|
22
95
|
ccweb --port 3000 --host 127.0.0.1
|
|
23
96
|
|
|
97
|
+
# 指定不同的 Shell
|
|
24
98
|
# Specify a different shell
|
|
25
99
|
ccweb --shell /bin/bash
|
|
100
|
+
|
|
101
|
+
# 通过 Cloudflare Tunnel 暴露到公网(无需账号)
|
|
102
|
+
# Expose via Cloudflare Tunnel (publicly accessible, no account required)
|
|
103
|
+
ccweb --tunnel
|
|
104
|
+
|
|
105
|
+
# 使用自定义域名(需要 Cloudflare 账号,在 Zero Trust 后台获取 Token)
|
|
106
|
+
# Use a custom domain (requires Cloudflare account, get token from Zero Trust dashboard)
|
|
107
|
+
ccweb --tunnel-token eyJhIjoiNjM...
|
|
108
|
+
|
|
109
|
+
# 组合使用 / Combine options
|
|
110
|
+
ccweb --port 3000 --shell /bin/zsh --tunnel
|
|
26
111
|
```
|
|
27
112
|
|
|
28
|
-
|
|
113
|
+
本地访问:在浏览器中打开 `http://localhost:<port>`(默认:`http://localhost:1989`)。
|
|
29
114
|
|
|
30
|
-
|
|
115
|
+
Local access: open `http://localhost:<port>` in your browser (default: `http://localhost:1989`).
|
|
31
116
|
|
|
32
|
-
|
|
117
|
+
## 参数 / Options
|
|
118
|
+
|
|
119
|
+
| 参数 Option | 默认值 Default | 说明 Description |
|
|
33
120
|
|--------|---------|-------------|
|
|
34
|
-
| `-p, --port <number>` | `
|
|
35
|
-
| `-H, --host <address>` | `0.0.0.0` | Host to bind to |
|
|
36
|
-
| `-s, --shell <path>` | `$SHELL` or `/bin/bash` | Shell to spawn |
|
|
37
|
-
| `-u, --username <name>` | `cc` | Username for authentication |
|
|
38
|
-
| `--password <password>` | random | Password for authentication |
|
|
121
|
+
| `-p, --port <number>` | `1989` | 监听端口 / Port to listen on |
|
|
122
|
+
| `-H, --host <address>` | `0.0.0.0` | 绑定地址 / Host to bind to |
|
|
123
|
+
| `-s, --shell <path>` | `$SHELL` or `/bin/bash` | 要启动的 Shell / Shell to spawn |
|
|
124
|
+
| `-u, --username <name>` | `cc` | 认证用户名 / Username for authentication |
|
|
125
|
+
| `--password <password>` | 随机生成 random | 认证密码(未设置则自动生成)/ Password for authentication |
|
|
126
|
+
| `--tunnel` | 禁用 disabled | 通过 Cloudflare Tunnel 暴露到公网(无需账号)/ Expose via Cloudflare Tunnel |
|
|
127
|
+
| `--tunnel-token <token>` | — | 使用 Cloudflare Tunnel Token 绑定自定义域名 / Use a Cloudflare Tunnel token for custom domain |
|
|
128
|
+
|
|
129
|
+
## 命令 / Commands
|
|
130
|
+
|
|
131
|
+
| 命令 Command | 说明 Description |
|
|
132
|
+
|---------|-------------|
|
|
133
|
+
| `ccweb start` | 启动 Web 终端服务(默认命令)/ Start the web terminal server (default) |
|
|
134
|
+
| `ccweb update` | 更新 ccweb 到最新版本 / Update ccweb to the latest version |
|
|
135
|
+
|
|
136
|
+
## 自定义域名(Cloudflare Tunnel Token)/ Custom Domain
|
|
137
|
+
|
|
138
|
+
除了使用 `--tunnel` 获取随机域名外,你还可以通过 Cloudflare Tunnel Token 绑定自己的域名。
|
|
139
|
+
|
|
140
|
+
In addition to using `--tunnel` for a random domain, you can bind your own domain via a Cloudflare Tunnel Token.
|
|
141
|
+
|
|
142
|
+
### 获取 Token / How to get a Token
|
|
143
|
+
|
|
144
|
+
1. 登录 [Cloudflare Zero Trust](https://one.dash.cloudflare.com/) 后台 / Log in to the Cloudflare Zero Trust dashboard
|
|
145
|
+
2. 进入 **Networks → Tunnels**,点击 **Create a tunnel** / Go to **Networks → Tunnels**, click **Create a tunnel**
|
|
146
|
+
3. 选择 **Cloudflared** 类型,为隧道命名(如 `ccweb`)/ Select **Cloudflared**, name your tunnel (e.g. `ccweb`)
|
|
147
|
+
4. 在安装页面复制 Token(`eyJ` 开头的字符串)/ Copy the Token from the install page (starts with `eyJ`)
|
|
148
|
+
5. 配置 **Public Hostname** / Configure **Public Hostname**:
|
|
149
|
+
- **Subdomain**: 填入子域名,如 `terminal` / Enter a subdomain, e.g. `terminal`
|
|
150
|
+
- **Domain**: 选择你在 Cloudflare 的域名 / Select your domain in Cloudflare
|
|
151
|
+
- **Service Type**: `HTTP`
|
|
152
|
+
- **URL**: `localhost:1989`(与 ccweb 端口一致)/ (must match ccweb port)
|
|
153
|
+
|
|
154
|
+
### 使用 / Usage
|
|
39
155
|
|
|
40
|
-
|
|
156
|
+
```bash
|
|
157
|
+
ccweb --tunnel-token eyJhIjoiNjM...
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
配置完成后,即可通过自定义域名(如 `https://terminal.example.com`)访问终端。
|
|
161
|
+
|
|
162
|
+
Once configured, you can access the terminal via your custom domain (e.g. `https://terminal.example.com`).
|
|
163
|
+
|
|
164
|
+
### 对比 / Comparison
|
|
165
|
+
|
|
166
|
+
| | `--tunnel` | `--tunnel-token <token>` |
|
|
167
|
+
|---|---|---|
|
|
168
|
+
| 域名 Domain | 随机 `*.trycloudflare.com` / Random | 自定义域名 / Custom domain |
|
|
169
|
+
| 需要账号 Account | 不需要 / No | 需要 Cloudflare 账号 / Yes |
|
|
170
|
+
| 持久性 Persistence | 每次启动域名不同 / Changes on restart | 固定域名 / Persistent |
|
|
171
|
+
| 适用场景 Use case | 临时分享 / Temporary sharing | 长期使用 / Long-term use |
|
|
172
|
+
|
|
173
|
+
## 防止休眠 / Sleep Prevention
|
|
174
|
+
|
|
175
|
+
ccweb 启动后会自动阻止系统进入休眠状态,确保终端服务在后台持续运行。
|
|
176
|
+
|
|
177
|
+
When ccweb starts, it automatically prevents the system from sleeping, ensuring the terminal server keeps running in the background.
|
|
178
|
+
|
|
179
|
+
| 平台 Platform | 实现方式 Implementation | 条件 Requirement |
|
|
180
|
+
|---------|---------|---------|
|
|
181
|
+
| macOS | `caffeinate -s -w <pid>` | 系统自带,无需额外安装 / Built-in, no extra install needed |
|
|
182
|
+
| Windows | PowerShell `SetThreadExecutionState` | 系统自带,无需额外安装 / Built-in, no extra install needed |
|
|
183
|
+
| Linux | 不支持 / Not supported | — |
|
|
184
|
+
|
|
185
|
+
## 开发 / Development
|
|
41
186
|
|
|
42
187
|
```bash
|
|
43
|
-
# Install dependencies
|
|
188
|
+
# 安装依赖 / Install dependencies
|
|
44
189
|
npm install
|
|
45
190
|
|
|
46
|
-
# Build
|
|
191
|
+
# 构建 / Build
|
|
47
192
|
npm run build
|
|
48
193
|
|
|
49
|
-
# Run
|
|
194
|
+
# 运行 / Run
|
|
50
195
|
npm start
|
|
51
196
|
|
|
52
|
-
# Watch mode
|
|
197
|
+
# 监听模式 / Watch mode
|
|
53
198
|
npm run dev
|
|
54
199
|
```
|
|
55
|
-
|
|
56
|
-
## Architecture
|
|
57
|
-
|
|
58
|
-
- **node-pty** spawns pseudo-terminals on the server
|
|
59
|
-
- **xterm.js** renders the terminal in the browser
|
|
60
|
-
- **WebSocket** provides real-time bidirectional communication
|
|
61
|
-
- Each browser tab gets its own independent shell session
|
package/dist/index.js
CHANGED
|
@@ -201,7 +201,7 @@ function parseCookies(header) {
|
|
|
201
201
|
return cookies;
|
|
202
202
|
}
|
|
203
203
|
function startServer(options) {
|
|
204
|
-
const { port, host, shell, username, password, tunnel: enableTunnel } = options;
|
|
204
|
+
const { port, host, shell, username, password, tunnel: enableTunnel, tunnelToken } = options;
|
|
205
205
|
const sessionManager = new SessionManager();
|
|
206
206
|
const authSecret = generateSecret();
|
|
207
207
|
const app = (0, import_express.default)();
|
|
@@ -223,8 +223,20 @@ function startServer(options) {
|
|
|
223
223
|
if (!token) return false;
|
|
224
224
|
return verifyToken(token, authSecret);
|
|
225
225
|
}
|
|
226
|
+
const LOGIN_MAX_ATTEMPTS = 3;
|
|
227
|
+
const LOGIN_LOCKOUT_MS = 30 * 60 * 1e3;
|
|
228
|
+
const loginAttempts = /* @__PURE__ */ new Map();
|
|
229
|
+
function getClientIp(req) {
|
|
230
|
+
return req.ip || req.socket.remoteAddress || "unknown";
|
|
231
|
+
}
|
|
226
232
|
app.use(import_express.default.urlencoded({ extended: false }));
|
|
227
233
|
app.post("/login", (req, res) => {
|
|
234
|
+
const ip = getClientIp(req);
|
|
235
|
+
const record = loginAttempts.get(ip);
|
|
236
|
+
if (record && record.lockedUntil > Date.now()) {
|
|
237
|
+
const remaining = Math.ceil((record.lockedUntil - Date.now()) / 6e4);
|
|
238
|
+
return res.status(429).send(`Too many failed attempts. Try again in ${remaining} minute(s).`);
|
|
239
|
+
}
|
|
228
240
|
const { username: u, password: p } = req.body;
|
|
229
241
|
if (!u || !p) {
|
|
230
242
|
return res.status(400).send("Missing credentials");
|
|
@@ -232,8 +244,16 @@ function startServer(options) {
|
|
|
232
244
|
const uMatch = u.length === username.length && import_crypto2.default.timingSafeEqual(Buffer.from(u), Buffer.from(username));
|
|
233
245
|
const pMatch = p.length === password.length && import_crypto2.default.timingSafeEqual(Buffer.from(p), Buffer.from(password));
|
|
234
246
|
if (!uMatch || !pMatch) {
|
|
235
|
-
|
|
247
|
+
const attempts = (record?.count || 0) + 1;
|
|
248
|
+
if (attempts >= LOGIN_MAX_ATTEMPTS) {
|
|
249
|
+
loginAttempts.set(ip, { count: attempts, lockedUntil: Date.now() + LOGIN_LOCKOUT_MS });
|
|
250
|
+
console.log(`[ccweb] IP ${ip} locked out for 30 minutes after ${attempts} failed attempts`);
|
|
251
|
+
return res.status(429).send(`Too many failed attempts. Try again in 30 minute(s).`);
|
|
252
|
+
}
|
|
253
|
+
loginAttempts.set(ip, { count: attempts, lockedUntil: 0 });
|
|
254
|
+
return res.status(401).send(`Invalid credentials (${LOGIN_MAX_ATTEMPTS - attempts} attempt(s) remaining)`);
|
|
236
255
|
}
|
|
256
|
+
loginAttempts.delete(ip);
|
|
237
257
|
const token = signToken("auth", authSecret);
|
|
238
258
|
const secure = req.protocol === "https" || req.headers["x-forwarded-proto"] === "https";
|
|
239
259
|
const cookieFlags = `Path=/; HttpOnly; SameSite=Lax${secure ? "; Secure" : ""}`;
|
|
@@ -245,6 +265,7 @@ function startServer(options) {
|
|
|
245
265
|
return next();
|
|
246
266
|
}
|
|
247
267
|
if (req.method === "GET") {
|
|
268
|
+
res.setHeader("Cache-Control", "no-store, no-cache, must-revalidate");
|
|
248
269
|
return res.sendFile(import_path.default.join(__dirname, "public", "login.html"), { dotfiles: "allow" });
|
|
249
270
|
}
|
|
250
271
|
res.status(401).send("Unauthorized");
|
|
@@ -374,15 +395,29 @@ function startServer(options) {
|
|
|
374
395
|
console.log(`[ccweb] Username: ${username}`);
|
|
375
396
|
console.log(`[ccweb] Password: ${password}`);
|
|
376
397
|
console.log(`[ccweb] Press Ctrl+C to stop`);
|
|
377
|
-
if (enableTunnel) {
|
|
398
|
+
if (enableTunnel || tunnelToken) {
|
|
399
|
+
console.log("[ccweb] Starting Cloudflare Tunnel...");
|
|
378
400
|
import("cloudflared").then(({ Tunnel }) => {
|
|
379
|
-
|
|
401
|
+
console.log("[ccweb] cloudflared module loaded, creating tunnel...");
|
|
402
|
+
const t = tunnelToken ? Tunnel.withToken(tunnelToken) : Tunnel.quick(`http://localhost:${port}`, { "--no-autoupdate": true });
|
|
403
|
+
console.log("[ccweb] Tunnel process PID:", t.process.pid);
|
|
380
404
|
t.on("url", (url) => {
|
|
381
405
|
console.log(`[ccweb] Tunnel: ${url}`);
|
|
382
406
|
});
|
|
407
|
+
t.on("stdout", (output) => {
|
|
408
|
+
const line = output.trim();
|
|
409
|
+
if (line) console.log(`[ccweb] Tunnel stdout: ${line}`);
|
|
410
|
+
});
|
|
411
|
+
t.on("stderr", (output) => {
|
|
412
|
+
const line = output.trim();
|
|
413
|
+
if (line) console.log(`[ccweb] Tunnel stderr: ${line}`);
|
|
414
|
+
});
|
|
383
415
|
t.on("error", (err) => {
|
|
384
416
|
console.error(`[ccweb] Tunnel error: ${err.message}`);
|
|
385
417
|
});
|
|
418
|
+
t.on("exit", (code, signal) => {
|
|
419
|
+
console.log(`[ccweb] Tunnel exited with code=${code} signal=${signal}`);
|
|
420
|
+
});
|
|
386
421
|
const stopTunnel = () => t.stop();
|
|
387
422
|
process.on("SIGINT", stopTunnel);
|
|
388
423
|
process.on("SIGTERM", stopTunnel);
|
|
@@ -450,10 +485,10 @@ function fetchLatestVersion() {
|
|
|
450
485
|
async function checkUpdate() {
|
|
451
486
|
try {
|
|
452
487
|
const latest = await fetchLatestVersion();
|
|
453
|
-
if (latest !== "1.0.
|
|
488
|
+
if (latest !== "1.0.5") {
|
|
454
489
|
console.log(
|
|
455
490
|
`
|
|
456
|
-
New version available: ${"1.0.
|
|
491
|
+
New version available: ${"1.0.5"} -> \x1B[32m${latest}\x1B[0m`
|
|
457
492
|
);
|
|
458
493
|
console.log(` Run \x1B[36mccweb update\x1B[0m to update
|
|
459
494
|
`);
|
|
@@ -464,11 +499,11 @@ async function checkUpdate() {
|
|
|
464
499
|
async function selfUpdate() {
|
|
465
500
|
try {
|
|
466
501
|
const latest = await fetchLatestVersion();
|
|
467
|
-
if (latest === "1.0.
|
|
468
|
-
console.log(`Already up to date (v${"1.0.
|
|
502
|
+
if (latest === "1.0.5") {
|
|
503
|
+
console.log(`Already up to date (v${"1.0.5"})`);
|
|
469
504
|
return;
|
|
470
505
|
}
|
|
471
|
-
console.log(`Updating ${PKG_NAME}: ${"1.0.
|
|
506
|
+
console.log(`Updating ${PKG_NAME}: ${"1.0.5"} -> ${latest} ...`);
|
|
472
507
|
(0, import_child_process2.execSync)(`npm install -g ${PKG_NAME}@latest`, { stdio: "inherit" });
|
|
473
508
|
console.log(`Successfully updated to v${latest}`);
|
|
474
509
|
} catch (err) {
|
|
@@ -479,15 +514,15 @@ async function selfUpdate() {
|
|
|
479
514
|
|
|
480
515
|
// src/index.ts
|
|
481
516
|
var program = new import_commander.Command();
|
|
482
|
-
program.name("ccweb").description("A CLI tool that exposes an interactive web terminal in the browser").version("1.0.
|
|
517
|
+
program.name("ccweb").description("A CLI tool that exposes an interactive web terminal in the browser").version("1.0.5");
|
|
483
518
|
program.command("update").description("Update ccweb to the latest version").action(async () => {
|
|
484
519
|
await selfUpdate();
|
|
485
520
|
});
|
|
486
|
-
program.command("start", { isDefault: true }).description("Start the web terminal server").option("-p, --port <number>", "port to listen on", "
|
|
521
|
+
program.command("start", { isDefault: true }).description("Start the web terminal server").option("-p, --port <number>", "port to listen on", "1989").option("-H, --host <address>", "host to bind to", "0.0.0.0").option(
|
|
487
522
|
"-s, --shell <path>",
|
|
488
523
|
"shell to spawn",
|
|
489
524
|
process.env.SHELL || "/bin/bash"
|
|
490
|
-
).option("-u, --username <name>", "username for authentication", "cc").option("--password <password>", "password for authentication (random if not set)").option("--tunnel", "expose via Cloudflare Tunnel (no account required)").action(async (opts) => {
|
|
525
|
+
).option("-u, --username <name>", "username for authentication", "cc").option("--password <password>", "password for authentication (random if not set)").option("--tunnel", "expose via Cloudflare Tunnel (no account required)").option("--tunnel-token <token>", "use a Cloudflare Tunnel token for custom domain (from Zero Trust dashboard)").action(async (opts) => {
|
|
491
526
|
const port = parseInt(opts.port, 10);
|
|
492
527
|
if (isNaN(port) || port < 1 || port > 65535) {
|
|
493
528
|
console.error(`Invalid port: ${opts.port}`);
|
|
@@ -501,7 +536,8 @@ program.command("start", { isDefault: true }).description("Start the web termina
|
|
|
501
536
|
shell: opts.shell,
|
|
502
537
|
username: opts.username,
|
|
503
538
|
password,
|
|
504
|
-
tunnel: !!opts.tunnel
|
|
539
|
+
tunnel: !!opts.tunnel,
|
|
540
|
+
tunnelToken: opts.tunnelToken
|
|
505
541
|
});
|
|
506
542
|
});
|
|
507
543
|
program.parse();
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/server.ts","../src/session-manager.ts","../src/terminal.ts","../src/updater.ts"],"sourcesContent":["import crypto from \"crypto\";\nimport { Command } from \"commander\";\nimport { startServer } from \"./server\";\nimport { checkUpdate, selfUpdate } from \"./updater\";\n\ndeclare const __VERSION__: string;\n\nconst program = new Command();\n\nprogram\n .name(\"ccweb\")\n .description(\"A CLI tool that exposes an interactive web terminal in the browser\")\n .version(__VERSION__);\n\nprogram\n .command(\"update\")\n .description(\"Update ccweb to the latest version\")\n .action(async () => {\n await selfUpdate();\n });\n\nprogram\n .command(\"start\", { isDefault: true })\n .description(\"Start the web terminal server\")\n .option(\"-p, --port <number>\", \"port to listen on\", \"8080\")\n .option(\"-H, --host <address>\", \"host to bind to\", \"0.0.0.0\")\n .option(\n \"-s, --shell <path>\",\n \"shell to spawn\",\n process.env.SHELL || \"/bin/bash\"\n )\n .option(\"-u, --username <name>\", \"username for authentication\", \"cc\")\n .option(\"--password <password>\", \"password for authentication (random if not set)\")\n .option(\"--tunnel\", \"expose via Cloudflare Tunnel (no account required)\")\n .action(async (opts) => {\n const port = parseInt(opts.port, 10);\n if (isNaN(port) || port < 1 || port > 65535) {\n console.error(`Invalid port: ${opts.port}`);\n process.exit(1);\n }\n\n const password = opts.password || crypto.randomBytes(12).toString(\"base64url\");\n\n // Check for updates in background (non-blocking)\n checkUpdate();\n\n startServer({\n port,\n host: opts.host,\n shell: opts.shell,\n username: opts.username,\n password,\n tunnel: !!opts.tunnel,\n });\n });\n\nprogram.parse();\n","import crypto from \"crypto\";\nimport { spawn } from \"child_process\";\nimport express from \"express\";\nimport http from \"http\";\nimport os from \"os\";\nimport path from \"path\";\nimport { WebSocketServer, WebSocket } from \"ws\";\nimport { SessionManager } from \"./session-manager\";\n\nexport interface ServerOptions {\n port: number;\n host: string;\n shell: string;\n username: string;\n password: string;\n tunnel?: boolean;\n}\n\ninterface ClientMessage {\n type: \"input\" | \"resize\" | \"kill\" | \"create\";\n data?: string;\n cols?: number;\n rows?: number;\n sessionId?: string;\n}\n\n// Cookie-based auth helpers\n\nfunction generateSecret(): string {\n return crypto.randomBytes(32).toString(\"hex\");\n}\n\nfunction signToken(payload: string, secret: string): string {\n const hmac = crypto.createHmac(\"sha256\", secret);\n hmac.update(payload);\n return payload + \".\" + hmac.digest(\"hex\");\n}\n\nfunction verifyToken(token: string, secret: string): boolean {\n const dotIdx = token.lastIndexOf(\".\");\n if (dotIdx === -1) return false;\n const payload = token.substring(0, dotIdx);\n const sig = token.substring(dotIdx + 1);\n const hmac = crypto.createHmac(\"sha256\", secret);\n hmac.update(payload);\n const expected = hmac.digest(\"hex\");\n if (sig.length !== expected.length) return false;\n return crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));\n}\n\nfunction parseCookies(header: string | undefined): Record<string, string> {\n const cookies: Record<string, string> = {};\n if (!header) return cookies;\n for (const part of header.split(\";\")) {\n const eq = part.indexOf(\"=\");\n if (eq === -1) continue;\n const key = part.substring(0, eq).trim();\n const val = part.substring(eq + 1).trim();\n cookies[key] = decodeURIComponent(val);\n }\n return cookies;\n}\n\nexport function startServer(options: ServerOptions): void {\n const { port, host, shell, username, password, tunnel: enableTunnel } = options;\n\n const sessionManager = new SessionManager();\n const authSecret = generateSecret();\n\n const app = express();\n app.set(\"trust proxy\", true);\n const server = http.createServer(app);\n\n // Track all connected WebSocket clients for tab-list broadcasting\n const allClients = new Set<WebSocket>();\n\n function broadcastTabs(): void {\n const tabs = sessionManager.listIds();\n const msg = JSON.stringify({ type: \"tabs\", tabs });\n for (const client of allClients) {\n if (client.readyState === WebSocket.OPEN) {\n client.send(msg);\n }\n }\n }\n\n function isAuthenticated(cookieHeader: string | undefined): boolean {\n const cookies = parseCookies(cookieHeader);\n const token = cookies[\"ccweb_token\"];\n if (!token) return false;\n return verifyToken(token, authSecret);\n }\n\n // Login endpoint\n app.use(express.urlencoded({ extended: false }));\n\n app.post(\"/login\", (req, res) => {\n const { username: u, password: p } = req.body as { username?: string; password?: string };\n if (!u || !p) {\n return res.status(400).send(\"Missing credentials\");\n }\n const uMatch = u.length === username.length && crypto.timingSafeEqual(Buffer.from(u), Buffer.from(username));\n const pMatch = p.length === password.length && crypto.timingSafeEqual(Buffer.from(p), Buffer.from(password));\n if (!uMatch || !pMatch) {\n return res.status(401).send(\"Invalid credentials\");\n }\n const token = signToken(\"auth\", authSecret);\n const secure = req.protocol === \"https\" || req.headers[\"x-forwarded-proto\"] === \"https\";\n const cookieFlags = `Path=/; HttpOnly; SameSite=Lax${secure ? \"; Secure\" : \"\"}`;\n res.setHeader(\"Set-Cookie\", `ccweb_token=${encodeURIComponent(token)}; ${cookieFlags}`);\n res.redirect(\"/\");\n });\n\n // Auth middleware - skip login route and static login page\n app.use((req, res, next) => {\n if (isAuthenticated(req.headers.cookie)) {\n return next();\n }\n // Serve login page for GET requests\n if (req.method === \"GET\") {\n return res.sendFile(path.join(__dirname, \"public\", \"login.html\"), { dotfiles: \"allow\" });\n }\n res.status(401).send(\"Unauthorized\");\n });\n\n app.use(express.static(path.join(__dirname, \"public\"), { dotfiles: \"allow\" }));\n\n app.get(\"/\", (_req, res) => {\n res.sendFile(path.join(__dirname, \"public\", \"index.html\"), { dotfiles: \"allow\" });\n });\n\n const wss = new WebSocketServer({ noServer: true });\n\n // WebSocket ping/pong keepalive (Cloudflare Tunnel idle timeout is ~100s)\n const WS_PING_INTERVAL = 30_000;\n const pingInterval = setInterval(() => {\n for (const client of wss.clients) {\n if (client.readyState === WebSocket.OPEN) {\n client.ping();\n }\n }\n }, WS_PING_INTERVAL);\n wss.on(\"close\", () => clearInterval(pingInterval));\n\n server.on(\"upgrade\", (req, socket, head) => {\n if (!isAuthenticated(req.headers.cookie)) {\n socket.write(\"HTTP/1.1 401 Unauthorized\\r\\n\\r\\n\");\n socket.destroy();\n return;\n }\n wss.handleUpgrade(req, socket, head, (ws) => {\n wss.emit(\"connection\", ws, req);\n });\n });\n\n wss.on(\"connection\", (ws: WebSocket, req) => {\n const url = new URL(req.url || \"/\", `http://${req.headers.host}`);\n const sessionId = url.searchParams.get(\"sessionId\");\n const cols = parseInt(url.searchParams.get(\"cols\") || \"80\", 10);\n const rows = parseInt(url.searchParams.get(\"rows\") || \"24\", 10);\n\n allClients.add(ws);\n\n // If no sessionId, this is a control connection - send current tab list\n if (!sessionId) {\n // Auto-create a default session if none exist\n if (sessionManager.listIds().length === 0) {\n const session = sessionManager.create(shell, cols, rows);\n console.log(`[ccweb] Default session: ${session.id}`);\n }\n ws.send(JSON.stringify({ type: \"tabs\", tabs: sessionManager.listIds() }));\n\n ws.on(\"message\", (raw: Buffer) => {\n try {\n const msg: ClientMessage = JSON.parse(raw.toString());\n if (msg.type === \"create\") {\n const c = msg.cols || 80;\n const r = msg.rows || 24;\n const session = sessionManager.create(shell, c, r);\n console.log(`[ccweb] New session: ${session.id}`);\n broadcastTabs();\n } else if (msg.type === \"kill\" && msg.sessionId) {\n console.log(`[ccweb] Session killed: ${msg.sessionId}`);\n sessionManager.remove(msg.sessionId);\n broadcastTabs();\n }\n } catch {\n // Ignore malformed messages\n }\n });\n\n ws.on(\"close\", () => {\n allClients.delete(ws);\n });\n ws.on(\"error\", () => {\n allClients.delete(ws);\n });\n return;\n }\n\n // Session-specific connection\n const session = sessionManager.get(sessionId);\n if (!session) {\n ws.send(JSON.stringify({ type: \"error\", data: \"Session not found\" }));\n ws.close();\n allClients.delete(ws);\n return;\n }\n\n console.log(`[ccweb] Client attached to session: ${sessionId}`);\n\n // Replay buffered output so new clients see existing content\n for (const chunk of session.buffer) {\n ws.send(JSON.stringify({ type: \"output\", data: chunk }));\n }\n\n // Subscribe to live output\n const onOutput = (data: string) => {\n if (ws.readyState === WebSocket.OPEN) {\n ws.send(JSON.stringify({ type: \"output\", data }));\n }\n };\n session.clients.add(onOutput);\n\n const onExit = () => {\n console.log(`[ccweb] Session ended: ${sessionId}`);\n if (ws.readyState === WebSocket.OPEN) {\n ws.send(JSON.stringify({ type: \"exit\" }));\n ws.close();\n }\n sessionManager.remove(sessionId);\n broadcastTabs();\n };\n session.onExit.add(onExit);\n\n // Resize to match this client\n session.terminal.resize(cols, rows);\n\n ws.on(\"message\", (raw: Buffer) => {\n try {\n const msg: ClientMessage = JSON.parse(raw.toString());\n switch (msg.type) {\n case \"input\":\n if (msg.data) {\n session.terminal.write(msg.data);\n }\n break;\n case \"resize\":\n if (msg.cols && msg.rows) {\n session.terminal.resize(msg.cols, msg.rows);\n }\n break;\n }\n } catch {\n // Ignore malformed messages\n }\n });\n\n ws.on(\"close\", () => {\n allClients.delete(ws);\n session.clients.delete(onOutput);\n session.onExit.delete(onExit);\n });\n\n ws.on(\"error\", () => {\n allClients.delete(ws);\n session.clients.delete(onOutput);\n session.onExit.delete(onExit);\n });\n });\n\n server.listen(port, host, () => {\n const addr = host === \"0.0.0.0\" ? \"localhost\" : host;\n console.log(`[ccweb] Terminal server running at http://${addr}:${port}`);\n console.log(`[ccweb] Shell: ${shell}`);\n console.log(`[ccweb] Username: ${username}`);\n console.log(`[ccweb] Password: ${password}`);\n console.log(`[ccweb] Press Ctrl+C to stop`);\n\n // Start Cloudflare Tunnel if requested\n if (enableTunnel) {\n import(\"cloudflared\").then(({ Tunnel }) => {\n const t = new Tunnel([\"tunnel\", \"--url\", `http://localhost:${port}`, \"--no-autoupdate\"]);\n t.on(\"url\", (url: string) => {\n console.log(`[ccweb] Tunnel: ${url}`);\n });\n t.on(\"error\", (err: Error) => {\n console.error(`[ccweb] Tunnel error: ${err.message}`);\n });\n const stopTunnel = () => t.stop();\n process.on(\"SIGINT\", stopTunnel);\n process.on(\"SIGTERM\", stopTunnel);\n }).catch((err) => {\n console.error(\"[ccweb] Failed to start tunnel:\", err.message);\n });\n }\n\n // Prevent system sleep while ccweb is running\n const platform = os.platform();\n if (platform === \"darwin\") {\n const caffeinate = spawn(\"caffeinate\", [\"-s\", \"-w\", process.pid.toString()], {\n stdio: \"ignore\",\n });\n caffeinate.unref();\n console.log(\"[ccweb] Sleep prevention enabled (caffeinate)\");\n } else if (platform === \"win32\") {\n const script = `\nAdd-Type -TypeDefinition @\"\nusing System.Runtime.InteropServices;\npublic class Power {\n [DllImport(\"kernel32.dll\")]\n public static extern uint SetThreadExecutionState(uint esFlags);\n}\n\"@\nwhile($true) {\n [Power]::SetThreadExecutionState(0x80000001)\n Start-Sleep -Seconds 30\n}`;\n const ps = spawn(\"powershell\", [\"-NoProfile\", \"-Command\", script], {\n stdio: \"ignore\",\n });\n ps.unref();\n console.log(\"[ccweb] Sleep prevention enabled (SetThreadExecutionState)\");\n }\n });\n\n const shutdown = () => {\n console.log(\"\\n[ccweb] Shutting down...\");\n wss.clients.forEach((client) => client.close());\n sessionManager.destroyAll();\n server.close(() => process.exit(0));\n setTimeout(() => process.exit(1), 3000);\n };\n\n process.on(\"SIGINT\", shutdown);\n process.on(\"SIGTERM\", shutdown);\n}\n","import crypto from \"crypto\";\nimport { Terminal } from \"./terminal\";\n\nexport interface Session {\n id: string;\n terminal: Terminal;\n buffer: string[];\n clients: Set<(data: string) => void>;\n onExit: Set<() => void>;\n alive: boolean;\n}\n\nconst MAX_BUFFER = 5000;\n\nexport class SessionManager {\n private sessions = new Map<string, Session>();\n private order: string[] = [];\n\n create(shell: string, cols: number, rows: number): Session {\n const id = crypto.randomUUID();\n const terminal = new Terminal({ shell, cols, rows });\n\n const session: Session = {\n id,\n terminal,\n buffer: [],\n clients: new Set(),\n onExit: new Set(),\n alive: true,\n };\n\n terminal.onData((data) => {\n if (session.buffer.length >= MAX_BUFFER) {\n session.buffer.shift();\n }\n session.buffer.push(data);\n for (const cb of session.clients) {\n cb(data);\n }\n });\n\n terminal.onExit(() => {\n session.alive = false;\n for (const cb of session.onExit) {\n cb();\n }\n });\n\n this.sessions.set(id, session);\n this.order.push(id);\n return session;\n }\n\n get(id: string): Session | undefined {\n const session = this.sessions.get(id);\n if (session && !session.alive) {\n this.sessions.delete(id);\n this.order = this.order.filter((x) => x !== id);\n return undefined;\n }\n return session;\n }\n\n listIds(): string[] {\n this.order = this.order.filter((id) => {\n const s = this.sessions.get(id);\n if (s && s.alive) return true;\n this.sessions.delete(id);\n return false;\n });\n return [...this.order];\n }\n\n remove(id: string): void {\n const session = this.sessions.get(id);\n if (session) {\n session.terminal.kill();\n this.sessions.delete(id);\n }\n this.order = this.order.filter((x) => x !== id);\n }\n\n destroyAll(): void {\n for (const session of this.sessions.values()) {\n session.terminal.kill();\n }\n this.sessions.clear();\n this.order = [];\n }\n}\n","import * as pty from \"node-pty\";\nimport os from \"os\";\n\nexport interface TerminalOptions {\n shell: string;\n cols?: number;\n rows?: number;\n cwd?: string;\n}\n\nexport class Terminal {\n private process: pty.IPty;\n private disposed = false;\n\n constructor(options: TerminalOptions) {\n const { shell, cols = 80, rows = 24, cwd } = options;\n const env = Object.assign({}, process.env, {\n TERM: \"xterm-256color\",\n COLORTERM: \"truecolor\",\n FORCE_COLOR: \"3\",\n TERM_PROGRAM: \"xterm-256color\",\n });\n delete (env as Record<string, string | undefined>).NO_COLOR;\n\n this.process = pty.spawn(shell, [], {\n name: \"xterm-256color\",\n cols,\n rows,\n cwd: cwd || os.homedir(),\n env: env as Record<string, string>,\n });\n }\n\n get pid(): number {\n return this.process.pid;\n }\n\n onData(callback: (data: string) => void): void {\n this.process.onData(callback);\n }\n\n onExit(callback: (exitCode: number, signal?: number) => void): void {\n this.process.onExit(({ exitCode, signal }) => {\n callback(exitCode, signal);\n });\n }\n\n write(data: string): void {\n if (!this.disposed) {\n this.process.write(data);\n }\n }\n\n resize(cols: number, rows: number): void {\n if (!this.disposed) {\n try {\n this.process.resize(cols, rows);\n } catch {\n // PTY may already be closed\n }\n }\n }\n\n kill(): void {\n if (!this.disposed) {\n this.disposed = true;\n try {\n this.process.kill();\n } catch {\n // Already dead\n }\n }\n }\n}\n","import https from \"https\";\nimport { execSync } from \"child_process\";\n\ndeclare const __VERSION__: string;\nconst PKG_NAME = \"ccwebtty\";\n\nfunction fetchLatestVersion(): Promise<string> {\n return new Promise((resolve, reject) => {\n https\n .get(`https://registry.npmjs.org/${PKG_NAME}/latest`, (res) => {\n let data = \"\";\n res.on(\"data\", (chunk) => (data += chunk));\n res.on(\"end\", () => {\n try {\n resolve(JSON.parse(data).version);\n } catch {\n reject(new Error(\"Failed to parse registry response\"));\n }\n });\n })\n .on(\"error\", reject);\n });\n}\n\nexport async function checkUpdate(): Promise<void> {\n try {\n const latest = await fetchLatestVersion();\n if (latest !== __VERSION__) {\n console.log(\n `\\n New version available: ${__VERSION__} -> \\x1b[32m${latest}\\x1b[0m`\n );\n console.log(` Run \\x1b[36mccweb update\\x1b[0m to update\\n`);\n }\n } catch {\n // silently ignore network errors\n }\n}\n\nexport async function selfUpdate(): Promise<void> {\n try {\n const latest = await fetchLatestVersion();\n if (latest === __VERSION__) {\n console.log(`Already up to date (v${__VERSION__})`);\n return;\n }\n console.log(`Updating ${PKG_NAME}: ${__VERSION__} -> ${latest} ...`);\n execSync(`npm install -g ${PKG_NAME}@latest`, { stdio: \"inherit\" });\n console.log(`Successfully updated to v${latest}`);\n } catch (err: any) {\n console.error(`Update failed: ${err.message}`);\n process.exit(1);\n }\n}\n"],"mappings":"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","names":["import_crypto","import_crypto","import_os","os","crypto","crypto","express","http","path","session","os","import_child_process","https","crypto"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/server.ts","../src/session-manager.ts","../src/terminal.ts","../src/updater.ts"],"sourcesContent":["import crypto from \"crypto\";\nimport { Command } from \"commander\";\nimport { startServer } from \"./server\";\nimport { checkUpdate, selfUpdate } from \"./updater\";\n\ndeclare const __VERSION__: string;\n\nconst program = new Command();\n\nprogram\n .name(\"ccweb\")\n .description(\"A CLI tool that exposes an interactive web terminal in the browser\")\n .version(__VERSION__);\n\nprogram\n .command(\"update\")\n .description(\"Update ccweb to the latest version\")\n .action(async () => {\n await selfUpdate();\n });\n\nprogram\n .command(\"start\", { isDefault: true })\n .description(\"Start the web terminal server\")\n .option(\"-p, --port <number>\", \"port to listen on\", \"1989\")\n .option(\"-H, --host <address>\", \"host to bind to\", \"0.0.0.0\")\n .option(\n \"-s, --shell <path>\",\n \"shell to spawn\",\n process.env.SHELL || \"/bin/bash\"\n )\n .option(\"-u, --username <name>\", \"username for authentication\", \"cc\")\n .option(\"--password <password>\", \"password for authentication (random if not set)\")\n .option(\"--tunnel\", \"expose via Cloudflare Tunnel (no account required)\")\n .option(\"--tunnel-token <token>\", \"use a Cloudflare Tunnel token for custom domain (from Zero Trust dashboard)\")\n .action(async (opts) => {\n const port = parseInt(opts.port, 10);\n if (isNaN(port) || port < 1 || port > 65535) {\n console.error(`Invalid port: ${opts.port}`);\n process.exit(1);\n }\n\n const password = opts.password || crypto.randomBytes(12).toString(\"base64url\");\n\n // Check for updates in background (non-blocking)\n checkUpdate();\n\n startServer({\n port,\n host: opts.host,\n shell: opts.shell,\n username: opts.username,\n password,\n tunnel: !!opts.tunnel,\n tunnelToken: opts.tunnelToken,\n });\n });\n\nprogram.parse();\n","import crypto from \"crypto\";\nimport { spawn } from \"child_process\";\nimport express from \"express\";\nimport http from \"http\";\nimport os from \"os\";\nimport path from \"path\";\nimport { WebSocketServer, WebSocket } from \"ws\";\nimport { SessionManager } from \"./session-manager\";\n\nexport interface ServerOptions {\n port: number;\n host: string;\n shell: string;\n username: string;\n password: string;\n tunnel?: boolean;\n tunnelToken?: string;\n}\n\ninterface ClientMessage {\n type: \"input\" | \"resize\" | \"kill\" | \"create\";\n data?: string;\n cols?: number;\n rows?: number;\n sessionId?: string;\n}\n\n// Cookie-based auth helpers\n\nfunction generateSecret(): string {\n return crypto.randomBytes(32).toString(\"hex\");\n}\n\nfunction signToken(payload: string, secret: string): string {\n const hmac = crypto.createHmac(\"sha256\", secret);\n hmac.update(payload);\n return payload + \".\" + hmac.digest(\"hex\");\n}\n\nfunction verifyToken(token: string, secret: string): boolean {\n const dotIdx = token.lastIndexOf(\".\");\n if (dotIdx === -1) return false;\n const payload = token.substring(0, dotIdx);\n const sig = token.substring(dotIdx + 1);\n const hmac = crypto.createHmac(\"sha256\", secret);\n hmac.update(payload);\n const expected = hmac.digest(\"hex\");\n if (sig.length !== expected.length) return false;\n return crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));\n}\n\nfunction parseCookies(header: string | undefined): Record<string, string> {\n const cookies: Record<string, string> = {};\n if (!header) return cookies;\n for (const part of header.split(\";\")) {\n const eq = part.indexOf(\"=\");\n if (eq === -1) continue;\n const key = part.substring(0, eq).trim();\n const val = part.substring(eq + 1).trim();\n cookies[key] = decodeURIComponent(val);\n }\n return cookies;\n}\n\nexport function startServer(options: ServerOptions): void {\n const { port, host, shell, username, password, tunnel: enableTunnel, tunnelToken } = options;\n\n const sessionManager = new SessionManager();\n const authSecret = generateSecret();\n\n const app = express();\n app.set(\"trust proxy\", true);\n const server = http.createServer(app);\n\n // Track all connected WebSocket clients for tab-list broadcasting\n const allClients = new Set<WebSocket>();\n\n function broadcastTabs(): void {\n const tabs = sessionManager.listIds();\n const msg = JSON.stringify({ type: \"tabs\", tabs });\n for (const client of allClients) {\n if (client.readyState === WebSocket.OPEN) {\n client.send(msg);\n }\n }\n }\n\n function isAuthenticated(cookieHeader: string | undefined): boolean {\n const cookies = parseCookies(cookieHeader);\n const token = cookies[\"ccweb_token\"];\n if (!token) return false;\n return verifyToken(token, authSecret);\n }\n\n // Login rate limiting: 3 failures per IP → 30 min lockout\n const LOGIN_MAX_ATTEMPTS = 3;\n const LOGIN_LOCKOUT_MS = 30 * 60 * 1000;\n const loginAttempts = new Map<string, { count: number; lockedUntil: number }>();\n\n function getClientIp(req: express.Request): string {\n return (req.ip || req.socket.remoteAddress || \"unknown\");\n }\n\n // Login endpoint\n app.use(express.urlencoded({ extended: false }));\n\n app.post(\"/login\", (req, res) => {\n const ip = getClientIp(req);\n const record = loginAttempts.get(ip);\n\n if (record && record.lockedUntil > Date.now()) {\n const remaining = Math.ceil((record.lockedUntil - Date.now()) / 60000);\n return res.status(429).send(`Too many failed attempts. Try again in ${remaining} minute(s).`);\n }\n\n const { username: u, password: p } = req.body as { username?: string; password?: string };\n if (!u || !p) {\n return res.status(400).send(\"Missing credentials\");\n }\n const uMatch = u.length === username.length && crypto.timingSafeEqual(Buffer.from(u), Buffer.from(username));\n const pMatch = p.length === password.length && crypto.timingSafeEqual(Buffer.from(p), Buffer.from(password));\n if (!uMatch || !pMatch) {\n const attempts = (record?.count || 0) + 1;\n if (attempts >= LOGIN_MAX_ATTEMPTS) {\n loginAttempts.set(ip, { count: attempts, lockedUntil: Date.now() + LOGIN_LOCKOUT_MS });\n console.log(`[ccweb] IP ${ip} locked out for 30 minutes after ${attempts} failed attempts`);\n return res.status(429).send(`Too many failed attempts. Try again in 30 minute(s).`);\n }\n loginAttempts.set(ip, { count: attempts, lockedUntil: 0 });\n return res.status(401).send(`Invalid credentials (${LOGIN_MAX_ATTEMPTS - attempts} attempt(s) remaining)`);\n }\n\n // Successful login - clear failed attempts\n loginAttempts.delete(ip);\n\n const token = signToken(\"auth\", authSecret);\n const secure = req.protocol === \"https\" || req.headers[\"x-forwarded-proto\"] === \"https\";\n const cookieFlags = `Path=/; HttpOnly; SameSite=Lax${secure ? \"; Secure\" : \"\"}`;\n res.setHeader(\"Set-Cookie\", `ccweb_token=${encodeURIComponent(token)}; ${cookieFlags}`);\n res.redirect(\"/\");\n });\n\n // Auth middleware - skip login route and static login page\n app.use((req, res, next) => {\n if (isAuthenticated(req.headers.cookie)) {\n return next();\n }\n // Serve login page for GET requests\n if (req.method === \"GET\") {\n res.setHeader(\"Cache-Control\", \"no-store, no-cache, must-revalidate\");\n return res.sendFile(path.join(__dirname, \"public\", \"login.html\"), { dotfiles: \"allow\" });\n }\n res.status(401).send(\"Unauthorized\");\n });\n\n app.use(express.static(path.join(__dirname, \"public\"), { dotfiles: \"allow\" }));\n\n app.get(\"/\", (_req, res) => {\n res.sendFile(path.join(__dirname, \"public\", \"index.html\"), { dotfiles: \"allow\" });\n });\n\n const wss = new WebSocketServer({ noServer: true });\n\n // WebSocket ping/pong keepalive (Cloudflare Tunnel idle timeout is ~100s)\n const WS_PING_INTERVAL = 30_000;\n const pingInterval = setInterval(() => {\n for (const client of wss.clients) {\n if (client.readyState === WebSocket.OPEN) {\n client.ping();\n }\n }\n }, WS_PING_INTERVAL);\n wss.on(\"close\", () => clearInterval(pingInterval));\n\n server.on(\"upgrade\", (req, socket, head) => {\n if (!isAuthenticated(req.headers.cookie)) {\n socket.write(\"HTTP/1.1 401 Unauthorized\\r\\n\\r\\n\");\n socket.destroy();\n return;\n }\n wss.handleUpgrade(req, socket, head, (ws) => {\n wss.emit(\"connection\", ws, req);\n });\n });\n\n wss.on(\"connection\", (ws: WebSocket, req) => {\n const url = new URL(req.url || \"/\", `http://${req.headers.host}`);\n const sessionId = url.searchParams.get(\"sessionId\");\n const cols = parseInt(url.searchParams.get(\"cols\") || \"80\", 10);\n const rows = parseInt(url.searchParams.get(\"rows\") || \"24\", 10);\n\n allClients.add(ws);\n\n // If no sessionId, this is a control connection - send current tab list\n if (!sessionId) {\n // Auto-create a default session if none exist\n if (sessionManager.listIds().length === 0) {\n const session = sessionManager.create(shell, cols, rows);\n console.log(`[ccweb] Default session: ${session.id}`);\n }\n ws.send(JSON.stringify({ type: \"tabs\", tabs: sessionManager.listIds() }));\n\n ws.on(\"message\", (raw: Buffer) => {\n try {\n const msg: ClientMessage = JSON.parse(raw.toString());\n if (msg.type === \"create\") {\n const c = msg.cols || 80;\n const r = msg.rows || 24;\n const session = sessionManager.create(shell, c, r);\n console.log(`[ccweb] New session: ${session.id}`);\n broadcastTabs();\n } else if (msg.type === \"kill\" && msg.sessionId) {\n console.log(`[ccweb] Session killed: ${msg.sessionId}`);\n sessionManager.remove(msg.sessionId);\n broadcastTabs();\n }\n } catch {\n // Ignore malformed messages\n }\n });\n\n ws.on(\"close\", () => {\n allClients.delete(ws);\n });\n ws.on(\"error\", () => {\n allClients.delete(ws);\n });\n return;\n }\n\n // Session-specific connection\n const session = sessionManager.get(sessionId);\n if (!session) {\n ws.send(JSON.stringify({ type: \"error\", data: \"Session not found\" }));\n ws.close();\n allClients.delete(ws);\n return;\n }\n\n console.log(`[ccweb] Client attached to session: ${sessionId}`);\n\n // Replay buffered output so new clients see existing content\n for (const chunk of session.buffer) {\n ws.send(JSON.stringify({ type: \"output\", data: chunk }));\n }\n\n // Subscribe to live output\n const onOutput = (data: string) => {\n if (ws.readyState === WebSocket.OPEN) {\n ws.send(JSON.stringify({ type: \"output\", data }));\n }\n };\n session.clients.add(onOutput);\n\n const onExit = () => {\n console.log(`[ccweb] Session ended: ${sessionId}`);\n if (ws.readyState === WebSocket.OPEN) {\n ws.send(JSON.stringify({ type: \"exit\" }));\n ws.close();\n }\n sessionManager.remove(sessionId);\n broadcastTabs();\n };\n session.onExit.add(onExit);\n\n // Resize to match this client\n session.terminal.resize(cols, rows);\n\n ws.on(\"message\", (raw: Buffer) => {\n try {\n const msg: ClientMessage = JSON.parse(raw.toString());\n switch (msg.type) {\n case \"input\":\n if (msg.data) {\n session.terminal.write(msg.data);\n }\n break;\n case \"resize\":\n if (msg.cols && msg.rows) {\n session.terminal.resize(msg.cols, msg.rows);\n }\n break;\n }\n } catch {\n // Ignore malformed messages\n }\n });\n\n ws.on(\"close\", () => {\n allClients.delete(ws);\n session.clients.delete(onOutput);\n session.onExit.delete(onExit);\n });\n\n ws.on(\"error\", () => {\n allClients.delete(ws);\n session.clients.delete(onOutput);\n session.onExit.delete(onExit);\n });\n });\n\n server.listen(port, host, () => {\n const addr = host === \"0.0.0.0\" ? \"localhost\" : host;\n console.log(`[ccweb] Terminal server running at http://${addr}:${port}`);\n console.log(`[ccweb] Shell: ${shell}`);\n console.log(`[ccweb] Username: ${username}`);\n console.log(`[ccweb] Password: ${password}`);\n console.log(`[ccweb] Press Ctrl+C to stop`);\n\n // Start Cloudflare Tunnel if requested\n if (enableTunnel || tunnelToken) {\n console.log(\"[ccweb] Starting Cloudflare Tunnel...\");\n import(\"cloudflared\").then(({ Tunnel }) => {\n console.log(\"[ccweb] cloudflared module loaded, creating tunnel...\");\n const t = tunnelToken\n ? Tunnel.withToken(tunnelToken)\n : Tunnel.quick(`http://localhost:${port}`, { \"--no-autoupdate\": true });\n\n console.log(\"[ccweb] Tunnel process PID:\", t.process.pid);\n t.on(\"url\", (url: string) => {\n console.log(`[ccweb] Tunnel: ${url}`);\n });\n t.on(\"stdout\", (output: string) => {\n const line = output.trim();\n if (line) console.log(`[ccweb] Tunnel stdout: ${line}`);\n });\n t.on(\"stderr\", (output: string) => {\n const line = output.trim();\n if (line) console.log(`[ccweb] Tunnel stderr: ${line}`);\n });\n t.on(\"error\", (err: Error) => {\n console.error(`[ccweb] Tunnel error: ${err.message}`);\n });\n t.on(\"exit\", (code: number | null, signal: string | null) => {\n console.log(`[ccweb] Tunnel exited with code=${code} signal=${signal}`);\n });\n const stopTunnel = () => t.stop();\n process.on(\"SIGINT\", stopTunnel);\n process.on(\"SIGTERM\", stopTunnel);\n }).catch((err) => {\n console.error(\"[ccweb] Failed to start tunnel:\", err.message);\n });\n }\n\n // Prevent system sleep while ccweb is running\n const platform = os.platform();\n if (platform === \"darwin\") {\n const caffeinate = spawn(\"caffeinate\", [\"-s\", \"-w\", process.pid.toString()], {\n stdio: \"ignore\",\n });\n caffeinate.unref();\n console.log(\"[ccweb] Sleep prevention enabled (caffeinate)\");\n } else if (platform === \"win32\") {\n const script = `\nAdd-Type -TypeDefinition @\"\nusing System.Runtime.InteropServices;\npublic class Power {\n [DllImport(\"kernel32.dll\")]\n public static extern uint SetThreadExecutionState(uint esFlags);\n}\n\"@\nwhile($true) {\n [Power]::SetThreadExecutionState(0x80000001)\n Start-Sleep -Seconds 30\n}`;\n const ps = spawn(\"powershell\", [\"-NoProfile\", \"-Command\", script], {\n stdio: \"ignore\",\n });\n ps.unref();\n console.log(\"[ccweb] Sleep prevention enabled (SetThreadExecutionState)\");\n }\n });\n\n const shutdown = () => {\n console.log(\"\\n[ccweb] Shutting down...\");\n wss.clients.forEach((client) => client.close());\n sessionManager.destroyAll();\n server.close(() => process.exit(0));\n setTimeout(() => process.exit(1), 3000);\n };\n\n process.on(\"SIGINT\", shutdown);\n process.on(\"SIGTERM\", shutdown);\n}\n","import crypto from \"crypto\";\nimport { Terminal } from \"./terminal\";\n\nexport interface Session {\n id: string;\n terminal: Terminal;\n buffer: string[];\n clients: Set<(data: string) => void>;\n onExit: Set<() => void>;\n alive: boolean;\n}\n\nconst MAX_BUFFER = 5000;\n\nexport class SessionManager {\n private sessions = new Map<string, Session>();\n private order: string[] = [];\n\n create(shell: string, cols: number, rows: number): Session {\n const id = crypto.randomUUID();\n const terminal = new Terminal({ shell, cols, rows });\n\n const session: Session = {\n id,\n terminal,\n buffer: [],\n clients: new Set(),\n onExit: new Set(),\n alive: true,\n };\n\n terminal.onData((data) => {\n if (session.buffer.length >= MAX_BUFFER) {\n session.buffer.shift();\n }\n session.buffer.push(data);\n for (const cb of session.clients) {\n cb(data);\n }\n });\n\n terminal.onExit(() => {\n session.alive = false;\n for (const cb of session.onExit) {\n cb();\n }\n });\n\n this.sessions.set(id, session);\n this.order.push(id);\n return session;\n }\n\n get(id: string): Session | undefined {\n const session = this.sessions.get(id);\n if (session && !session.alive) {\n this.sessions.delete(id);\n this.order = this.order.filter((x) => x !== id);\n return undefined;\n }\n return session;\n }\n\n listIds(): string[] {\n this.order = this.order.filter((id) => {\n const s = this.sessions.get(id);\n if (s && s.alive) return true;\n this.sessions.delete(id);\n return false;\n });\n return [...this.order];\n }\n\n remove(id: string): void {\n const session = this.sessions.get(id);\n if (session) {\n session.terminal.kill();\n this.sessions.delete(id);\n }\n this.order = this.order.filter((x) => x !== id);\n }\n\n destroyAll(): void {\n for (const session of this.sessions.values()) {\n session.terminal.kill();\n }\n this.sessions.clear();\n this.order = [];\n }\n}\n","import * as pty from \"node-pty\";\nimport os from \"os\";\n\nexport interface TerminalOptions {\n shell: string;\n cols?: number;\n rows?: number;\n cwd?: string;\n}\n\nexport class Terminal {\n private process: pty.IPty;\n private disposed = false;\n\n constructor(options: TerminalOptions) {\n const { shell, cols = 80, rows = 24, cwd } = options;\n const env = Object.assign({}, process.env, {\n TERM: \"xterm-256color\",\n COLORTERM: \"truecolor\",\n FORCE_COLOR: \"3\",\n TERM_PROGRAM: \"xterm-256color\",\n });\n delete (env as Record<string, string | undefined>).NO_COLOR;\n\n this.process = pty.spawn(shell, [], {\n name: \"xterm-256color\",\n cols,\n rows,\n cwd: cwd || os.homedir(),\n env: env as Record<string, string>,\n });\n }\n\n get pid(): number {\n return this.process.pid;\n }\n\n onData(callback: (data: string) => void): void {\n this.process.onData(callback);\n }\n\n onExit(callback: (exitCode: number, signal?: number) => void): void {\n this.process.onExit(({ exitCode, signal }) => {\n callback(exitCode, signal);\n });\n }\n\n write(data: string): void {\n if (!this.disposed) {\n this.process.write(data);\n }\n }\n\n resize(cols: number, rows: number): void {\n if (!this.disposed) {\n try {\n this.process.resize(cols, rows);\n } catch {\n // PTY may already be closed\n }\n }\n }\n\n kill(): void {\n if (!this.disposed) {\n this.disposed = true;\n try {\n this.process.kill();\n } catch {\n // Already dead\n }\n }\n }\n}\n","import https from \"https\";\nimport { execSync } from \"child_process\";\n\ndeclare const __VERSION__: string;\nconst PKG_NAME = \"ccwebtty\";\n\nfunction fetchLatestVersion(): Promise<string> {\n return new Promise((resolve, reject) => {\n https\n .get(`https://registry.npmjs.org/${PKG_NAME}/latest`, (res) => {\n let data = \"\";\n res.on(\"data\", (chunk) => (data += chunk));\n res.on(\"end\", () => {\n try {\n resolve(JSON.parse(data).version);\n } catch {\n reject(new Error(\"Failed to parse registry response\"));\n }\n });\n })\n .on(\"error\", reject);\n });\n}\n\nexport async function checkUpdate(): Promise<void> {\n try {\n const latest = await fetchLatestVersion();\n if (latest !== __VERSION__) {\n console.log(\n `\\n New version available: ${__VERSION__} -> \\x1b[32m${latest}\\x1b[0m`\n );\n console.log(` Run \\x1b[36mccweb update\\x1b[0m to update\\n`);\n }\n } catch {\n // silently ignore network errors\n }\n}\n\nexport async function selfUpdate(): Promise<void> {\n try {\n const latest = await fetchLatestVersion();\n if (latest === __VERSION__) {\n console.log(`Already up to date (v${__VERSION__})`);\n return;\n }\n console.log(`Updating ${PKG_NAME}: ${__VERSION__} -> ${latest} ...`);\n execSync(`npm install -g ${PKG_NAME}@latest`, { stdio: \"inherit\" });\n console.log(`Successfully updated to v${latest}`);\n } catch (err: any) {\n console.error(`Update failed: ${err.message}`);\n process.exit(1);\n }\n}\n"],"mappings":"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","names":["import_crypto","import_crypto","import_os","os","crypto","crypto","express","http","path","session","os","import_child_process","https","crypto"]}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ccwebtty",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "1.0.5",
|
|
4
|
+
"description": "Remote-control your computer's terminal from any browser — run Claude Code, Cursor Agent, Aider and more from your phone",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"ccweb": "dist/index.js"
|
|
@@ -24,7 +24,13 @@
|
|
|
24
24
|
"web",
|
|
25
25
|
"cli",
|
|
26
26
|
"pty",
|
|
27
|
-
"ttyd"
|
|
27
|
+
"ttyd",
|
|
28
|
+
"remote-terminal",
|
|
29
|
+
"claude-code",
|
|
30
|
+
"cursor",
|
|
31
|
+
"aider",
|
|
32
|
+
"ai-coding",
|
|
33
|
+
"cloudflare-tunnel"
|
|
28
34
|
],
|
|
29
35
|
"author": "",
|
|
30
36
|
"license": "ISC",
|