ciphermesh 2.0.0 โ†’ 2.2.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/README.md CHANGED
@@ -53,7 +53,9 @@ forwarding, survives CGNAT).
53
53
  | ๐Ÿ’ฌ | **Modern chat feel** | Right-aligned own messages, per-user emoji avatars, replies with quotes, `:fire:` โ†’ ๐Ÿ”ฅ |
54
54
  | ๐ŸŽž๏ธ | **Animated UI** | Splash intro, reconnect spinner, live transfer bars (shimmer + ETA), a lock-closing handshake on connect, and a pulsing "new messages โ†“" pill |
55
55
  | ๐Ÿ‘ป | **Deniable & ephemeral** | Symmetric-crypto deniable mode; ephemeral messages *burn away* char-by-char when they expire |
56
+ | ๐Ÿ”’ | **Private rooms** | `/create <room> <password>` โ€” zero-knowledge: the password never leaves your machine (Argon2id โ†’ Ed25519 challenge-response) and room content gets an extra symmetric layer the relay can't fake its way into |
56
57
  | ๐Ÿ›ฐ๏ธ | **Serverless P2P mode** | mDNS peer discovery on the LAN โ€” no relay at all |
58
+ | ๐Ÿงฉ | **Plugins** | Drop a JS file in `~/.ciphermesh/plugins` and get new slash-commands โ€” `/roll` and `/poll` examples included ([Plugin API](docs/PLUGINS.md)) |
57
59
 
58
60
  ## ๐Ÿš€ Quick start
59
61
 
@@ -131,12 +133,27 @@ QR code) to whoever you want to pull in.
131
133
 
132
134
  | Command | Description |
133
135
  |---------|-------------|
134
- | `/join <room>` | Enter/create a room |
135
- | `/rooms` | List rooms |
136
- | `/room` | Current room |
136
+ | `/join <room> [password]` | Open a room as a **new buffer** โ€” you stay in your other rooms (IRC style) |
137
+ | `/leave [room]` | Leave a room; its buffer closes (the last room is protected) |
138
+ | `/create <room> <password>` | Create a **private room** ๐Ÿ”’ โ€” see below |
139
+ | `/rooms` | List rooms (๐Ÿ”’ marks private ones) |
140
+ | `/room` | Current room + your buffer list |
137
141
  | `/owner` | Room owner |
138
142
  | `/kick` `/mute` `/ban` | Owner moderation |
139
143
 
144
+ **Buffers:** be in several rooms at once โ€” **Alt+1..9** switches, and the status
145
+ bar shows `[1:general] [2:dev โ€ข3]` with per-room unread badges. Because the
146
+ relay is blind (sealed sender), which room a message belongs to travels
147
+ *inside* the encrypted payload โ€” the server never learns it.
148
+
149
+ **Private rooms** are zero-knowledge: the password never leaves your machine.
150
+ Joining derives an Ed25519 key from the password (Argon2id) and answers a
151
+ server challenge with a signature โ€” the server stores only a verifier, in
152
+ memory, and it dies when the last member leaves (rooms are always ephemeral).
153
+ On top of that, everything said in a private room carries an extra symmetric
154
+ layer keyed by the password, so even a malicious relay that let someone in
155
+ without verifying couldn't read a word. Share the password out-of-band.
156
+
140
157
  </details>
141
158
 
142
159
  <details>
@@ -148,8 +165,10 @@ QR code) to whoever you want to pull in.
148
165
  | `/verify <nick>` | SAS code (~40-bit) + QR + key randomart for out-of-band verification |
149
166
  | `/verify-confirm <nick>` | Mark peer as verified |
150
167
  | `/trust <nick>` / `/trustlist` | Accept new key / trust status |
168
+ | `/contacts [add\|remove\|all]` | Contact book โ€” persistent aliases on trust records ("this fingerprint is Joรฃo"); shows in `/users`, rides along in identity backups |
151
169
  | `/backup [path]` | Encrypted backup of identity + verified peers (restore at startup) |
152
170
  | `/deniable [on\|off]` | Plausible-deniability mode |
171
+ | `/lock` / `/autolock <min\|off>` | Lock the screen behind the session passphrase โ€” manually or after idle time (`autoLock` in config). Privacy for the "stepped away" moment; `/panic` is for the worst one |
153
172
  | `/panic [yes]` | Duress wipe โ€” securely erase all on-disk secrets (session, history, trust, keys) and exit |
