ciphermesh 2.13.0 → 2.14.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/docs/SETUP.md CHANGED
@@ -293,6 +293,24 @@ Run `/help` in the chat for the **full list**. The main ones:
293
293
  - Or: `netsh advfirewall firewall add rule name="CipherMesh" dir=in action=allow protocol=TCP localport=3600`
294
294
  - Test whether the port responds: `curl ws://IP:3600` or open `http://IP:3600` in the browser (it will error out, but if the port connects it is open)
295
295
 
296
+ ### Shift+Enter does not insert a newline
297
+
298
+ A terminal cannot tell Shift+Enter from Enter unless the application asks it to,
299
+ so on startup CipherMesh requests the two protocols that make the difference
300
+ visible — the kitty keyboard protocol and xterm's `modifyOtherKeys`. Terminals
301
+ that implement neither simply ignore the request.
302
+
303
+ On those, use **Alt+Enter** or **Ctrl+J**, which work everywhere.
304
+
305
+ If a terminal reacts badly to the request, `CIPHERMESH_LEGACY_KEYS=1` turns the
306
+ negotiation off and hands the terminal to blessed untouched:
307
+
308
+ ```bash
309
+ CIPHERMESH_LEGACY_KEYS=1 ciphermesh
310
+ ```
311
+
312
+ Alt+Enter and Ctrl+J keep working in that mode; only Shift+Enter is lost.
313
+
296
314
  ### "npm install" fails on sodium-native
297
315
 
298
316
  `sodium-native` needs to compile C code. Requirements:
