diff --git a/chat-cli/README.md b/chat-cli/README.md index 481def5..aff6278 100644 --- a/chat-cli/README.md +++ b/chat-cli/README.md @@ -6,7 +6,8 @@ A terminal chat application built with [ratatui](https://ratatui.rs/) using the - 💬 End-to-end encrypted messaging using the Double Ratchet algorithm - 📁 File-based transport for local simulation (no network required) -- 💾 Persistent storage (SQLite) +- 💾 Persistent storage (SQLite + JSON state) +- 🔄 Multiple chat support with chat switching - 🖥️ Beautiful terminal UI with ratatui ## Usage @@ -40,11 +41,19 @@ cargo run -p chat-cli -- bob | `/help` | Show available commands | | `/intro` | Generate and display your introduction bundle | | `/connect ` | Connect to a user using their introduction bundle | -| `/peers` | List available peers | +| `/chats` | List all your established chats | +| `/switch ` | Switch to a different chat | +| `/delete ` | Delete a chat (removes session and crypto state) | +| `/peers` | List transport-level peers (users with inbox directories) | | `/status` | Show connection status and your address | -| `/clear` | Clear message history | +| `/clear` | Clear current chat's message history | | `/quit` or `Esc` or `Ctrl+C` | Exit the application | +#### `/peers` vs `/chats` + +- **`/peers`**: Shows users whose CLI has been started (have inbox directories). These are potential contacts you *could* message. +- **`/chats`**: Shows users you have an **encrypted session** with (via `/connect`). These are active conversations. + ### Sending Messages Simply type your message and press Enter. Messages are automatically encrypted and delivered via file-based transport. @@ -62,8 +71,13 @@ Messages are passed between users via files in a shared directory: ### Storage -User data (identity keys, chat state) is stored in SQLite databases at: -- `chat-cli-data/.db` +Data is stored in the `chat-cli-data/` directory: + +| File | Purpose | +|------|---------| +| `.db` | SQLite database for identity keys, inbox keys, chat metadata, and Double Ratchet state | +| `_state.json` | CLI state: username↔chat mappings, message history, active chat | +| `transport//` | Inbox directory for receiving messages | ### Encryption @@ -88,6 +102,13 @@ $ cargo run -p chat-cli -- bob # Connected! Bob sends "Hello!" automatically # Now type messages in either terminal to chat! + +# To see your chats: +/chats +# Output: alice (active) + +# To switch between chats (if you have multiple): +/switch alice ``` ## Architecture diff --git a/chat-cli/src/app.rs b/chat-cli/src/app.rs index c5b3cd1..fa14100 100644 --- a/chat-cli/src/app.rs +++ b/chat-cli/src/app.rs @@ -206,6 +206,32 @@ impl ChatApp { } } + /// Delete a chat session. + pub fn delete_chat(&mut self, remote_user: &str) -> Result<()> { + if let Some(session) = self.state.sessions.remove(remote_user) { + // Also delete from the library's storage + if let Err(e) = self.manager.delete_chat(&session.chat_id) { + // Log but don't fail - the CLI state is already updated + self.status = format!("Warning: failed to delete crypto state: {}", e); + } + + // If we deleted the active chat, clear it + if self.state.active_chat.as_deref() == Some(remote_user) { + // Switch to another chat if available, otherwise clear + self.state.active_chat = self.state.sessions.keys().next().cloned(); + } + + self.save_state()?; + self.status = format!("Deleted chat with {}", remote_user); + Ok(()) + } else { + Err(anyhow::anyhow!( + "No chat with {}. Use /chats to list available chats.", + remote_user + )) + } + } + /// Send a message in the current chat. pub fn send_message(&mut self, content: &str) -> Result<()> { let active = self @@ -324,6 +350,8 @@ impl ChatApp { self.add_system_message("/connect - Connect to a user"); self.add_system_message("/chats - List all chats"); self.add_system_message("/switch - Switch to chat with user"); + self.add_system_message("/delete - Delete chat with user"); + self.add_system_message("/peers - List transport peers"); self.add_system_message("/status - Show connection status"); self.add_system_message("/clear - Clear current chat messages"); self.add_system_message("/quit or Esc or Ctrl+C - Exit"); @@ -370,6 +398,13 @@ impl ChatApp { self.switch_chat(args)?; Ok(Some(format!("Switched to {}", args))) } + "/delete" => { + if args.is_empty() { + return Ok(Some("Usage: /delete ".to_string())); + } + self.delete_chat(args)?; + Ok(Some(format!("Deleted chat with {}", args))) + } "/peers" => { let peers = self.transport.list_peers(); if peers.is_empty() {