154
173
  | `/cover [on\|constant\|off]` | Cover traffic โ€” `on` = jittered decoys, `constant` = steady-rate paced channel |
155
174
  | `/theme [name]` | Nick colour theme: neon, matrix, mono, sunset, ocean |
@@ -183,7 +202,8 @@ A green **โœ“** next to a name marks a SAS-verified peer; a red **โœ—** flags a
183
202
 
184
203
  | Command | Description |
185
204
  |---------|-------------|
186
- | `/away [reason]` / `/back` | Mark yourself away |
205
+ | `/away [reason]` / `/back` | Mark yourself away โ€” while away, unreads are counted (`[away ยท N new]`) and `/back` shows a summary |
206
+ | `/mentions [n]` | Recent mentions of you this session (who, where, when) |
187
207
  | `/status <text\|off>` | Free-form status โ€” emojis welcome (`/status :fire: coding`) |
188
208
  | `/react <emoji>` | React to the last message |
189
209
  | `/edit` `/delete` | Edit/delete your last message |
@@ -194,11 +214,23 @@ A green **โœ“** next to a name marks a SAS-verified peer; a red **โœ—** flags a
194
214
 
195
215
  </details>
196
216
 
197
- Typing `:fire:` anywhere becomes ๐Ÿ”ฅ (Tab autocompletes shortcodes). **Ctrl+K** opens a fuzzy command palette, **Ctrl+E** an emoji picker. PageUp/PageDown scroll the history. **Alt+Enter** (or Shift+Enter where the terminal supports it, plus Ctrl+J) inserts a newline for multi-line messages; Enter sends. Markdown works: \`code\`, **bold**, *italic*, links, plus fenced \`\`\` code blocks and | tables |. Received images preview inline (half-blocks) and render full-res with `/img` on kitty/iTerm2. Day separators and message grouping keep the log clean.
217
+ Typing `:fire:` anywhere becomes ๐Ÿ”ฅ (Tab autocompletes shortcodes). **Ctrl+K** opens a fuzzy command palette, **Ctrl+E** an emoji picker. PageUp/PageDown scroll the history. **Alt+Enter** (or Shift+Enter where the terminal supports it, plus Ctrl+J) inserts a newline for multi-line messages; Enter sends. Pasting multi-line text (code included) keeps its line breaks โ€” paste, check, Enter. Markdown works: \`code\`, **bold**, *italic*, links, plus fenced \`\`\` code blocks and | tables |. Received images preview inline (half-blocks) and render full-res with `/img` on kitty/iTerm2. Day separators and message grouping keep the log clean.
218
+
219
+ ### First run & config file
220
+
221
+ On your very first run, a **30-second setup wizard** walks you through nickname,
222
+ colour theme and default server (with a 3-line crash course on how the
223
+ encryption works), then saves everything so later runs go straight to the chat.
224
+ Re-run it anytime with `ciphermesh --setup`; skip it in scripts/CI with
225
+ `--no-onboard`.
198
226
 
199
- ### Config file
227
+ CipherMesh also remembers your **last session**: the server prompt defaults to
228
+ where you were, and after connecting you land back in your last room
229
+ automatically (private rooms excluded โ€” their names never touch disk). Start
230
+ clean with `ciphermesh --fresh`.
200
231
 
201
- Drop a `~/.ciphermesh/config.json` to set defaults and skip the prompts. All keys are optional (unknown keys are ignored):
232
+ The wizard writes `~/.ciphermesh/config.json` โ€” you can also edit it by hand.
233
+ All keys are optional (unknown keys are ignored):
202
234
 
203
235
  ```json
204
236
  {
@@ -211,6 +243,7 @@ Drop a `~/.ciphermesh/config.json` to set defaults and skip the prompts. All key
211
243
  "cover": "constant",
212
244
  "theme": "matrix",
213
245
  "autoAway": 10,
246
+ "autoLock": 5,
214
247
  "dnd": "22:00-08:00"
215
248
  }
