cmux-ssh-here 0.4.1 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/bin.js +28 -33
  2. package/package.json +1 -1
package/bin.js CHANGED
@@ -33,10 +33,10 @@ const { default: pty } = await import("node-pty");
33
33
  // and an ssh client treats a leading-dash username as a flag. Hex is URL/ssh-safe.
34
34
  const TTL_SECONDS = Number(process.env.CMUX_SSH_TTL) || 180; // link/token lifetime before regeneration
35
35
  let token; // secret carried in user=<token>; rotated every TTL_SECONDS
36
- let remaining; // seconds until the next regeneration
36
+ let expiry; // epoch ms when the current token expires
37
37
  const regenerateToken = () => {
38
38
  token = randomBytes(12).toString("hex");
39
- remaining = TTL_SECONDS;
39
+ expiry = Date.now() + TTL_SECONDS * 1000;
40
40
  };
41
41
  regenerateToken();
42
42
 
@@ -251,13 +251,11 @@ const server = new Server(serverCfg, (client, info) => {
251
251
  return ctx.username === token ? ctx.accept() : ctx.reject();
252
252
  });
253
253
  client.on("ready", () => {
254
+ // ponytail: just track the connection; the 5s timer redraws — no per-event
255
+ // repaint (that was the screen-churn that broke copying).
254
256
  const id = nextSid++;
255
257
  sessions.set(id, { ip: info?.ip || "?", since: Date.now() });
256
- render();
257
- client.on("close", () => {
258
- sessions.delete(id);
259
- render();
260
- });
258
+ client.on("close", () => sessions.delete(id));
261
259
  });
262
260
  client.on("session", (accept) => {
263
261
  const session = accept();
@@ -324,16 +322,18 @@ server.listen(PORT, "0.0.0.0", function () {
324
322
 
325
323
  // ponytail: in debug mode skip the dashboard so logs stay readable.
326
324
  const liveUI = !process.env.CMUX_SSH_DEBUG;
325
+ const REFRESH_MS = 5000; // ponytail: 5s cadence — long enough to select & copy the link
327
326
  const ago = (since) => `${Math.floor((Date.now() - since) / 1000)}s`;
327
+ const remainingSec = () => Math.max(0, Math.ceil((expiry - Date.now()) / 1000));
328
328
 
329
- // ponytail: never clear the whole screen (that wipes the user's selection and
330
- // makes the link impossible to copy). Repaint the static block only on real
331
- // events (link rotation, session add/remove); the per-second countdown rewrites
332
- // just its own line via "\r", leaving the link above untouched and selectable.
333
- const countdownLine = () => ` ↻ Link regenerates in ${remaining}s — copy it above. Ctrl-C to stop.`;
329
+ // Decreasing progress bar for the link's remaining lifetime.
330
+ const bar = (rem) => {
331
+ const W = 28;
332
+ const filled = Math.round((rem / TTL_SECONDS) * W);
333
+ return `[${"█".repeat(filled)}${"░".repeat(W - filled)}]`;
334
+ };
334
335
 
335
- // Collapse the raw SSH connections (cmux opens several from one machine:
336
- // ControlMaster, the daemon channel, short-lived probes) into one row per IP.
336
+ // Collapse cmux's several SSH connections from one machine into one row per IP.
337
337
  const sessionRows = () => {
338
338
  const byIp = new Map();
339
339
  for (const s of sessions.values()) {
@@ -346,7 +346,9 @@ server.listen(PORT, "0.0.0.0", function () {
346
346
  );
347
347
  };
348
348
 
349
- const block = () => {
349
+ render = () => {
350
+ if (!liveUI) return;
351
+ const rem = remainingSec();
350
352
  const lines = [
351
353
  "",
352
354
  ` cmux-ssh-here — shell as ${user} over the LAN`,
@@ -354,33 +356,26 @@ server.listen(PORT, "0.0.0.0", function () {
354
356
  " Open in cmux:",
355
357
  ` ${buildLink()}`,
356
358
  "",
359
+ ` Link valid ${bar(rem)} ${rem}s`,
360
+ "",
357
361
  ];
358
362
  const rows = sessionRows();
359
363
  if (rows.length) lines.push(` Connected (${rows.length}):`, ...rows);
360
364
  else lines.push(" No active sessions yet.");
361
- lines.push("");
362
- return lines.join("\n");
363
- };
364
-
365
- // Repaint the block, then leave the cursor parked on the countdown line.
366
- render = () => {
367
- if (!liveUI) return;
368
- process.stdout.write(`\n${block()}\n${countdownLine()}`);
365
+ lines.push("", " Updates every 5s · Ctrl-C to stop.", "");
366
+ // Home + clear-to-end (not full \x1b[2J): redraw in the same spot every 5s.
367
+ process.stdout.write(`\x1b[H\x1b[J${lines.join("\n")}\n`);
369
368
  };
370
369
 
371
370
  if (liveUI) render();
372
- else console.log(`\n Open in cmux (regenerates in ${remaining}s):\n ${buildLink()}\n`);
371
+ else console.log(`\n Open in cmux (regenerates in ${remainingSec()}s):\n ${buildLink()}\n`);
373
372
 
373
+ // Refresh every 5s: regenerate the link when it expires, then redraw.
374
374
  setInterval(() => {
375
- remaining--;
376
- if (remaining <= 0) {
375
+ if (remainingSec() <= 0) {
377
376
  regenerateToken();
378
- // Link changed: repaint the whole block (rare — once per TTL).
379
- if (liveUI) render();
380
- else console.log(`\n [link regenerated]\n ${buildLink()}\n`);
381
- return;
377
+ if (!liveUI) console.log(`\n [link regenerated]\n ${buildLink()}\n`);
382
378
  }
383
- // Common case: rewrite only the countdown line in place — no screen churn.
384
- if (liveUI) process.stdout.write(`\r\x1b[2K${countdownLine()}`);
385
- }, 1000);
379
+ render();
380
+ }, REFRESH_MS);
386
381
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cmux-ssh-here",
3
- "version": "0.4.1",
3
+ "version": "0.5.0",
4
4
  "description": "Spin up a token-auth SSH server and print a cmux deep link to connect over LAN",
5
5
  "type": "module",
6
6
  "bin": {