ccwebtty 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,15 @@
1
+ ISC License
2
+
3
+ Copyright (c) 2025
4
+
5
+ Permission to use, copy, modify, and/or distribute this software for any
6
+ purpose with or without fee is hereby granted, provided that the above
7
+ copyright notice and this permission notice appear in all copies.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
10
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
11
+ AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
12
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
13
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
14
+ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
15
+ PERFORMANCE OF THIS SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,61 @@
1
+ # ccweb
2
+
3
+ A CLI tool that exposes an interactive web terminal in the browser, similar to [ttyd](https://github.com/tsl0922/ttyd).
4
+
5
+ ## Prerequisites
6
+
7
+ - [Node.js](https://nodejs.org/) >= 18
8
+
9
+ ## Install
10
+
11
+ ```bash
12
+ npm install -g ccweb
13
+ ```
14
+
15
+ ## Usage
16
+
17
+ ```bash
18
+ # Start with default settings (port 8080, all interfaces)
19
+ ccweb
20
+
21
+ # Custom port and host
22
+ ccweb --port 3000 --host 127.0.0.1
23
+
24
+ # Specify a different shell
25
+ ccweb --shell /bin/bash
26
+ ```
27
+
28
+ Then open `http://localhost:8080` in your browser.
29
+
30
+ ## Options
31
+
32
+ | Option | Default | Description |
33
+ |--------|---------|-------------|
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 |
39
+
40
+ ## Development
41
+
42
+ ```bash
43
+ # Install dependencies
44
+ npm install
45
+
46
+ # Build
47
+ npm run build
48
+
49
+ # Run
50
+ npm start
51
+
52
+ # Watch mode
53
+ npm run dev
54
+ ```
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 ADDED
@@ -0,0 +1,454 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") {
11
+ for (let key of __getOwnPropNames(from))
12
+ if (!__hasOwnProp.call(to, key) && key !== except)
13
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
+ }
15
+ return to;
16
+ };
17
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
18
+ // If the importer is in node compatibility mode or this is not an ESM
19
+ // file that has been converted to a CommonJS file using a Babel-
20
+ // compatible transform (i.e. "__esModule" has not been set), then set
21
+ // "default" to the CommonJS "module.exports" for node compatibility.
22
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
23
+ mod
24
+ ));
25
+
26
+ // src/index.ts
27
+ var import_crypto3 = __toESM(require("crypto"));
28
+ var import_commander = require("commander");
29
+
30
+ // src/server.ts
31
+ var import_crypto2 = __toESM(require("crypto"));
32
+ var import_child_process = require("child_process");
33
+ var import_express = __toESM(require("express"));
34
+ var import_http = __toESM(require("http"));
35
+ var import_os2 = __toESM(require("os"));
36
+ var import_path = __toESM(require("path"));
37
+ var import_ws = require("ws");
38
+
39
+ // src/session-manager.ts
40
+ var import_crypto = __toESM(require("crypto"));
41
+
42
+ // src/terminal.ts
43
+ var pty = __toESM(require("node-pty"));
44
+ var import_os = __toESM(require("os"));
45
+ var Terminal = class {
46
+ constructor(options) {
47
+ this.disposed = false;
48
+ const { shell, cols = 80, rows = 24, cwd } = options;
49
+ const env = Object.assign({}, process.env, {
50
+ TERM: "xterm-256color",
51
+ COLORTERM: "truecolor",
52
+ FORCE_COLOR: "3",
53
+ TERM_PROGRAM: "xterm-256color"
54
+ });
55
+ delete env.NO_COLOR;
56
+ this.process = pty.spawn(shell, [], {
57
+ name: "xterm-256color",
58
+ cols,
59
+ rows,
60
+ cwd: cwd || import_os.default.homedir(),
61
+ env
62
+ });
63
+ }
64
+ get pid() {
65
+ return this.process.pid;
66
+ }
67
+ onData(callback) {
68
+ this.process.onData(callback);
69
+ }
70
+ onExit(callback) {
71
+ this.process.onExit(({ exitCode, signal }) => {
72
+ callback(exitCode, signal);
73
+ });
74
+ }
75
+ write(data) {
76
+ if (!this.disposed) {
77
+ this.process.write(data);
78
+ }
79
+ }
80
+ resize(cols, rows) {
81
+ if (!this.disposed) {
82
+ try {
83
+ this.process.resize(cols, rows);
84
+ } catch {
85
+ }
86
+ }
87
+ }
88
+ kill() {
89
+ if (!this.disposed) {
90
+ this.disposed = true;
91
+ try {
92
+ this.process.kill();
93
+ } catch {
94
+ }
95
+ }
96
+ }
97
+ };
98
+
99
+ // src/session-manager.ts
100
+ var MAX_BUFFER = 5e3;
101
+ var SessionManager = class {
102
+ constructor() {
103
+ this.sessions = /* @__PURE__ */ new Map();
104
+ this.order = [];
105
+ }
106
+ create(shell, cols, rows) {
107
+ const id = import_crypto.default.randomUUID();
108
+ const terminal = new Terminal({ shell, cols, rows });
109
+ const session = {
110
+ id,
111
+ terminal,
112
+ buffer: [],
113
+ clients: /* @__PURE__ */ new Set(),
114
+ onExit: /* @__PURE__ */ new Set(),
115
+ alive: true
116
+ };
117
+ terminal.onData((data) => {
118
+ if (session.buffer.length >= MAX_BUFFER) {
119
+ session.buffer.shift();
120
+ }
121
+ session.buffer.push(data);
122
+ for (const cb of session.clients) {
123
+ cb(data);
124
+ }
125
+ });
126
+ terminal.onExit(() => {
127
+ session.alive = false;
128
+ for (const cb of session.onExit) {
129
+ cb();
130
+ }
131
+ });
132
+ this.sessions.set(id, session);
133
+ this.order.push(id);
134
+ return session;
135
+ }
136
+ get(id) {
137
+ const session = this.sessions.get(id);
138
+ if (session && !session.alive) {
139
+ this.sessions.delete(id);
140
+ this.order = this.order.filter((x) => x !== id);
141
+ return void 0;
142
+ }
143
+ return session;
144
+ }
145
+ listIds() {
146
+ this.order = this.order.filter((id) => {
147
+ const s = this.sessions.get(id);
148
+ if (s && s.alive) return true;
149
+ this.sessions.delete(id);
150
+ return false;
151
+ });
152
+ return [...this.order];
153
+ }
154
+ remove(id) {
155
+ const session = this.sessions.get(id);
156
+ if (session) {
157
+ session.terminal.kill();
158
+ this.sessions.delete(id);
159
+ }
160
+ this.order = this.order.filter((x) => x !== id);
161
+ }
162
+ destroyAll() {
163
+ for (const session of this.sessions.values()) {
164
+ session.terminal.kill();
165
+ }
166
+ this.sessions.clear();
167
+ this.order = [];
168
+ }
169
+ };
170
+
171
+ // src/server.ts
172
+ function generateSecret() {
173
+ return import_crypto2.default.randomBytes(32).toString("hex");
174
+ }
175
+ function signToken(payload, secret) {
176
+ const hmac = import_crypto2.default.createHmac("sha256", secret);
177
+ hmac.update(payload);
178
+ return payload + "." + hmac.digest("hex");
179
+ }
180
+ function verifyToken(token, secret) {
181
+ const dotIdx = token.lastIndexOf(".");
182
+ if (dotIdx === -1) return false;
183
+ const payload = token.substring(0, dotIdx);
184
+ const sig = token.substring(dotIdx + 1);
185
+ const hmac = import_crypto2.default.createHmac("sha256", secret);
186
+ hmac.update(payload);
187
+ const expected = hmac.digest("hex");
188
+ if (sig.length !== expected.length) return false;
189
+ return import_crypto2.default.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
190
+ }
191
+ function parseCookies(header) {
192
+ const cookies = {};
193
+ if (!header) return cookies;
194
+ for (const part of header.split(";")) {
195
+ const eq = part.indexOf("=");
196
+ if (eq === -1) continue;
197
+ const key = part.substring(0, eq).trim();
198
+ const val = part.substring(eq + 1).trim();
199
+ cookies[key] = decodeURIComponent(val);
200
+ }
201
+ return cookies;
202
+ }
203
+ function startServer(options) {
204
+ const { port, host, shell, username, password, tunnel: enableTunnel } = options;
205
+ const sessionManager = new SessionManager();
206
+ const authSecret = generateSecret();
207
+ const app = (0, import_express.default)();
208
+ app.set("trust proxy", true);
209
+ const server = import_http.default.createServer(app);
210
+ const allClients = /* @__PURE__ */ new Set();
211
+ function broadcastTabs() {
212
+ const tabs = sessionManager.listIds();
213
+ const msg = JSON.stringify({ type: "tabs", tabs });
214
+ for (const client of allClients) {
215
+ if (client.readyState === import_ws.WebSocket.OPEN) {
216
+ client.send(msg);
217
+ }
218
+ }
219
+ }
220
+ function isAuthenticated(cookieHeader) {
221
+ const cookies = parseCookies(cookieHeader);
222
+ const token = cookies["ccweb_token"];
223
+ if (!token) return false;
224
+ return verifyToken(token, authSecret);
225
+ }
226
+ app.use(import_express.default.urlencoded({ extended: false }));
227
+ app.post("/login", (req, res) => {
228
+ const { username: u, password: p } = req.body;
229
+ if (!u || !p) {
230
+ return res.status(400).send("Missing credentials");
231
+ }
232
+ const uMatch = u.length === username.length && import_crypto2.default.timingSafeEqual(Buffer.from(u), Buffer.from(username));
233
+ const pMatch = p.length === password.length && import_crypto2.default.timingSafeEqual(Buffer.from(p), Buffer.from(password));
234
+ if (!uMatch || !pMatch) {
235
+ return res.status(401).send("Invalid credentials");
236
+ }
237
+ const token = signToken("auth", authSecret);
238
+ const secure = req.protocol === "https" || req.headers["x-forwarded-proto"] === "https";
239
+ const cookieFlags = `Path=/; HttpOnly; SameSite=Lax${secure ? "; Secure" : ""}`;
240
+ res.setHeader("Set-Cookie", `ccweb_token=${encodeURIComponent(token)}; ${cookieFlags}`);
241
+ res.redirect("/");
242
+ });
243
+ app.use((req, res, next) => {
244
+ if (isAuthenticated(req.headers.cookie)) {
245
+ return next();
246
+ }
247
+ if (req.method === "GET") {
248
+ return res.sendFile(import_path.default.join(__dirname, "public", "login.html"));
249
+ }
250
+ res.status(401).send("Unauthorized");
251
+ });
252
+ app.use(import_express.default.static(import_path.default.join(__dirname, "public")));
253
+ app.get("/", (_req, res) => {
254
+ res.sendFile(import_path.default.join(__dirname, "public", "index.html"));
255
+ });
256
+ const wss = new import_ws.WebSocketServer({ noServer: true });
257
+ const WS_PING_INTERVAL = 3e4;
258
+ const pingInterval = setInterval(() => {
259
+ for (const client of wss.clients) {
260
+ if (client.readyState === import_ws.WebSocket.OPEN) {
261
+ client.ping();
262
+ }
263
+ }
264
+ }, WS_PING_INTERVAL);
265
+ wss.on("close", () => clearInterval(pingInterval));
266
+ server.on("upgrade", (req, socket, head) => {
267
+ if (!isAuthenticated(req.headers.cookie)) {
268
+ socket.write("HTTP/1.1 401 Unauthorized\r\n\r\n");
269
+ socket.destroy();
270
+ return;
271
+ }
272
+ wss.handleUpgrade(req, socket, head, (ws) => {
273
+ wss.emit("connection", ws, req);
274
+ });
275
+ });
276
+ wss.on("connection", (ws, req) => {
277
+ const url = new URL(req.url || "/", `http://${req.headers.host}`);
278
+ const sessionId = url.searchParams.get("sessionId");
279
+ const cols = parseInt(url.searchParams.get("cols") || "80", 10);
280
+ const rows = parseInt(url.searchParams.get("rows") || "24", 10);
281
+ allClients.add(ws);
282
+ if (!sessionId) {
283
+ if (sessionManager.listIds().length === 0) {
284
+ const session2 = sessionManager.create(shell, cols, rows);
285
+ console.log(`[ccweb] Default session: ${session2.id}`);
286
+ }
287
+ ws.send(JSON.stringify({ type: "tabs", tabs: sessionManager.listIds() }));
288
+ ws.on("message", (raw) => {
289
+ try {
290
+ const msg = JSON.parse(raw.toString());
291
+ if (msg.type === "create") {
292
+ const c = msg.cols || 80;
293
+ const r = msg.rows || 24;
294
+ const session2 = sessionManager.create(shell, c, r);
295
+ console.log(`[ccweb] New session: ${session2.id}`);
296
+ broadcastTabs();
297
+ } else if (msg.type === "kill" && msg.sessionId) {
298
+ console.log(`[ccweb] Session killed: ${msg.sessionId}`);
299
+ sessionManager.remove(msg.sessionId);
300
+ broadcastTabs();
301
+ }
302
+ } catch {
303
+ }
304
+ });
305
+ ws.on("close", () => {
306
+ allClients.delete(ws);
307
+ });
308
+ ws.on("error", () => {
309
+ allClients.delete(ws);
310
+ });
311
+ return;
312
+ }
313
+ const session = sessionManager.get(sessionId);
314
+ if (!session) {
315
+ ws.send(JSON.stringify({ type: "error", data: "Session not found" }));
316
+ ws.close();
317
+ allClients.delete(ws);
318
+ return;
319
+ }
320
+ console.log(`[ccweb] Client attached to session: ${sessionId}`);
321
+ for (const chunk of session.buffer) {
322
+ ws.send(JSON.stringify({ type: "output", data: chunk }));
323
+ }
324
+ const onOutput = (data) => {
325
+ if (ws.readyState === import_ws.WebSocket.OPEN) {
326
+ ws.send(JSON.stringify({ type: "output", data }));
327
+ }
328
+ };
329
+ session.clients.add(onOutput);
330
+ const onExit = () => {
331
+ console.log(`[ccweb] Session ended: ${sessionId}`);
332
+ if (ws.readyState === import_ws.WebSocket.OPEN) {
333
+ ws.send(JSON.stringify({ type: "exit" }));
334
+ ws.close();
335
+ }
336
+ sessionManager.remove(sessionId);
337
+ broadcastTabs();
338
+ };
339
+ session.onExit.add(onExit);
340
+ session.terminal.resize(cols, rows);
341
+ ws.on("message", (raw) => {
342
+ try {
343
+ const msg = JSON.parse(raw.toString());
344
+ switch (msg.type) {
345
+ case "input":
346
+ if (msg.data) {
347
+ session.terminal.write(msg.data);
348
+ }
349
+ break;
350
+ case "resize":
351
+ if (msg.cols && msg.rows) {
352
+ session.terminal.resize(msg.cols, msg.rows);
353
+ }
354
+ break;
355
+ }
356
+ } catch {
357
+ }
358
+ });
359
+ ws.on("close", () => {
360
+ allClients.delete(ws);
361
+ session.clients.delete(onOutput);
362
+ session.onExit.delete(onExit);
363
+ });
364
+ ws.on("error", () => {
365
+ allClients.delete(ws);
366
+ session.clients.delete(onOutput);
367
+ session.onExit.delete(onExit);
368
+ });
369
+ });
370
+ server.listen(port, host, () => {
371
+ const addr = host === "0.0.0.0" ? "localhost" : host;
372
+ console.log(`[ccweb] Terminal server running at http://${addr}:${port}`);
373
+ console.log(`[ccweb] Shell: ${shell}`);
374
+ console.log(`[ccweb] Username: ${username}`);
375
+ console.log(`[ccweb] Password: ${password}`);
376
+ console.log(`[ccweb] Press Ctrl+C to stop`);
377
+ if (enableTunnel) {
378
+ import("cloudflared").then(({ Tunnel }) => {
379
+ const t = new Tunnel(["tunnel", "--url", `http://localhost:${port}`, "--no-autoupdate"]);
380
+ t.on("url", (url) => {
381
+ console.log(`[ccweb] Tunnel: ${url}`);
382
+ });
383
+ t.on("error", (err) => {
384
+ console.error(`[ccweb] Tunnel error: ${err.message}`);
385
+ });
386
+ const stopTunnel = () => t.stop();
387
+ process.on("SIGINT", stopTunnel);
388
+ process.on("SIGTERM", stopTunnel);
389
+ }).catch((err) => {
390
+ console.error("[ccweb] Failed to start tunnel:", err.message);
391
+ });
392
+ }
393
+ const platform = import_os2.default.platform();
394
+ if (platform === "darwin") {
395
+ const caffeinate = (0, import_child_process.spawn)("caffeinate", ["-s", "-w", process.pid.toString()], {
396
+ stdio: "ignore"
397
+ });
398
+ caffeinate.unref();
399
+ console.log("[ccweb] Sleep prevention enabled (caffeinate)");
400
+ } else if (platform === "win32") {
401
+ const script = `
402
+ Add-Type -TypeDefinition @"
403
+ using System.Runtime.InteropServices;
404
+ public class Power {
405
+ [DllImport("kernel32.dll")]
406
+ public static extern uint SetThreadExecutionState(uint esFlags);
407
+ }
408
+ "@
409
+ while($true) {
410
+ [Power]::SetThreadExecutionState(0x80000001)
411
+ Start-Sleep -Seconds 30
412
+ }`;
413
+ const ps = (0, import_child_process.spawn)("powershell", ["-NoProfile", "-Command", script], {
414
+ stdio: "ignore"
415
+ });
416
+ ps.unref();
417
+ console.log("[ccweb] Sleep prevention enabled (SetThreadExecutionState)");
418
+ }
419
+ });
420
+ const shutdown = () => {
421
+ console.log("\n[ccweb] Shutting down...");
422
+ wss.clients.forEach((client) => client.close());
423
+ sessionManager.destroyAll();
424
+ server.close(() => process.exit(0));
425
+ setTimeout(() => process.exit(1), 3e3);
426
+ };
427
+ process.on("SIGINT", shutdown);
428
+ process.on("SIGTERM", shutdown);
429
+ }
430
+
431
+ // src/index.ts
432
+ var program = new import_commander.Command();
433
+ program.name("ccweb").description("A CLI tool that exposes an interactive web terminal in the browser").version("1.0.0").option("-p, --port <number>", "port to listen on", "8080").option("-H, --host <address>", "host to bind to", "0.0.0.0").option(
434
+ "-s, --shell <path>",
435
+ "shell to spawn",
436
+ process.env.SHELL || "/bin/bash"
437
+ ).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((opts) => {
438
+ const port = parseInt(opts.port, 10);
439
+ if (isNaN(port) || port < 1 || port > 65535) {
440
+ console.error(`Invalid port: ${opts.port}`);
441
+ process.exit(1);
442
+ }
443
+ const password = opts.password || import_crypto3.default.randomBytes(12).toString("base64url");
444
+ startServer({
445
+ port,
446
+ host: opts.host,
447
+ shell: opts.shell,
448
+ username: opts.username,
449
+ password,
450
+ tunnel: !!opts.tunnel
451
+ });
452
+ });
453
+ program.parse();
454
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/server.ts","../src/session-manager.ts","../src/terminal.ts"],"sourcesContent":["import crypto from \"crypto\";\nimport { Command } from \"commander\";\nimport { startServer } from \"./server\";\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 .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((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 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\"));\n }\n res.status(401).send(\"Unauthorized\");\n });\n\n app.use(express.static(path.join(__dirname, \"public\")));\n\n app.get(\"/\", (_req, res) => {\n res.sendFile(path.join(__dirname, \"public\", \"index.html\"));\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"],"mappings":"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","names":["import_crypto","import_crypto","import_os","os","crypto","crypto","express","http","path","session","os","crypto"]}