216
249
  ```
package/README.pt-BR.md CHANGED
@@ -53,7 +53,9 @@ forwarding, imune a CGNAT).
53
53
  | ๐Ÿ’ฌ | **Cara de app moderno** | Suas mensagens ร  direita, avatar de emoji por usuรกrio, reply com citaรงรฃo, `:fire:` โ†’ ๐Ÿ”ฅ |
54
54
  | ๐ŸŽž๏ธ | **Interface animada** | Splash na abertura, spinner de reconexรฃo, barra de transferรชncia viva (shimmer + ETA), cadeado fechando no handshake e um selo pulsante "novas mensagens โ†“" |
55
55
  | ๐Ÿ‘ป | **Deniable e efรชmeras** | Modo de negaรงรฃo plausรญvel (crypto simรฉtrica); mensagens efรชmeras *queimam* caractere a caractere ao expirar |
56
+ | ๐Ÿ”’ | **Salas privadas** | `/create <sala> <senha>` โ€” zero-knowledge: a senha nunca sai da sua mรกquina (Argon2id โ†’ challenge-response Ed25519) e o conteรบdo da sala ganha uma camada simรฉtrica extra que nem um relay malicioso atravessa |
56
57
  | ๐Ÿ›ฐ๏ธ | **Modo P2P sem servidor** | Descoberta de peers via mDNS na LAN โ€” sem relay nenhum |
58
+ | ๐Ÿงฉ | **Plugins** | Solta um arquivo JS em `~/.ciphermesh/plugins` e ganha comandos novos โ€” exemplos `/roll` e `/poll` inclusos ([API de plugins](docs/PLUGINS.md)) |
57
59
 
58
60
  ## ๐Ÿš€ Comeรงando
59
61
 
@@ -131,12 +133,27 @@ quem vocรช quiser puxar pra conversa.
131
133
 
132
134
  | Comando | Descriรงรฃo |
133
135
  |---------|-----------|
134
- | `/join <sala>` | Entra/cria uma sala |
135
- | `/rooms` | Lista salas |
136
- | `/room` | Sala atual |
136
+ | `/join <sala> [senha]` | Abre a sala como um **novo buffer** โ€” vocรช continua nas outras (estilo IRC) |
137
+ | `/leave [sala]` | Sai de uma sala; o buffer fecha (a รบltima sala รฉ protegida) |
138
+ | `/create <sala> <senha>` | Cria uma **sala privada** ๐Ÿ”’ โ€” veja abaixo |
139
+ | `/rooms` | Lista salas (๐Ÿ”’ marca as privadas) |
140
+ | `/room` | Sala atual + sua lista de buffers |
137
141
  | `/owner` | Dono da sala |
138
142
  | `/kick` `/mute` `/ban` | Moderaรงรฃo (dono da sala) |
139
143
 
144
+ **Buffers:** esteja em vรกrias salas ao mesmo tempo โ€” **Alt+1..9** alterna, e a
145
+ barra de status mostra `[1:general] [2:dev โ€ข3]` com nรฃo-lidas por sala. Como o
146
+ relay รฉ cego (sealed sender), a qual sala cada mensagem pertence viaja *dentro*
147
+ do payload cifrado โ€” o servidor nunca fica sabendo.
148
+
149
+ **Salas privadas** sรฃo zero-knowledge: a senha nunca sai da sua mรกquina. Ao
150
+ entrar, o cliente deriva uma chave Ed25519 da senha (Argon2id) e responde um
151
+ desafio do servidor com uma assinatura โ€” o servidor guarda sรณ um verificador,
152
+ em memรณria, que morre quando a รบltima pessoa sai (salas sรฃo sempre efรชmeras).
153
+ Alรฉm disso, tudo que รฉ dito numa sala privada carrega uma camada simรฉtrica
154
+ extra derivada da senha: mesmo um relay malicioso que deixasse alguรฉm entrar
155
+ sem verificar nรฃo leria uma palavra. Combine a senha por outro canal.
156
+
140
157
  </details>
141
158
 
142
159
  <details>
@@ -149,7 +166,9 @@ quem vocรช quiser puxar pra conversa.
149
166
  | `/verify-confirm <nick>` | Marca o peer como verificado |
150
167
  | `/backup [caminho]` | Backup cifrado da identidade + peers verificados (restaura no startup) |
151
168
  | `/trust <nick>` / `/trustlist` | Aceita chave nova / status de confianรงa |
169
+ | `/contacts [add\|remove\|all]` | Agenda โ€” apelidos persistentes nos registros de confianรงa ("esse fingerprint รฉ o Joรฃo"); aparece no `/users` e viaja no backup de identidade |
152
170
  | `/deniable [on\|off]` | Modo de negaรงรฃo plausรญvel |
171
+ | `/lock` / `/autolock <min\|off>` | Trava a tela atrรกs da passphrase da sessรฃo โ€” na mรฃo ou apรณs inatividade (`autoLock` no config). Privacidade para o "saรญ um minuto"; o `/panic` รฉ para o pior minuto |
153
172
  | `/panic [sim]` | Wipe de coaรงรฃo โ€” apaga com seguranรงa todos os segredos do disco (sessรฃo, histรณrico, confianรงa, chaves) e sai |
154
173
  | `/cover [on\|constant\|off]` | Cover traffic โ€” `on` = iscas com jitter, `constant` = canal de taxa constante |
155
174
  | `/theme [nome]` | Tema de cores dos nicks: neon, matrix, mono, sunset, ocean |
@@ -183,7 +202,8 @@ Um **โœ“** verde ao lado de um nome indica um peer verificado por SAS; um **โœ—*
183
202
 
184
203
  | Comando | Descriรงรฃo |
185
204
  |---------|-----------|
186
- | `/away [motivo]` / `/back` | Marca/remove ausรชncia |
205
+ | `/away [motivo]` / `/back` | Marca/remove ausรชncia โ€” enquanto ausente, nรฃo-lidas sรฃo contadas (`[away ยท N new]`) e o `/back` mostra um resumo |
206
+ | `/mentions [n]` | Menรงรตes recentes a vocรช na sessรฃo (quem, onde, quando) |
187
207
  | `/status <texto\|off>` | Status livre โ€” emoji ร  vontade (`/status :fire: codando`) |
188
208
  | `/react <emoji>` | Reage ร  รบltima mensagem |
189
209
  | `/edit` `/delete` | Edita/apaga sua รบltima mensagem |
@@ -195,11 +215,23 @@ Um **โœ“** verde ao lado de um nome indica um peer verificado por SAS; um **โœ—*
195
215
  </details>
196
216
 
197
217
  Digitar `:fire:` em qualquer lugar vira ๐Ÿ”ฅ (Tab autocompleta shortcodes).
198
- **Ctrl+K** abre uma paleta de comandos fuzzy, **Ctrl+E** um seletor de emoji. PageUp/PageDown rolam o histรณrico. **Alt+Enter** (ou Shift+Enter onde o terminal suporta, alรฉm de Ctrl+J) insere uma nova linha para mensagens de vรกrias linhas; Enter envia. Markdown funciona: \`cรณdigo\`, **negrito**, *itรกlico*, links, alรฉm de blocos de cรณdigo \`\`\` e | tabelas |. Imagens recebidas tรชm preview inline (half-blocks) e renderizam em alta resoluรงรฃo com `/img` no kitty/iTerm2. Separadores de dia e agrupamento de mensagens deixam o log limpo.
218
+ **Ctrl+K** abre uma paleta de comandos fuzzy, **Ctrl+E** um seletor de emoji. PageUp/PageDown rolam o histรณrico. **Alt+Enter** (ou Shift+Enter onde o terminal suporta, alรฉm de Ctrl+J) insere uma nova linha para mensagens de vรกrias linhas; Enter envia. Colar texto multi-linha (cรณdigo incluso) preserva as quebras โ€” cola, confere, Enter. Markdown funciona: \`cรณdigo\`, **negrito**, *itรกlico*, links, alรฉm de blocos de cรณdigo \`\`\` e | tabelas |. Imagens recebidas tรชm preview inline (half-blocks) e renderizam em alta resoluรงรฃo com `/img` no kitty/iTerm2. Separadores de dia e agrupamento de mensagens deixam o log limpo.
219
+
220
+ ### Primeira execuรงรฃo & arquivo de config
221
+
222
+ Na primeirรญssima execuรงรฃo, um **wizard de 30 segundos** te guia por nickname,
223
+ tema de cores e servidor padrรฃo (com um mini-curso de 3 linhas de como a
224
+ criptografia funciona) e salva tudo โ€” das prรณximas vezes vocรช cai direto no
225
+ chat. Refaรงa quando quiser com `ciphermesh --setup`; pule em scripts/CI com
226
+ `--no-onboard`.
199
227
 
200
- ### Arquivo de config
228
+ O CipherMesh tambรฉm lembra a sua **รบltima sessรฃo**: o prompt de servidor passa
229
+ a ter como padrรฃo onde vocรช estava, e depois de conectar vocรช volta sozinho
230
+ para a รบltima sala (salas privadas ficam de fora โ€” o nome delas nunca toca o
231
+ disco). Para comeรงar do zero: `ciphermesh --fresh`.
201
232
 
202
- Crie um `~/.ciphermesh/config.json` para definir padrรตes e pular os prompts. Todas as chaves sรฃo opcionais (chaves desconhecidas sรฃo ignoradas):
233
+ O wizard grava o `~/.ciphermesh/config.json` โ€” que vocรช tambรฉm pode editar na
234
+ mรฃo. Todas as chaves sรฃo opcionais (chaves desconhecidas sรฃo ignoradas):
203
235
 
204
236
  ```json
