A Rust CLI for publishing thought records. Designed to work with thought.stream.

Rename CLI from 'think' to 'thought' and add stream command

- Rename main binary from 'think' to 'thought'
- Add 'thought stream' subcommand (replaces 'thought interactive --tui')
- Update all references and help text to use 'thought'
- Remove separate thought.rs binary in favor of single unified CLI
- Update README to reflect new command structure

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

+26 -84
+1 -5
Cargo.toml
··· 5 5 description = "CLI tool for publishing stream.thought.blip records to ATProto" 6 6 7 7 [[bin]] 8 - name = "think" 9 - path = "src/main.rs" 10 - 11 - [[bin]] 12 8 name = "thought" 13 - path = "src/thought.rs" 9 + path = "src/main.rs" 14 10 15 11 [dependencies] 16 12 clap = { version = "4.4", features = ["derive"] }
+12 -20
README.md
··· 1 - # Think CLI 1 + # Thought CLI 2 2 3 3 A terminal UI for real-time streaming and posting to the ATProto/Bluesky network using `stream.thought.blip` records. 4 4 ··· 20 20 21 21 ### 2. Login 22 22 ```bash 23 - ./target/release/think login 23 + ./target/release/thought login 24 24 ``` 25 25 Enter your Bluesky handle/email and app password when prompted. 26 26 ··· 32 32 33 33 ## Commands 34 34 35 - ### think 35 + ### thought 36 36 The main CLI tool with multiple modes: 37 37 38 38 ```bash 39 39 # Login to Bluesky 40 - think login 40 + thought login 41 41 42 42 # Post a single blip 43 - think "Your thought here" 43 + thought "Your thought here" 44 44 45 45 # Enter simple REPL mode 46 - think interactive 46 + thought interactive 47 47 48 48 # Enter full TUI mode with live feed 49 - think interactive --tui 49 + thought stream 50 50 51 51 # Logout and clear credentials 52 - think logout 52 + thought logout 53 53 ``` 54 54 55 - ### thought 56 - Direct entry to the streaming TUI: 57 - 58 - ```bash 59 - # Launch the TUI directly (same as think interactive --tui) 60 - thought stream 61 - ``` 62 55 63 56 ## TUI Interface 64 57 ··· 86 79 cargo build --release 87 80 ``` 88 81 89 - The binaries will be available at: 90 - - `target/release/think` 82 + The binary will be available at: 91 83 - `target/release/thought` 92 84 93 85 ### App Password Setup 94 86 1. Go to [Bluesky Settings](https://bsky.app/settings/app-passwords) 95 87 2. Generate a new app password 96 - 3. Use this password (not your main password) when running `think login` 88 + 3. Use this password (not your main password) when running `thought login` 97 89 98 90 ## Configuration 99 91 ··· 113 105 114 106 ### Post a quick thought 115 107 ```bash 116 - think "Just built something cool!" 108 + thought "Just built something cool!" 117 109 ``` 118 110 119 111 ### Start a streaming session ··· 124 116 125 117 ### Interactive REPL 126 118 ```bash 127 - think interactive 119 + thought interactive 128 120 # Enter multiple thoughts line by line 129 121 ``` 130 122
+1 -1
src/client.rs
··· 94 94 95 95 pub async fn publish_blip(&self, content: &str) -> Result<String> { 96 96 let session = self.session.as_ref() 97 - .context("Not authenticated. Please run 'think login' first.")?; 97 + .context("Not authenticated. Please run 'thought login' first.")?; 98 98 99 99 let record = BlipRecord { 100 100 record_type: "stream.thought.blip".to_string(),
+12 -9
src/main.rs
··· 12 12 use credentials::{CredentialStore, Credentials}; 13 13 14 14 #[derive(Parser)] 15 - #[command(name = "think")] 15 + #[command(name = "thought")] 16 16 #[command(about = "CLI tool for publishing stream.thought.blip records")] 17 17 #[command(version = "0.1.0")] 18 18 struct Cli { ··· 35 35 #[arg(long)] 36 36 tui: bool, 37 37 }, 38 + /// Enter the thought stream (TUI mode with live feed) 39 + Stream, 38 40 } 39 41 40 42 fn prompt_for_input(prompt: &str) -> Result<String> { ··· 53 55 let store = CredentialStore::new()?; 54 56 55 57 if store.exists() { 56 - println!("You are already logged in. Use 'think logout' to clear credentials first."); 58 + println!("You are already logged in. Use 'thought logout' to clear credentials first."); 57 59 return Ok(()); 58 60 } 59 61 ··· 102 104 let store = CredentialStore::new()?; 103 105 104 106 let credentials = store.load()? 105 - .context("Not logged in. Please run 'think login' first.")?; 107 + .context("Not logged in. Please run 'thought login' first.")?; 106 108 107 109 let mut client = AtProtoClient::new(&credentials.pds_uri); 108 110 client.login(&credentials).await?; ··· 117 119 let store = CredentialStore::new()?; 118 120 119 121 let credentials = store.load()? 120 - .context("Not logged in. Please run 'think login' first.")?; 122 + .context("Not logged in. Please run 'thought login' first.")?; 121 123 122 124 let mut client = AtProtoClient::new(&credentials.pds_uri); 123 125 client.login(&credentials).await?; ··· 135 137 Some(Commands::Login) => handle_login().await?, 136 138 Some(Commands::Logout) => handle_logout().await?, 137 139 Some(Commands::Interactive { tui }) => handle_interactive(tui).await?, 140 + Some(Commands::Stream) => handle_interactive(true).await?, 138 141 None => { 139 142 if let Some(message) = cli.message { 140 143 handle_publish(&message).await?; 141 144 } else { 142 145 println!("Usage:"); 143 - println!(" think login # Login to Bluesky"); 144 - println!(" think logout # Logout and clear credentials"); 145 - println!(" think interactive # Enter simple REPL mode"); 146 - println!(" think interactive --tui # Enter TUI mode with live feed"); 147 - println!(" think \"message\" # Publish a blip"); 146 + println!(" thought login # Login to Bluesky"); 147 + println!(" thought logout # Logout and clear credentials"); 148 + println!(" thought interactive # Enter simple REPL mode"); 149 + println!(" thought stream # Enter TUI mode with live feed"); 150 + println!(" thought \"message\" # Publish a blip"); 148 151 } 149 152 } 150 153 }
-49
src/thought.rs
··· 1 - mod client; 2 - mod credentials; 3 - mod interactive; 4 - mod jetstream; 5 - mod tui; 6 - 7 - use anyhow::{Context, Result}; 8 - use clap::Parser; 9 - 10 - use client::AtProtoClient; 11 - use credentials::CredentialStore; 12 - 13 - #[derive(Parser)] 14 - #[command(name = "thought")] 15 - #[command(about = "Stream your thoughts to the bluesky firehose", long_about = None)] 16 - #[command(version = "0.1.0")] 17 - struct Cli { 18 - /// Subcommand 19 - #[command(subcommand)] 20 - command: Option<Commands>, 21 - } 22 - 23 - #[derive(clap::Subcommand)] 24 - enum Commands { 25 - /// Enter the thought stream (TUI mode) 26 - Stream, 27 - } 28 - 29 - #[tokio::main] 30 - async fn main() -> Result<()> { 31 - let cli = Cli::parse(); 32 - 33 - match cli.command { 34 - Some(Commands::Stream) | None => { 35 - // Always launch TUI mode 36 - let store = CredentialStore::new()?; 37 - 38 - let credentials = store.load()? 39 - .context("Not logged in. Please run 'think login' first to set up your credentials.")?; 40 - 41 - let mut client = AtProtoClient::new(&credentials.pds_uri); 42 - client.login(&credentials).await?; 43 - 44 - interactive::run_interactive_mode(&client, true).await?; 45 - } 46 - } 47 - 48 - Ok(()) 49 - }