From a40a0e7a8283f76d09bd7b9b8d0b0756c2910d14 Mon Sep 17 00:00:00 2001 From: Cameron Pfiffer Date: Tue, 30 Sep 2025 10:46:20 -0700 Subject: [PATCH] Persist refreshed session tokens in TUI mode When publish_blip() automatically refreshes expired tokens, the updated session was only stored in memory. This caused subsequent publishes to fail with the same expired token error. Now the session is persisted to disk after each successful publish. Also add CLAUDE.md documentation for codebase architecture. --- CLAUDE.md | 113 +++++++++++++++++++++++++++++++++++++++++++++++++++++ src/tui.rs | 7 ++++ 2 files changed, 120 insertions(+) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..6baabcf --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,113 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +"Thought" is a Rust CLI application for real-time streaming and posting to the ATProto network using `stream.thought.blip` records. It functions like an IRC client for Bluesky, featuring a terminal UI (TUI) for live message streaming. + +**Key Technology Stack:** +- Rust with async/await (Tokio runtime) +- ATProto API for authentication and posting +- Jetstream WebSocket for real-time firehose +- Ratatui for terminal UI +- Custom record type: `stream.thought.blip` + +## Building and Running + +```bash +# Build (development) +cargo build + +# Build (release) +cargo build --release + +# Run tests +cargo test + +# Lint +cargo clippy + +# Run the CLI +./target/release/thought + +# Login first-time use +./target/release/thought login + +# Main TUI mode (recommended) +./target/release/thought stream +``` + +## Architecture + +### Module Structure + +- **main.rs**: CLI entry point, command routing, and authentication setup +- **client.rs**: ATProto HTTP client for authentication and posting blips +- **credentials.rs**: Secure credential and session storage (platform-specific config directories) +- **jetstream.rs**: WebSocket client for Jetstream firehose, DID resolution, and message filtering +- **tui.rs**: Ratatui-based terminal UI with scrollable message feed +- **interactive.rs**: Coordinator for TUI and simple REPL modes + +### Authentication Flow + +1. User provides handle/email and app password via `thought login` +2. Credentials stored in platform config directory (macOS: `~/Library/Application Support/com.thoughtstream.think/`) +3. Session tokens (accessJwt/refreshJwt) cached separately in `session.json` +4. Token refresh handled transparently by `create_authenticated_client()` in main.rs:133 +5. Failed refresh triggers re-login with stored credentials + +**Important**: Session management uses both `credentials.json` (username/password/PDS URI) and `session.json` (JWT tokens). The client automatically refreshes tokens before operations. + +### Real-time Message Flow + +1. TUI spawns Jetstream listener in background task +2. Listener connects to `wss://jetstream2.us-west.bsky.network/subscribe` with collection filter +3. Incoming commits filtered for `stream.thought.blip` collection and "create" operations +4. DIDs resolved to handles via `app.bsky.actor.getProfile` (with caching) +5. Messages sent through mpsc channel to TUI for rendering +6. User's own messages filtered out using stored DID + +### TUI Rendering + +- Messages displayed in **reverse chronological order** (newest first) +- Scrollable with arrow keys/PageUp/PageDown +- Auto-scrolls to bottom when at offset 0 +- User's own posts styled in bold green +- Connection status tracked via channel activity +- Maximum 1000 messages buffered + +## Development Notes + +### Credential Storage Paths +- macOS: `~/Library/Application Support/com.thoughtstream.think/` +- Linux: `~/.config/thoughtstream/think/` +- Windows: `%APPDATA%\thoughtstream\think\` + +### Token Refresh Logic +The client implements automatic token refresh (client.rs:106-137): +1. Attempt operation with current access token +2. On 401/ExpiredToken error, call `refresh_session()` +3. Retry operation with new token +4. If refresh fails, clear session and prompt re-login + +### Jetstream Connection +- Attempts simple connection first, then falls back to collection-filtered URL +- Reconnects with 5-second delay on failure +- Uses User-Agent: `think-cli/0.1.0` +- Filters messages on client side even with server-side collection filter + +### Publishing Blips +Records posted with structure: +```json +{ + "$type": "stream.thought.blip", + "content": "", + "createdAt": "" +} +``` +Posted to `com.atproto.repo.createRecord` with user's DID as repo. + +## Note on Julia Dependencies + +The `Project.toml` and `Manifest.toml` files are unrelated to the Rust project—likely leftover artifacts or separate experiments (Franklin static site generator). The actual Rust dependencies are in `Cargo.toml`. diff --git a/src/tui.rs b/src/tui.rs index 677ce22..d0bf244 100644 --- a/src/tui.rs +++ b/src/tui.rs @@ -249,6 +249,13 @@ async fn run_tui_loop( message, true, )); + + // Save updated session (in case token was refreshed) + if let Ok(store) = crate::credentials::CredentialStore::new() { + if let Some(session) = client.get_session() { + let _ = store.store_session(session); + } + } } Err(e) => { // Add error message -- 2.51.2