205
237
  {
@@ -212,6 +244,7 @@ Crie um `~/.ciphermesh/config.json` para definir padrรตes e pular os prompts. To
212
244
  "cover": "constant",
213
245
  "theme": "matrix",
214
246
  "autoAway": 10,
247
+ "autoLock": 5,
215
248
  "dnd": "22:00-08:00"
216
249
  }
217
250
  ```
package/bin/ciphermesh.js CHANGED
@@ -19,6 +19,11 @@ Usage:
19
19
  Options:
20
20
  -h, --help show this help
21
21
  -v, --version show the version
22
+
23
+ Client options:
24
+ --setup re-run the first-run setup wizard
25
+ --no-onboard skip the first-run wizard (scripts/CI)
26
+ --fresh forget the last session (server/room) and start clean
22
27
  `;
23
28
 
24
29
  const arg = process.argv[2];
@@ -448,7 +448,15 @@ Types:
448
448
  | `peer_left` | Server -> Clients | A peer left |
449
449
  | `key_exchange` | Client -> Server -> Client | Public key exchange between peers |
450
450
  | `encrypted_message` | Client -> Server -> Client | Encrypted message |
451
- | `error` | Server -> Client | Error (duplicate nickname, etc.) |
451
+ | `change_room` | Client -> Server | Legacy single-room switch: leave every room, enter one (+ optional `roomAuthPk` when creating a private room) |
452
+ | `room_changed` | Server -> Client | Room switch confirmed (+ `private` flag) |
453
+ | `join_room` | Client -> Server | Multi-room: join an ADDITIONAL room, keeping current ones (same `roomAuthPk` option) |
454
+ | `room_joined` | Server -> Client | Additive join confirmed (`room`, `peers`, `private`, `roomOwner`) |
455
+ | `leave_room` / `room_left` | Client <-> Server | Leave one room (the last one is refused โ€” a session is always somewhere) |
456
+ | `list_rooms` / `room_list` | Client <-> Server | Room list (each room carries a `private` flag) |
457
+ | `room_challenge` | Server -> Client | Target room is private โ€” sign this nonce (see 6.9) |
458
+ | `room_auth` | Client -> Server | Ed25519 signature proving password knowledge (see 6.9) |
459
+ | `error` | Server -> Client | Error (duplicate nickname, wrong room password, etc.) |
452
460
  | `ping` / `pong` | Bidirectional | Heartbeat |
