ccwebtty 1.0.3 → 1.0.4

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 CHANGED
@@ -2,60 +2,138 @@
2
2
 
3
3
  A CLI tool that exposes an interactive web terminal in the browser, similar to [ttyd](https://github.com/tsl0922/ttyd).
4
4
 
5
- ## Prerequisites
5
+ 一个在浏览器中提供交互式 Web 终端的命令行工具,类似于 [ttyd](https://github.com/tsl0922/ttyd)。
6
+
7
+ - **node-pty** — 在服务端创建伪终端 / Spawns pseudo-terminals on the server
8
+ - **xterm.js** — 在浏览器中渲染终端 / Renders the terminal in the browser
9
+ - **WebSocket** — 实时双向通信 / Real-time bidirectional communication
10
+ - 每个浏览器标签页拥有独立的 Shell 会话 / Each browser tab gets its own independent shell session
11
+ - 内置用户名/密码认证 / Built-in authentication (username/password)
12
+ - 可选 [Cloudflare Tunnel](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/) 支持,无需账号即可公网访问 / Optional Cloudflare Tunnel support for public access
13
+ - 自动防止系统休眠,保持后台长时间运行 / Automatic sleep prevention to keep the server running in the background
14
+
15
+ ## 环境要求 / Prerequisites
6
16
 
7
17
  - [Node.js](https://nodejs.org/) >= 18
8
18
 
9
- ## Install
19
+ ## 安装 / Install
10
20
 
11
21
  ```bash
12
22
  npm install -g ccwebtty
13
23
  ```
14
24
 
15
- ## Usage
25
+ ## 使用 / Usage
16
26
 
17
27
  ```bash
18
- # Start with default settings (port 8080, all interfaces)
28
+ # 使用默认设置启动(端口 1989,监听所有网络接口)
29
+ # Start with default settings (port 1989, all interfaces)
19
30
  ccweb
20
31
 
32
+ # 自定义端口和主机地址
21
33
  # Custom port and host
22
34
  ccweb --port 3000 --host 127.0.0.1
23
35
 
36
+ # 指定不同的 Shell
24
37
  # Specify a different shell
25
38
  ccweb --shell /bin/bash
39
+
40
+ # 通过 Cloudflare Tunnel 暴露到公网(无需账号)
41
+ # Expose via Cloudflare Tunnel (publicly accessible, no account required)
42
+ ccweb --tunnel
43
+
44
+ # 使用自定义域名(需要 Cloudflare 账号,在 Zero Trust 后台获取 Token)
45
+ # Use a custom domain (requires Cloudflare account, get token from Zero Trust dashboard)
46
+ ccweb --tunnel-token eyJhIjoiNjM...
47
+
48
+ # 组合使用
49
+ # Combine options
50
+ ccweb --port 3000 --shell /bin/zsh --tunnel
26
51
  ```
27
52
 
28
- Then open `http://localhost:8080` in your browser.
53
+ 然后在浏览器中打开 `http://localhost:<port>`(默认:`http://localhost:1989`)。
54
+
55
+ Then open `http://localhost:<port>` in your browser (default: `http://localhost:1989`).
29
56
 
30
- ## Options
57
+ ## 参数 / Options
31
58
 
32
- | Option | Default | Description |
59
+ | 参数 Option | 默认值 Default | 说明 Description |
33
60
  |--------|---------|-------------|
34
- | `-p, --port <number>` | `8080` | Port to listen on |
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 |
61
+ | `-p, --port <number>` | `1989` | 监听端口 / Port to listen on |
62
+ | `-H, --host <address>` | `0.0.0.0` | 绑定地址 / Host to bind to |
63
+ | `-s, --shell <path>` | `$SHELL` or `/bin/bash` | 要启动的 Shell / Shell to spawn |
64
+ | `-u, --username <name>` | `cc` | 认证用户名 / Username for authentication |
65
+ | `--password <password>` | 随机生成 random | 认证密码(未设置则自动生成)/ Password for authentication |
66
+ | `--tunnel` | 禁用 disabled | 通过 Cloudflare Tunnel 暴露到公网(无需账号)/ Expose via Cloudflare Tunnel |
67
+ | `--tunnel-token <token>` | — | 使用 Cloudflare Tunnel Token 绑定自定义域名 / Use a Cloudflare Tunnel token for custom domain |
68
+
69
+ ## 命令 / Commands
39
70
 
40
- ## Development
71
+ | 命令 Command | 说明 Description |
72
+ |---------|-------------|
73
+ | `ccweb start` | 启动 Web 终端服务(默认命令)/ Start the web terminal server (default) |
74
+ | `ccweb update` | 更新 ccweb 到最新版本 / Update ccweb to the latest version |
75
+
76
+ ## 自定义域名(Cloudflare Tunnel Token)/ Custom Domain
77
+
78
+ 除了使用 `--tunnel` 获取随机域名外,你还可以通过 Cloudflare Tunnel Token 绑定自己的域名。
79
+
80
+ In addition to using `--tunnel` for a random domain, you can bind your own domain via a Cloudflare Tunnel Token.
81
+
82
+ ### 获取 Token / How to get a Token
83
+
84
+ 1. 登录 [Cloudflare Zero Trust](https://one.dash.cloudflare.com/) 后台 / Log in to the Cloudflare Zero Trust dashboard
85
+ 2. 进入 **Networks → Tunnels**,点击 **Create a tunnel** / Go to **Networks → Tunnels**, click **Create a tunnel**
86
+ 3. 选择 **Cloudflared** 类型,为隧道命名(如 `ccweb`)/ Select **Cloudflared**, name your tunnel (e.g. `ccweb`)
87
+ 4. 在安装页面复制 Token(`eyJ` 开头的字符串)/ Copy the Token from the install page (starts with `eyJ`)
88
+ 5. 配置 **Public Hostname** / Configure **Public Hostname**:
89
+ - **Subdomain**: 填入子域名,如 `terminal` / Enter a subdomain, e.g. `terminal`
90
+ - **Domain**: 选择你在 Cloudflare 的域名 / Select your domain in Cloudflare
91
+ - **Service Type**: `HTTP`
92
+ - **URL**: `localhost:1989`(与 ccweb 端口一致)/ (must match ccweb port)
93
+
94
+ ### 使用 / Usage
41
95
 
42
96
  ```bash
43
- # Install dependencies
97
+ ccweb --tunnel-token eyJhIjoiNjM...
98
+ ```
99
+
100
+ 配置完成后,即可通过自定义域名(如 `https://terminal.example.com`)访问终端。
101
+
102
+ Once configured, you can access the terminal via your custom domain (e.g. `https://terminal.example.com`).
103
+
104
+ ### 对比 / Comparison
105
+
106
+ | | `--tunnel` | `--tunnel-token <token>` |
107
+ |---|---|---|
108
+ | 域名 Domain | 随机 `*.trycloudflare.com` / Random | 自定义域名 / Custom domain |
109
+ | 需要账号 Account | 不需要 / No | 需要 Cloudflare 账号 / Yes |
110
+ | 持久性 Persistence | 每次启动域名不同 / Changes on restart | 固定域名 / Persistent |
111
+ | 适用场景 Use case | 临时分享 / Temporary sharing | 长期使用 / Long-term use |
112
+
113
+ ## 防止休眠 / Sleep Prevention
114
+
115
+ ccweb 启动后会自动阻止系统进入休眠状态,确保终端服务在后台持续运行。
116
+
117
+ When ccweb starts, it automatically prevents the system from sleeping, ensuring the terminal server keeps running in the background.
118
+
119
+ | 平台 Platform | 实现方式 Implementation | 条件 Requirement |
120
+ |---------|---------|---------|
121
+ | macOS | `caffeinate -s -w <pid>` | 系统自带,无需额外安装 / Built-in, no extra install needed |
122
+ | Windows | PowerShell `SetThreadExecutionState` | 系统自带,无需额外安装 / Built-in, no extra install needed |
123
+ | Linux | 不支持 / Not supported | — |
124
+
125
+ ## 开发 / Development
126
+
127
+ ```bash
128
+ # 安装依赖 / Install dependencies
44
129
  npm install
45
130
 
46
- # Build
131
+ # 构建 / Build
47
132
  npm run build
48
133
 
49
- # Run
134
+ # 运行 / Run
50
135
  npm start
51
136
 
52
- # Watch mode
137
+ # 监听模式 / Watch mode
53
138
  npm run dev
54
139
  ```
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
- return res.status(401).send("Invalid credentials");
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" : ""}`;
@@ -374,15 +394,29 @@ function startServer(options) {
374
394
  console.log(`[ccweb] Username: ${username}`);
375
395
  console.log(`[ccweb] Password: ${password}`);
376
396
  console.log(`[ccweb] Press Ctrl+C to stop`);
377
- if (enableTunnel) {
397
+ if (enableTunnel || tunnelToken) {
398
+ console.log("[ccweb] Starting Cloudflare Tunnel...");
378
399
  import("cloudflared").then(({ Tunnel }) => {
379
- const t = new Tunnel(["tunnel", "--url", `http://localhost:${port}`, "--no-autoupdate"]);
400
+ console.log("[ccweb] cloudflared module loaded, creating tunnel...");
401
+ const t = tunnelToken ? Tunnel.withToken(tunnelToken) : Tunnel.quick(`http://localhost:${port}`, { "--no-autoupdate": true });
402
+ console.log("[ccweb] Tunnel process PID:", t.process.pid);
380
403
  t.on("url", (url) => {
381
404
  console.log(`[ccweb] Tunnel: ${url}`);
382
405
  });
406
+ t.on("stdout", (output) => {
407
+ const line = output.trim();
408
+ if (line) console.log(`[ccweb] Tunnel stdout: ${line}`);
409
+ });
410
+ t.on("stderr", (output) => {
411
+ const line = output.trim();
412
+ if (line) console.log(`[ccweb] Tunnel stderr: ${line}`);
413
+ });
383
414
  t.on("error", (err) => {
384
415
  console.error(`[ccweb] Tunnel error: ${err.message}`);
385
416
  });
417
+ t.on("exit", (code, signal) => {
418
+ console.log(`[ccweb] Tunnel exited with code=${code} signal=${signal}`);
419
+ });
386
420
  const stopTunnel = () => t.stop();
387
421
  process.on("SIGINT", stopTunnel);
388
422
  process.on("SIGTERM", stopTunnel);
@@ -450,10 +484,10 @@ function fetchLatestVersion() {
450
484
  async function checkUpdate() {
451
485
  try {
452
486
  const latest = await fetchLatestVersion();
453
- if (latest !== "1.0.3") {
487
+ if (latest !== "1.0.4") {
454
488
  console.log(
455
489
  `
456
- New version available: ${"1.0.3"} -> \x1B[32m${latest}\x1B[0m`
490
+ New version available: ${"1.0.4"} -> \x1B[32m${latest}\x1B[0m`
457
491
  );
458
492
  console.log(` Run \x1B[36mccweb update\x1B[0m to update
459
493
  `);
@@ -464,11 +498,11 @@ async function checkUpdate() {
464
498
  async function selfUpdate() {
465
499
  try {
466
500
  const latest = await fetchLatestVersion();
467
- if (latest === "1.0.3") {
468
- console.log(`Already up to date (v${"1.0.3"})`);
501
+ if (latest === "1.0.4") {
502
+ console.log(`Already up to date (v${"1.0.4"})`);
469
503
  return;
470
504
  }
471
- console.log(`Updating ${PKG_NAME}: ${"1.0.3"} -> ${latest} ...`);
505
+ console.log(`Updating ${PKG_NAME}: ${"1.0.4"} -> ${latest} ...`);
472
506
  (0, import_child_process2.execSync)(`npm install -g ${PKG_NAME}@latest`, { stdio: "inherit" });
473
507
  console.log(`Successfully updated to v${latest}`);
474
508
  } catch (err) {
@@ -479,15 +513,15 @@ async function selfUpdate() {
479
513
 
480
514
  // src/index.ts
481
515
  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.3");
516
+ program.name("ccweb").description("A CLI tool that exposes an interactive web terminal in the browser").version("1.0.4");
483
517
  program.command("update").description("Update ccweb to the latest version").action(async () => {
484
518
  await selfUpdate();
485
519
  });
486
- program.command("start", { isDefault: true }).description("Start the web terminal server").option("-p, --port <number>", "port to listen on", "8080").option("-H, --host <address>", "host to bind to", "0.0.0.0").option(
520
+ 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
521
  "-s, --shell <path>",
488
522
  "shell to spawn",
489
523
  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) => {
524
+ ).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
525
  const port = parseInt(opts.port, 10);
492
526
  if (isNaN(port) || port < 1 || port > 65535) {
493
527
  console.error(`Invalid port: ${opts.port}`);
@@ -501,7 +535,8 @@ program.command("start", { isDefault: true }).description("Start the web termina
501
535
  shell: opts.shell,
502
536
  username: opts.username,
503
537
  password,
504
- tunnel: !!opts.tunnel
538
+ tunnel: !!opts.tunnel,
539
+ tunnelToken: opts.tunnelToken
505
540
  });
506
541
  });
507
542
  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 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":";;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,IAAAA,iBAAmB;AACnB,uBAAwB;;;ACDxB,IAAAC,iBAAmB;AACnB,2BAAsB;AACtB,qBAAoB;AACpB,kBAAiB;AACjB,IAAAC,aAAe;AACf,kBAAiB;AACjB,gBAA2C;;;ACN3C,oBAAmB;;;ACAnB,UAAqB;AACrB,gBAAe;AASR,IAAM,WAAN,MAAe;AAAA,EAIpB,YAAY,SAA0B;AAFtC,SAAQ,WAAW;AAGjB,UAAM,EAAE,OAAO,OAAO,IAAI,OAAO,IAAI,IAAI,IAAI;AAC7C,UAAM,MAAM,OAAO,OAAO,CAAC,GAAG,QAAQ,KAAK;AAAA,MACzC,MAAM;AAAA,MACN,WAAW;AAAA,MACX,aAAa;AAAA,MACb,cAAc;AAAA,IAChB,CAAC;AACD,WAAQ,IAA2C;AAEnD,SAAK,UAAc,UAAM,OAAO,CAAC,GAAG;AAAA,MAClC,MAAM;AAAA,MACN;AAAA,MACA;AAAA,MACA,KAAK,OAAO,UAAAC,QAAG,QAAQ;AAAA,MACvB;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEA,IAAI,MAAc;AAChB,WAAO,KAAK,QAAQ;AAAA,EACtB;AAAA,EAEA,OAAO,UAAwC;AAC7C,SAAK,QAAQ,OAAO,QAAQ;AAAA,EAC9B;AAAA,EAEA,OAAO,UAA6D;AAClE,SAAK,QAAQ,OAAO,CAAC,EAAE,UAAU,OAAO,MAAM;AAC5C,eAAS,UAAU,MAAM;AAAA,IAC3B,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,MAAoB;AACxB,QAAI,CAAC,KAAK,UAAU;AAClB,WAAK,QAAQ,MAAM,IAAI;AAAA,IACzB;AAAA,EACF;AAAA,EAEA,OAAO,MAAc,MAAoB;AACvC,QAAI,CAAC,KAAK,UAAU;AAClB,UAAI;AACF,aAAK,QAAQ,OAAO,MAAM,IAAI;AAAA,MAChC,QAAQ;AAAA,MAER;AAAA,IACF;AAAA,EACF;AAAA,EAEA,OAAa;AACX,QAAI,CAAC,KAAK,UAAU;AAClB,WAAK,WAAW;AAChB,UAAI;AACF,aAAK,QAAQ,KAAK;AAAA,MACpB,QAAQ;AAAA,MAER;AAAA,IACF;AAAA,EACF;AACF;;;AD7DA,IAAM,aAAa;AAEZ,IAAM,iBAAN,MAAqB;AAAA,EAArB;AACL,SAAQ,WAAW,oBAAI,IAAqB;AAC5C,SAAQ,QAAkB,CAAC;AAAA;AAAA,EAE3B,OAAO,OAAe,MAAc,MAAuB;AACzD,UAAM,KAAK,cAAAC,QAAO,WAAW;AAC7B,UAAM,WAAW,IAAI,SAAS,EAAE,OAAO,MAAM,KAAK,CAAC;AAEnD,UAAM,UAAmB;AAAA,MACvB;AAAA,MACA;AAAA,MACA,QAAQ,CAAC;AAAA,MACT,SAAS,oBAAI,IAAI;AAAA,MACjB,QAAQ,oBAAI,IAAI;AAAA,MAChB,OAAO;AAAA,IACT;AAEA,aAAS,OAAO,CAAC,SAAS;AACxB,UAAI,QAAQ,OAAO,UAAU,YAAY;AACvC,gBAAQ,OAAO,MAAM;AAAA,MACvB;AACA,cAAQ,OAAO,KAAK,IAAI;AACxB,iBAAW,MAAM,QAAQ,SAAS;AAChC,WAAG,IAAI;AAAA,MACT;AAAA,IACF,CAAC;AAED,aAAS,OAAO,MAAM;AACpB,cAAQ,QAAQ;AAChB,iBAAW,MAAM,QAAQ,QAAQ;AAC/B,WAAG;AAAA,MACL;AAAA,IACF,CAAC;AAED,SAAK,SAAS,IAAI,IAAI,OAAO;AAC7B,SAAK,MAAM,KAAK,EAAE;AAClB,WAAO;AAAA,EACT;AAAA,EAEA,IAAI,IAAiC;AACnC,UAAM,UAAU,KAAK,SAAS,IAAI,EAAE;AACpC,QAAI,WAAW,CAAC,QAAQ,OAAO;AAC7B,WAAK,SAAS,OAAO,EAAE;AACvB,WAAK,QAAQ,KAAK,MAAM,OAAO,CAAC,MAAM,MAAM,EAAE;AAC9C,aAAO;AAAA,IACT;AACA,WAAO;AAAA,EACT;AAAA,EAEA,UAAoB;AAClB,SAAK,QAAQ,KAAK,MAAM,OAAO,CAAC,OAAO;AACrC,YAAM,IAAI,KAAK,SAAS,IAAI,EAAE;AAC9B,UAAI,KAAK,EAAE,MAAO,QAAO;AACzB,WAAK,SAAS,OAAO,EAAE;AACvB,aAAO;AAAA,IACT,CAAC;AACD,WAAO,CAAC,GAAG,KAAK,KAAK;AAAA,EACvB;AAAA,EAEA,OAAO,IAAkB;AACvB,UAAM,UAAU,KAAK,SAAS,IAAI,EAAE;AACpC,QAAI,SAAS;AACX,cAAQ,SAAS,KAAK;AACtB,WAAK,SAAS,OAAO,EAAE;AAAA,IACzB;AACA,SAAK,QAAQ,KAAK,MAAM,OAAO,CAAC,MAAM,MAAM,EAAE;AAAA,EAChD;AAAA,EAEA,aAAmB;AACjB,eAAW,WAAW,KAAK,SAAS,OAAO,GAAG;AAC5C,cAAQ,SAAS,KAAK;AAAA,IACxB;AACA,SAAK,SAAS,MAAM;AACpB,SAAK,QAAQ,CAAC;AAAA,EAChB;AACF;;;AD5DA,SAAS,iBAAyB;AAChC,SAAO,eAAAC,QAAO,YAAY,EAAE,EAAE,SAAS,KAAK;AAC9C;AAEA,SAAS,UAAU,SAAiB,QAAwB;AAC1D,QAAM,OAAO,eAAAA,QAAO,WAAW,UAAU,MAAM;AAC/C,OAAK,OAAO,OAAO;AACnB,SAAO,UAAU,MAAM,KAAK,OAAO,KAAK;AAC1C;AAEA,SAAS,YAAY,OAAe,QAAyB;AAC3D,QAAM,SAAS,MAAM,YAAY,GAAG;AACpC,MAAI,WAAW,GAAI,QAAO;AAC1B,QAAM,UAAU,MAAM,UAAU,GAAG,MAAM;AACzC,QAAM,MAAM,MAAM,UAAU,SAAS,CAAC;AACtC,QAAM,OAAO,eAAAA,QAAO,WAAW,UAAU,MAAM;AAC/C,OAAK,OAAO,OAAO;AACnB,QAAM,WAAW,KAAK,OAAO,KAAK;AAClC,MAAI,IAAI,WAAW,SAAS,OAAQ,QAAO;AAC3C,SAAO,eAAAA,QAAO,gBAAgB,OAAO,KAAK,GAAG,GAAG,OAAO,KAAK,QAAQ,CAAC;AACvE;AAEA,SAAS,aAAa,QAAoD;AACxE,QAAM,UAAkC,CAAC;AACzC,MAAI,CAAC,OAAQ,QAAO;AACpB,aAAW,QAAQ,OAAO,MAAM,GAAG,GAAG;AACpC,UAAM,KAAK,KAAK,QAAQ,GAAG;AAC3B,QAAI,OAAO,GAAI;AACf,UAAM,MAAM,KAAK,UAAU,GAAG,EAAE,EAAE,KAAK;AACvC,UAAM,MAAM,KAAK,UAAU,KAAK,CAAC,EAAE,KAAK;AACxC,YAAQ,GAAG,IAAI,mBAAmB,GAAG;AAAA,EACvC;AACA,SAAO;AACT;AAEO,SAAS,YAAY,SAA8B;AACxD,QAAM,EAAE,MAAM,MAAM,OAAO,UAAU,UAAU,QAAQ,cAAc,YAAY,IAAI;AAErF,QAAM,iBAAiB,IAAI,eAAe;AAC1C,QAAM,aAAa,eAAe;AAElC,QAAM,UAAM,eAAAC,SAAQ;AACpB,MAAI,IAAI,eAAe,IAAI;AAC3B,QAAM,SAAS,YAAAC,QAAK,aAAa,GAAG;AAGpC,QAAM,aAAa,oBAAI,IAAe;AAEtC,WAAS,gBAAsB;AAC7B,UAAM,OAAO,eAAe,QAAQ;AACpC,UAAM,MAAM,KAAK,UAAU,EAAE,MAAM,QAAQ,KAAK,CAAC;AACjD,eAAW,UAAU,YAAY;AAC/B,UAAI,OAAO,eAAe,oBAAU,MAAM;AACxC,eAAO,KAAK,GAAG;AAAA,MACjB;AAAA,IACF;AAAA,EACF;AAEA,WAAS,gBAAgB,cAA2C;AAClE,UAAM,UAAU,aAAa,YAAY;AACzC,UAAM,QAAQ,QAAQ,aAAa;AACnC,QAAI,CAAC,MAAO,QAAO;AACnB,WAAO,YAAY,OAAO,UAAU;AAAA,EACtC;AAGA,QAAM,qBAAqB;AAC3B,QAAM,mBAAmB,KAAK,KAAK;AACnC,QAAM,gBAAgB,oBAAI,IAAoD;AAE9E,WAAS,YAAY,KAA8B;AACjD,WAAQ,IAAI,MAAM,IAAI,OAAO,iBAAiB;AAAA,EAChD;AAGA,MAAI,IAAI,eAAAD,QAAQ,WAAW,EAAE,UAAU,MAAM,CAAC,CAAC;AAE/C,MAAI,KAAK,UAAU,CAAC,KAAK,QAAQ;AAC/B,UAAM,KAAK,YAAY,GAAG;AAC1B,UAAM,SAAS,cAAc,IAAI,EAAE;AAEnC,QAAI,UAAU,OAAO,cAAc,KAAK,IAAI,GAAG;AAC7C,YAAM,YAAY,KAAK,MAAM,OAAO,cAAc,KAAK,IAAI,KAAK,GAAK;AACrE,aAAO,IAAI,OAAO,GAAG,EAAE,KAAK,0CAA0C,SAAS,aAAa;AAAA,IAC9F;AAEA,UAAM,EAAE,UAAU,GAAG,UAAU,EAAE,IAAI,IAAI;AACzC,QAAI,CAAC,KAAK,CAAC,GAAG;AACZ,aAAO,IAAI,OAAO,GAAG,EAAE,KAAK,qBAAqB;AAAA,IACnD;AACA,UAAM,SAAS,EAAE,WAAW,SAAS,UAAU,eAAAD,QAAO,gBAAgB,OAAO,KAAK,CAAC,GAAG,OAAO,KAAK,QAAQ,CAAC;AAC3G,UAAM,SAAS,EAAE,WAAW,SAAS,UAAU,eAAAA,QAAO,gBAAgB,OAAO,KAAK,CAAC,GAAG,OAAO,KAAK,QAAQ,CAAC;AAC3G,QAAI,CAAC,UAAU,CAAC,QAAQ;AACtB,YAAM,YAAY,QAAQ,SAAS,KAAK;AACxC,UAAI,YAAY,oBAAoB;AAClC,sBAAc,IAAI,IAAI,EAAE,OAAO,UAAU,aAAa,KAAK,IAAI,IAAI,iBAAiB,CAAC;AACrF,gBAAQ,IAAI,cAAc,EAAE,oCAAoC,QAAQ,kBAAkB;AAC1F,eAAO,IAAI,OAAO,GAAG,EAAE,KAAK,sDAAsD;AAAA,MACpF;AACA,oBAAc,IAAI,IAAI,EAAE,OAAO,UAAU,aAAa,EAAE,CAAC;AACzD,aAAO,IAAI,OAAO,GAAG,EAAE,KAAK,wBAAwB,qBAAqB,QAAQ,wBAAwB;AAAA,IAC3G;AAGA,kBAAc,OAAO,EAAE;AAEvB,UAAM,QAAQ,UAAU,QAAQ,UAAU;AAC1C,UAAM,SAAS,IAAI,aAAa,WAAW,IAAI,QAAQ,mBAAmB,MAAM;AAChF,UAAM,cAAc,iCAAiC,SAAS,aAAa,EAAE;AAC7E,QAAI,UAAU,cAAc,eAAe,mBAAmB,KAAK,CAAC,KAAK,WAAW,EAAE;AACtF,QAAI,SAAS,GAAG;AAAA,EAClB,CAAC;AAGD,MAAI,IAAI,CAAC,KAAK,KAAK,SAAS;AAC1B,QAAI,gBAAgB,IAAI,QAAQ,MAAM,GAAG;AACvC,aAAO,KAAK;AAAA,IACd;AAEA,QAAI,IAAI,WAAW,OAAO;AACxB,aAAO,IAAI,SAAS,YAAAG,QAAK,KAAK,WAAW,UAAU,YAAY,GAAG,EAAE,UAAU,QAAQ,CAAC;AAAA,IACzF;AACA,QAAI,OAAO,GAAG,EAAE,KAAK,cAAc;AAAA,EACrC,CAAC;AAED,MAAI,IAAI,eAAAF,QAAQ,OAAO,YAAAE,QAAK,KAAK,WAAW,QAAQ,GAAG,EAAE,UAAU,QAAQ,CAAC,CAAC;AAE7E,MAAI,IAAI,KAAK,CAAC,MAAM,QAAQ;AAC1B,QAAI,SAAS,YAAAA,QAAK,KAAK,WAAW,UAAU,YAAY,GAAG,EAAE,UAAU,QAAQ,CAAC;AAAA,EAClF,CAAC;AAED,QAAM,MAAM,IAAI,0BAAgB,EAAE,UAAU,KAAK,CAAC;AAGlD,QAAM,mBAAmB;AACzB,QAAM,eAAe,YAAY,MAAM;AACrC,eAAW,UAAU,IAAI,SAAS;AAChC,UAAI,OAAO,eAAe,oBAAU,MAAM;AACxC,eAAO,KAAK;AAAA,MACd;AAAA,IACF;AAAA,EACF,GAAG,gBAAgB;AACnB,MAAI,GAAG,SAAS,MAAM,cAAc,YAAY,CAAC;AAEjD,SAAO,GAAG,WAAW,CAAC,KAAK,QAAQ,SAAS;AAC1C,QAAI,CAAC,gBAAgB,IAAI,QAAQ,MAAM,GAAG;AACxC,aAAO,MAAM,mCAAmC;AAChD,aAAO,QAAQ;AACf;AAAA,IACF;AACA,QAAI,cAAc,KAAK,QAAQ,MAAM,CAAC,OAAO;AAC3C,UAAI,KAAK,cAAc,IAAI,GAAG;AAAA,IAChC,CAAC;AAAA,EACH,CAAC;AAED,MAAI,GAAG,cAAc,CAAC,IAAe,QAAQ;AAC3C,UAAM,MAAM,IAAI,IAAI,IAAI,OAAO,KAAK,UAAU,IAAI,QAAQ,IAAI,EAAE;AAChE,UAAM,YAAY,IAAI,aAAa,IAAI,WAAW;AAClD,UAAM,OAAO,SAAS,IAAI,aAAa,IAAI,MAAM,KAAK,MAAM,EAAE;AAC9D,UAAM,OAAO,SAAS,IAAI,aAAa,IAAI,MAAM,KAAK,MAAM,EAAE;AAE9D,eAAW,IAAI,EAAE;AAGjB,QAAI,CAAC,WAAW;AAEd,UAAI,eAAe,QAAQ,EAAE,WAAW,GAAG;AACzC,cAAMC,WAAU,eAAe,OAAO,OAAO,MAAM,IAAI;AACvD,gBAAQ,IAAI,4BAA4BA,SAAQ,EAAE,EAAE;AAAA,MACtD;AACA,SAAG,KAAK,KAAK,UAAU,EAAE,MAAM,QAAQ,MAAM,eAAe,QAAQ,EAAE,CAAC,CAAC;AAExE,SAAG,GAAG,WAAW,CAAC,QAAgB;AAChC,YAAI;AACF,gBAAM,MAAqB,KAAK,MAAM,IAAI,SAAS,CAAC;AACpD,cAAI,IAAI,SAAS,UAAU;AACzB,kBAAM,IAAI,IAAI,QAAQ;AACtB,kBAAM,IAAI,IAAI,QAAQ;AACtB,kBAAMA,WAAU,eAAe,OAAO,OAAO,GAAG,CAAC;AACjD,oBAAQ,IAAI,wBAAwBA,SAAQ,EAAE,EAAE;AAChD,0BAAc;AAAA,UAChB,WAAW,IAAI,SAAS,UAAU,IAAI,WAAW;AAC/C,oBAAQ,IAAI,2BAA2B,IAAI,SAAS,EAAE;AACtD,2BAAe,OAAO,IAAI,SAAS;AACnC,0BAAc;AAAA,UAChB;AAAA,QACF,QAAQ;AAAA,QAER;AAAA,MACF,CAAC;AAED,SAAG,GAAG,SAAS,MAAM;AACnB,mBAAW,OAAO,EAAE;AAAA,MACtB,CAAC;AACD,SAAG,GAAG,SAAS,MAAM;AACnB,mBAAW,OAAO,EAAE;AAAA,MACtB,CAAC;AACD;AAAA,IACF;AAGA,UAAM,UAAU,eAAe,IAAI,SAAS;AAC5C,QAAI,CAAC,SAAS;AACZ,SAAG,KAAK,KAAK,UAAU,EAAE,MAAM,SAAS,MAAM,oBAAoB,CAAC,CAAC;AACpE,SAAG,MAAM;AACT,iBAAW,OAAO,EAAE;AACpB;AAAA,IACF;AAEA,YAAQ,IAAI,uCAAuC,SAAS,EAAE;AAG9D,eAAW,SAAS,QAAQ,QAAQ;AAClC,SAAG,KAAK,KAAK,UAAU,EAAE,MAAM,UAAU,MAAM,MAAM,CAAC,CAAC;AAAA,IACzD;AAGA,UAAM,WAAW,CAAC,SAAiB;AACjC,UAAI,GAAG,eAAe,oBAAU,MAAM;AACpC,WAAG,KAAK,KAAK,UAAU,EAAE,MAAM,UAAU,KAAK,CAAC,CAAC;AAAA,MAClD;AAAA,IACF;AACA,YAAQ,QAAQ,IAAI,QAAQ;AAE5B,UAAM,SAAS,MAAM;AACnB,cAAQ,IAAI,0BAA0B,SAAS,EAAE;AACjD,UAAI,GAAG,eAAe,oBAAU,MAAM;AACpC,WAAG,KAAK,KAAK,UAAU,EAAE,MAAM,OAAO,CAAC,CAAC;AACxC,WAAG,MAAM;AAAA,MACX;AACA,qBAAe,OAAO,SAAS;AAC/B,oBAAc;AAAA,IAChB;AACA,YAAQ,OAAO,IAAI,MAAM;AAGzB,YAAQ,SAAS,OAAO,MAAM,IAAI;AAElC,OAAG,GAAG,WAAW,CAAC,QAAgB;AAChC,UAAI;AACF,cAAM,MAAqB,KAAK,MAAM,IAAI,SAAS,CAAC;AACpD,gBAAQ,IAAI,MAAM;AAAA,UAChB,KAAK;AACH,gBAAI,IAAI,MAAM;AACZ,sBAAQ,SAAS,MAAM,IAAI,IAAI;AAAA,YACjC;AACA;AAAA,UACF,KAAK;AACH,gBAAI,IAAI,QAAQ,IAAI,MAAM;AACxB,sBAAQ,SAAS,OAAO,IAAI,MAAM,IAAI,IAAI;AAAA,YAC5C;AACA;AAAA,QACJ;AAAA,MACF,QAAQ;AAAA,MAER;AAAA,IACF,CAAC;AAED,OAAG,GAAG,SAAS,MAAM;AACnB,iBAAW,OAAO,EAAE;AACpB,cAAQ,QAAQ,OAAO,QAAQ;AAC/B,cAAQ,OAAO,OAAO,MAAM;AAAA,IAC9B,CAAC;AAED,OAAG,GAAG,SAAS,MAAM;AACnB,iBAAW,OAAO,EAAE;AACpB,cAAQ,QAAQ,OAAO,QAAQ;AAC/B,cAAQ,OAAO,OAAO,MAAM;AAAA,IAC9B,CAAC;AAAA,EACH,CAAC;AAED,SAAO,OAAO,MAAM,MAAM,MAAM;AAC9B,UAAM,OAAO,SAAS,YAAY,cAAc;AAChD,YAAQ,IAAI,6CAA6C,IAAI,IAAI,IAAI,EAAE;AACvE,YAAQ,IAAI,kBAAkB,KAAK,EAAE;AACrC,YAAQ,IAAI,qBAAqB,QAAQ,EAAE;AAC3C,YAAQ,IAAI,qBAAqB,QAAQ,EAAE;AAC3C,YAAQ,IAAI,8BAA8B;AAG1C,QAAI,gBAAgB,aAAa;AAC/B,cAAQ,IAAI,uCAAuC;AACnD,aAAO,aAAa,EAAE,KAAK,CAAC,EAAE,OAAO,MAAM;AACzC,gBAAQ,IAAI,uDAAuD;AACnE,cAAM,IAAI,cACN,OAAO,UAAU,WAAW,IAC5B,OAAO,MAAM,oBAAoB,IAAI,IAAI,EAAE,mBAAmB,KAAK,CAAC;AAExE,gBAAQ,IAAI,+BAA+B,EAAE,QAAQ,GAAG;AACxD,UAAE,GAAG,OAAO,CAAC,QAAgB;AAC3B,kBAAQ,IAAI,mBAAmB,GAAG,EAAE;AAAA,QACtC,CAAC;AACD,UAAE,GAAG,UAAU,CAAC,WAAmB;AACjC,gBAAM,OAAO,OAAO,KAAK;AACzB,cAAI,KAAM,SAAQ,IAAI,0BAA0B,IAAI,EAAE;AAAA,QACxD,CAAC;AACD,UAAE,GAAG,UAAU,CAAC,WAAmB;AACjC,gBAAM,OAAO,OAAO,KAAK;AACzB,cAAI,KAAM,SAAQ,IAAI,0BAA0B,IAAI,EAAE;AAAA,QACxD,CAAC;AACD,UAAE,GAAG,SAAS,CAAC,QAAe;AAC5B,kBAAQ,MAAM,yBAAyB,IAAI,OAAO,EAAE;AAAA,QACtD,CAAC;AACD,UAAE,GAAG,QAAQ,CAAC,MAAqB,WAA0B;AAC3D,kBAAQ,IAAI,mCAAmC,IAAI,WAAW,MAAM,EAAE;AAAA,QACxE,CAAC;AACD,cAAM,aAAa,MAAM,EAAE,KAAK;AAChC,gBAAQ,GAAG,UAAU,UAAU;AAC/B,gBAAQ,GAAG,WAAW,UAAU;AAAA,MAClC,CAAC,EAAE,MAAM,CAAC,QAAQ;AAChB,gBAAQ,MAAM,mCAAmC,IAAI,OAAO;AAAA,MAC9D,CAAC;AAAA,IACH;AAGA,UAAM,WAAW,WAAAC,QAAG,SAAS;AAC7B,QAAI,aAAa,UAAU;AACzB,YAAM,iBAAa,4BAAM,cAAc,CAAC,MAAM,MAAM,QAAQ,IAAI,SAAS,CAAC,GAAG;AAAA,QAC3E,OAAO;AAAA,MACT,CAAC;AACD,iBAAW,MAAM;AACjB,cAAQ,IAAI,+CAA+C;AAAA,IAC7D,WAAW,aAAa,SAAS;AAC/B,YAAM,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAYf,YAAM,SAAK,4BAAM,cAAc,CAAC,cAAc,YAAY,MAAM,GAAG;AAAA,QACjE,OAAO;AAAA,MACT,CAAC;AACD,SAAG,MAAM;AACT,cAAQ,IAAI,4DAA4D;AAAA,IAC1E;AAAA,EACF,CAAC;AAED,QAAM,WAAW,MAAM;AACrB,YAAQ,IAAI,4BAA4B;AACxC,QAAI,QAAQ,QAAQ,CAAC,WAAW,OAAO,MAAM,CAAC;AAC9C,mBAAe,WAAW;AAC1B,WAAO,MAAM,MAAM,QAAQ,KAAK,CAAC,CAAC;AAClC,eAAW,MAAM,QAAQ,KAAK,CAAC,GAAG,GAAI;AAAA,EACxC;AAEA,UAAQ,GAAG,UAAU,QAAQ;AAC7B,UAAQ,GAAG,WAAW,QAAQ;AAChC;;;AG9XA,mBAAkB;AAClB,IAAAC,wBAAyB;AAGzB,IAAM,WAAW;AAEjB,SAAS,qBAAsC;AAC7C,SAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,iBAAAC,QACG,IAAI,8BAA8B,QAAQ,WAAW,CAAC,QAAQ;AAC7D,UAAI,OAAO;AACX,UAAI,GAAG,QAAQ,CAAC,UAAW,QAAQ,KAAM;AACzC,UAAI,GAAG,OAAO,MAAM;AAClB,YAAI;AACF,kBAAQ,KAAK,MAAM,IAAI,EAAE,OAAO;AAAA,QAClC,QAAQ;AACN,iBAAO,IAAI,MAAM,mCAAmC,CAAC;AAAA,QACvD;AAAA,MACF,CAAC;AAAA,IACH,CAAC,EACA,GAAG,SAAS,MAAM;AAAA,EACvB,CAAC;AACH;AAEA,eAAsB,cAA6B;AACjD,MAAI;AACF,UAAM,SAAS,MAAM,mBAAmB;AACxC,QAAI,WAAW,SAAa;AAC1B,cAAQ;AAAA,QACN;AAAA,2BAA8B,OAAW,eAAe,MAAM;AAAA,MAChE;AACA,cAAQ,IAAI;AAAA,CAA+C;AAAA,IAC7D;AAAA,EACF,QAAQ;AAAA,EAER;AACF;AAEA,eAAsB,aAA4B;AAChD,MAAI;AACF,UAAM,SAAS,MAAM,mBAAmB;AACxC,QAAI,WAAW,SAAa;AAC1B,cAAQ,IAAI,wBAAwB,OAAW,GAAG;AAClD;AAAA,IACF;AACA,YAAQ,IAAI,YAAY,QAAQ,KAAK,OAAW,OAAO,MAAM,MAAM;AACnE,wCAAS,kBAAkB,QAAQ,WAAW,EAAE,OAAO,UAAU,CAAC;AAClE,YAAQ,IAAI,4BAA4B,MAAM,EAAE;AAAA,EAClD,SAAS,KAAU;AACjB,YAAQ,MAAM,kBAAkB,IAAI,OAAO,EAAE;AAC7C,YAAQ,KAAK,CAAC;AAAA,EAChB;AACF;;;AJ7CA,IAAM,UAAU,IAAI,yBAAQ;AAE5B,QACG,KAAK,OAAO,EACZ,YAAY,oEAAoE,EAChF,QAAQ,OAAW;AAEtB,QACG,QAAQ,QAAQ,EAChB,YAAY,oCAAoC,EAChD,OAAO,YAAY;AAClB,QAAM,WAAW;AACnB,CAAC;AAEH,QACG,QAAQ,SAAS,EAAE,WAAW,KAAK,CAAC,EACpC,YAAY,+BAA+B,EAC3C,OAAO,uBAAuB,qBAAqB,MAAM,EACzD,OAAO,wBAAwB,mBAAmB,SAAS,EAC3D;AAAA,EACC;AAAA,EACA;AAAA,EACA,QAAQ,IAAI,SAAS;AACvB,EACC,OAAO,yBAAyB,+BAA+B,IAAI,EACnE,OAAO,yBAAyB,iDAAiD,EACjF,OAAO,YAAY,oDAAoD,EACvE,OAAO,0BAA0B,6EAA6E,EAC9G,OAAO,OAAO,SAAS;AACtB,QAAM,OAAO,SAAS,KAAK,MAAM,EAAE;AACnC,MAAI,MAAM,IAAI,KAAK,OAAO,KAAK,OAAO,OAAO;AAC3C,YAAQ,MAAM,iBAAiB,KAAK,IAAI,EAAE;AAC1C,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,QAAM,WAAW,KAAK,YAAY,eAAAC,QAAO,YAAY,EAAE,EAAE,SAAS,WAAW;AAG7E,cAAY;AAEZ,cAAY;AAAA,IACV;AAAA,IACA,MAAM,KAAK;AAAA,IACX,OAAO,KAAK;AAAA,IACZ,UAAU,KAAK;AAAA,IACf;AAAA,IACA,QAAQ,CAAC,CAAC,KAAK;AAAA,IACf,aAAa,KAAK;AAAA,EACpB,CAAC;AACH,CAAC;AAEH,QAAQ,MAAM;","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,6 +1,6 @@
1
1
  {
2
2
  "name": "ccwebtty",
3
- "version": "1.0.3",
3
+ "version": "1.0.4",
4
4
  "description": "A CLI tool that exposes an interactive web terminal, similar to ttyd",
5
5
  "main": "dist/index.js",
6
6
  "bin": {