@@ -582,7 +582,7 @@
582
582
  {
583
583
  "name": "/notify",
584
584
  "args": "",
585
- "summary": "Sound / desktop notifications",
585
+ "summary": "Desktop notifications rate-limited, and muted for the session (with a line saying why) if the OS refuses them",
586
586
  "modes": [
587
587
  "relay",
588
588
  "p2p"
@@ -618,7 +618,7 @@
618
618
  {
619
619
  "name": "/sound",
620
620
  "args": "",
621
- "summary": "Sound / desktop notifications",
621
+ "summary": "Sound alerts on incoming messages",
622
622
  "modes": [
623
623
  "relay",
624
624
  "p2p"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ciphermesh",
3
- "version": "2.13.0",
3
+ "version": "2.14.0",
4
4
  "description": "Secure terminal chat for the local network (LAN) with real end-to-end encryption (E2EE) using libsodium",
5
5
  "type": "module",
6
6
  "main": "src/client/index.js",
@@ -67,7 +67,7 @@
67
67
  },
68
68
  "devDependencies": {
69
69
  "@eslint/js": "10.0.1",
70
- "eslint": "10.8.1",
70
+ "eslint": "10.9.1",
71
71
  "figlet": "^1.11.4",
72
72
  "globals": "^17.7.0",
73
73
  "prettier": "^3.9.5"
@@ -2,7 +2,6 @@ import { mkdirSync, writeFileSync } from 'node:fs';
2
2
  import { dirname, resolve } from 'node:path';
3
3
  import { tmpdir } from 'node:os';
4
4
  import sodium from 'sodium-native';
5
- import notifier from 'node-notifier';
6
5
  import qrcode from 'qrcode-terminal';
7
6
  import {
8
7
  MSG,
@@ -84,6 +83,7 @@ import {
84
83
  import { saveLastSession } from '../shared/lastSession.js';
85
84
  import { diagnose, formatDiagnosis } from '../shared/doctor.js';
86
85
  import { pluginsCommand } from '../shared/pluginCommand.js';
86
+ import { DesktopNotifier } from '../shared/desktopNotify.js';
87
87
  import { COMMANDS } from './UI.js';
88
88
 
89
89
  const TYPING_SEND_INTERVAL = 2000; // debounce: max 1 typing event per 2s
@@ -132,6 +132,7 @@ export class ChatController {
132
132
  #inviteRoom;
133
133
  #historyStore;
134
134
  #receiptsEnabled;
135
+ #desktopNotifier;
135
136
  #sentMessageLines; // Map<messageId, { lineIndex, baseLine }>
136
137
  #messageReaders; // Map<messageId, Set<nickname>>
137
138
  #away;
@@ -240,6 +241,9 @@ export class ChatController {
240
241
  this.#coverMode = 'off';
241
242
  this.#coverTimer = null;
242
243
  this.#paceQueue = [];
244
+ this.#desktopNotifier = new DesktopNotifier({
245
+ onUnavailable: (reason) => this.#onNotifierUnavailable(reason),
246
+ });
243
247
 
244
248
  this.#setupConnectionHandlers();
245
249
  this.#setupUIHandlers();
@@ -2326,7 +2330,7 @@ export class ChatController {
2326
2330
 
2327
2331
  // DND / mentions-only gates desktop notifications too.
2328
2332
  if (notify && (this.#ui.notifyEnabled || mentioned)) {
2329
- notifier.notify({
2333
+ this.#desktopNotifier.notify({
2330
2334
  title: mentioned
2331
2335
  ? `🔔 ${peer.nickname} mentioned you`
2332
2336
  : data.isDM
@@ -2346,6 +2350,17 @@ export class ChatController {
2346
2350
  }
2347
2351
  }
2348
2352
 
2353
+ // The OS refused a desktop notification (Windows with notifications turned
2354
+ // off for the app is the common case). Say it once, then stay quiet — the
2355
+ // notifier has already stopped trying, so the chat never sees it again.
2356
+ #onNotifierUnavailable(reason) {
2357
+ this.#ui.setNotifyEnabled(false);
2358
+ this.#ui.addInfoMessage(
2359
+ `Desktop notifications unavailable — ${reason}. Muted for this session; ` +
2360
+ 'sound alerts still work. Use /notify on to retry.',
2361
+ );
2362
+ }
2363
+
2349
2364
  // True if an incoming message references my nickname (@nick or standalone word).
2350
2365
  #mentionsMe(text) {
2351
2366
  return mentionsMe(text, this.#nickname);
@@ -2664,14 +2679,17 @@ export class ChatController {
2664
2679
  const notifyArg = parts[1]?.toLowerCase();
2665
2680
  if (notifyArg === 'off') {
2666
2681
  this.#ui.setNotifyEnabled(false);
2682
+ this.#desktopNotifier.disable();
2667
2683
  this.#ui.addInfoMessage('Desktop notifications disabled');
2668
2684
  } else if (notifyArg === 'on') {
2669
2685
  this.#ui.setNotifyEnabled(true);
2686
+ this.#desktopNotifier.reset(); // give a previously refusing OS another go
2670
2687
  this.#ui.addInfoMessage('Desktop notifications enabled');
2671
2688
  } else {
2672
2689
  const status = this.#ui.notifyEnabled ? 'enabled' : 'disabled';
2690
+ const blocked = this.#desktopNotifier.available ? '' : ' (blocked by the OS)';
2673
2691
  this.#ui.addInfoMessage(
2674
- `Desktop notifications: ${status}. Use /notify on or /notify off`,
2692
+ `Desktop notifications: ${status}${blocked}. Use /notify on or /notify off`,
2675
2693
  );
2676
2694
  }
2677
2695
  break;
@@ -4605,7 +4623,7 @@ export class ChatController {
4605
4623
 
4606
4624
  // Show own message locally
4607
4625
  if (replyTo) {
4608
- this.#ui.addQuoteLine(replyTo.nickname, replyTo.excerpt, true);
4626
+ this.#ui.addQuoteLine(replyTo.nickname, replyTo.excerpt);
4609
4627
  }
4610
4628
  const ephLabel = this.#ephemeralMode ? this.#formatDuration(this.#ephemeralDurationMs) : null;
4611
4629
  const { lineIndex, render } = isAction