453
461
 
454
462
  #### `src/protocol/validators.js` โ€” Validation
@@ -733,6 +741,41 @@ fingerprint = SHA-256(publicKey)
733
741
  = "A1B2:C3D4:E5F6:7890"
734
742
  ```
735
743
 
744
+ ### 6.9 Private Rooms (password-protected, zero-knowledge)
745
+
746
+ `/create <room> <password>` creates a room the server can gate **without ever
747
+ seeing the password** (`src/crypto/RoomKey.js`):
748
+
749
+ ```
750
+ seed(64B) = Argon2id(password, salt = BLAKE2b("ciphermesh/room-v1:" + room))
751
+ โ”œโ”€โ”€ authSeed (32B) โ†’ Ed25519 keypair (join authentication)
752
+ โ””โ”€โ”€ roomKey (32B) โ†’ secretbox key (room content layer)
753
+ ```
754
+
755
+ **Join authentication (challenge-response):**
756
+ ```
757
+ 1. Creator sends change_room + roomAuthPk (the Ed25519 PUBLIC key).
758
+ The server stores it as the room's verifier โ€” in memory only.
759
+ 2. A joiner sends change_room โ†’ server replies room_challenge { nonce }.
760
+ 3. The joiner signs "ciphermesh/room-auth-v1:room:nonce:sessionId" with the
761
+ password-derived secret key โ†’ room_auth { signature }.
762
+ 4. The server verifies against the stored verifier. Wrong password โ‡’ a
763
+ different keypair โ‡’ invalid signature โ‡’ ROOM_AUTH_FAILED.
764
+ ```
765
+
766
+ The signature binds room, nonce **and** sessionId, so it can't be replayed by
767
+ another connection. Wrong-password attempts are throttled per connection, and
768
+ each guess costs the attacker a full Argon2id derivation client-side. When the
769
+ last member leaves, the room dies and the verifier with it โ€” same ephemeral
770
+ lifecycle as public rooms.
771
+
772
+ **Content layer:** every payload sent in a private room is wrapped as
773
+ `{ rk: 1, n, c }` โ€” `crypto_secretbox(payload, roomKey)` โ€” *before* the normal
774
+ pairwise encryption (ratchet/sealed sender). So even a malicious relay that
775
+ skipped verification and injected a member could not read the room: reading
776
+ requires the password. The outer layer already pads ciphertexts, so the
777
+ wrapper adds no metadata leak.
778
+
736
779
  ---
737
780
 
738
781
  ## 7. Handshake Protocol
@@ -0,0 +1,69 @@
1
+ # CipherMesh Plugin API
2
+
3
+ CipherMesh loads user plugins at startup from `~/.ciphermesh/plugins/*.js` and
4
+ routes unknown slash-commands to them. Plugins work in both relay and P2P mode.
5
+
6
+ ## Quick start
7
+
8
+ ```bash
9
+ mkdir -p ~/.ciphermesh/plugins
10
+ cp examples/plugins/roll.js examples/plugins/poll.js ~/.ciphermesh/plugins/
11
+ ```
12
+
13
+ Restart the client โ€” `/plugins` lists what loaded, and `/roll 2d20+3` /
14
+ `/poll Pizza tonight? | yes | obviously` just work.
15
+
16
+ ## Plugin format
17
+
18
+ A plugin is an ES module whose **default export** is:
19
+
20
+ ```js
21
+ export default {
22
+ name: 'roll', // required, unique
23
+ description: 'Roll dice', // optional, shown by /plugins
24
+ commands: {
25
+ // key = command name (with or without the leading slash)
26
+ roll(args) {
27
+ // args: string[] โ€” everything typed after the command, split on spaces
28
+ return { send: '๐ŸŽฒ 4' };
29
+ },
30
+ },
31
+ };
32
+ ```
33
+
34
+ Files that fail to import, or lack `name`/`commands`, are skipped silently.
35
+
36
+ ## Handler return values
37
+
38
+ | Return | Effect |
39
+ |--------|--------|
40
+ | `{ send: '<text>' }` | The text is **sent to the current room** as a normal end-to-end-encrypted message (and echoed locally). Markdown and multi-line text work. |
41
+ | `{ info: '<text>' }` | Shown **only locally** as an info line. |
42
+ | `'<text>'` (plain string) | Same as `{ info }` โ€” the original API, still supported. |
43
+ | `null` / `undefined` / throw | Treated as "not handled": the user sees *Unknown command*. |
44
+
45
+ Handlers are synchronous โ€” return the final value directly.
46
+
47
+ ## Precedence and naming
48
+
49
+ Built-in commands always win: a plugin command named `/help` or `/join` is
50
+ never reached. Plugin commands are only tried when no built-in matches. If two
51
+ plugins register the same command, the one loaded last wins (load order is the
52
+ directory listing).
53
+
54
+ ## Security model โ€” read this
55
+
56
+ A plugin is **arbitrary JavaScript running inside your chat process**, with
57
+ your privileges and full access to your keys in memory. There is no sandbox.
58
+
59
+ - Only install plugins you wrote or read line-by-line.
60
+ - Treat a plugin file like you treat `curl | sh`.
61
+ - Plugins are never synced, auto-updated or downloaded by CipherMesh โ€” the
62
+ only way code gets into `~/.ciphermesh/plugins/` is you putting it there.
63
+
64
+ ## Included examples
65
+
66
+ - [`examples/plugins/roll.js`](../examples/plugins/roll.js) โ€” dice roller,
67
+ D&D notation (`/roll 2d20+3`), result goes to the room.
68
+ - [`examples/plugins/poll.js`](../examples/plugins/poll.js) โ€” quick poll
69
+ (`/poll question | opt A | opt B`), voted with the built-in `/react`.
@@ -0,0 +1,28 @@
1
+ // CipherMesh example plugin: quick poll voted with reactions.
2
+ // Install: cp poll.js ~/.ciphermesh/plugins/
3
+ const NUMBERS = ['1๏ธโƒฃ', '2๏ธโƒฃ', '3๏ธโƒฃ', '4๏ธโƒฃ', '5๏ธโƒฃ', '6๏ธโƒฃ', '7๏ธโƒฃ', '8๏ธโƒฃ', '9๏ธโƒฃ'];
4
+
5
+ export default {
6
+ name: 'poll',
7
+ description: 'Create a poll: /poll <question> | <option> | <option> [| โ€ฆ]',
8
+ commands: {
9
+ poll(args) {
10
+ const raw = args.join(' ');
11
+ if (!raw.includes('|')) {
12
+ return { info: 'Usage: /poll <question> | <option> | <option> [| โ€ฆ]' };
13
+ }
14
+ const [question, ...options] = raw
15
+ .split('|')
16
+ .map((s) => s.trim())
17
+ .filter(Boolean);
18
+ if (!question || options.length < 2 || options.length > NUMBERS.length) {
19
+ return { info: `A poll needs a question and 2-${NUMBERS.length} options.` };
20
+ }
21
+ const lines = [`๐Ÿ“Š **${question}**`];
22
+ options.forEach((opt, i) => lines.push(`${NUMBERS[i]} ${opt}`));
23
+ lines.push(`Vote by reacting โ€” e.g. /react ${NUMBERS[0]}`);
24
+ // `send` โ†’ delivered to the whole room as a normal E2EE message.
25
+ return { send: lines.join('\n') };
26
+ },
27
+ },
28
+ };
@@ -0,0 +1,24 @@
1
+ // CipherMesh example plugin: dice roller (D&D notation).
2
+ // Install: cp roll.js ~/.ciphermesh/plugins/
3
+ export default {
4
+ name: 'roll',
5
+ description: 'Roll dice: /roll [NdM+K] โ€” e.g. /roll, /roll d20, /roll 2d20+3',
6
+ commands: {
7
+ roll(args) {
8
+ const spec = (args[0] || 'd6').toLowerCase();
9
+ const m = spec.match(/^(\d{0,2})d(\d{1,4})([+-]\d{1,4})?$/);
10
+ if (!m) {
11
+ return { info: 'Usage: /roll [NdM(+K)] โ€” e.g. /roll 2d20+3' };
12
+ }
13
+ const count = Math.min(parseInt(m[1] || '1', 10) || 1, 20);
14
+ const sides = Math.max(2, parseInt(m[2], 10));
15
+ const modifier = parseInt(m[3] || '0', 10);
16
+ const rolls = Array.from({ length: count }, () => 1 + Math.floor(Math.random() * sides));
17
+ const total = rolls.reduce((a, b) => a + b, 0) + modifier;
18
+ const detail =
19
+ count > 1 || modifier ? ` (${rolls.join(' + ')}${modifier ? ` ${m[3]}` : ''})` : '';
20
+ // `send` โ†’ delivered to the whole room as a normal E2EE message.
21
+ return { send: `๐ŸŽฒ rolled ${spec}: **${total}**${detail}` };
22
+ },
23
+ },
24
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ciphermesh",
3
- "version": "2.0.0",
3
+ "version": "2.2.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",
@@ -11,6 +11,7 @@
11
11
  "bin",
12
12
  "src",
13
13
  "docs",
14
+ "examples",
14
15
  "README.md",
15
16
  "README.pt-BR.md",
16
17
  "LICENSE"
@@ -54,7 +55,7 @@
54
55
  "bonjour-service": "1.4.3",
55
56
  "boxen": "8.0.1",
56
57
  "chalk": "^5.4.1",
57
- "figlet": "1.11.2",
58
+ "figlet": "1.11.3",
58
59
  "gradient-string": "3.0.0",
59
60
  "jimp": "1.6.1",
60
61
  "node-notifier": "10.0.1",
@@ -64,7 +65,7 @@
64
65
  },
65
66
  "devDependencies": {
66
67
  "@eslint/js": "10.0.1",
67
- "eslint": "10.7.0",
68
+ "eslint": "10.8.0",
68
69
  "globals": "^17.7.0",
69
70
  "prettier": "^3.9.5"
70
71
  },