cmux-ssh-here 0.4.0 → 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 +42 -24
  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();
@@ -322,42 +320,62 @@ server.listen(PORT, "0.0.0.0", function () {
322
320
  return `https://cmux.com/deeplink/ssh?${params}`;
323
321
  };
324
322
 
325
- // ponytail: in debug mode skip the screen-clearing UI so logs stay readable;
326
- // just print the link whenever it rotates.
323
+ // ponytail: in debug mode skip the dashboard so logs stay readable.
327
324
  const liveUI = !process.env.CMUX_SSH_DEBUG;
325
+ const REFRESH_MS = 5000; // ponytail: 5s cadence — long enough to select & copy the link
328
326
  const ago = (since) => `${Math.floor((Date.now() - since) / 1000)}s`;
327
+ const remainingSec = () => Math.max(0, Math.ceil((expiry - Date.now()) / 1000));
328
+
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
+ };
335
+
336
+ // Collapse cmux's several SSH connections from one machine into one row per IP.
337
+ const sessionRows = () => {
338
+ const byIp = new Map();
339
+ for (const s of sessions.values()) {
340
+ const e = byIp.get(s.ip);
341
+ if (e) { e.count++; e.since = Math.min(e.since, s.since); }
342
+ else byIp.set(s.ip, { count: 1, since: s.since });
343
+ }
344
+ return [...byIp.entries()].map(
345
+ ([ipAddr, e]) => ` • ${ipAddr} connected ${ago(e.since)} ago${e.count > 1 ? ` (${e.count} connections)` : ""}`
346
+ );
347
+ };
329
348
 
330
349
  render = () => {
331
350
  if (!liveUI) return;
351
+ const rem = remainingSec();
332
352
  const lines = [
333
353
  "",
334
354
  ` cmux-ssh-here — shell as ${user} over the LAN`,
335
355
  "",
336
- ` Open in cmux (regenerates in ${remaining}s):`,
356
+ " Open in cmux:",
337
357
  ` ${buildLink()}`,
338
358
  "",
359
+ ` Link valid ${bar(rem)} ${rem}s`,
360
+ "",
339
361
  ];
340
- if (sessions.size) {
341
- lines.push(` Connected sessions (${sessions.size}):`);
342
- for (const s of sessions.values()) lines.push(` • ${s.ip} connected ${ago(s.since)} ago`);
343
- } else {
344
- lines.push(" No active sessions yet.");
345
- }
346
- lines.push("", " Ctrl-C to stop.", "");
347
- // Clear screen + home, then redraw.
348
- process.stdout.write(`\x1b[2J\x1b[3J\x1b[H${lines.join("\n")}\n`);
362
+ const rows = sessionRows();
363
+ if (rows.length) lines.push(` Connected (${rows.length}):`, ...rows);
364
+ else lines.push(" No active sessions yet.");
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`);
349
368
  };
350
369
 
351
370
  if (liveUI) render();
352
- 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`);
353
372
 
354
- // Tick once a second: count down, regenerate on expiry, refresh the UI.
373
+ // Refresh every 5s: regenerate the link when it expires, then redraw.
355
374
  setInterval(() => {
356
- remaining--;
357
- if (remaining <= 0) {
375
+ if (remainingSec() <= 0) {
358
376
  regenerateToken();
359
377
  if (!liveUI) console.log(`\n [link regenerated]\n ${buildLink()}\n`);
360
378
  }
361
379
  render();
362
- }, 1000);
380
+ }, REFRESH_MS);
363
381
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cmux-ssh-here",
3
- "version": "0.4.0",
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": {