diff --git a/CHANGELOG.md b/CHANGELOG.md index a59f0e4a..fbe7164c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.0.2] - 2026-01-20 + +### Fixed +- Disabled automatic RLM mode switching; use /rlm or /aleph to enter RLM mode. + ## [0.0.1] - 2026-01-19 ### Added @@ -84,7 +89,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Hooks system and config profiles - Example skills and launch assets -[Unreleased]: https://github.com/Hmbown/DeepSeek-CLI/compare/v0.0.1...HEAD +[Unreleased]: https://github.com/Hmbown/DeepSeek-CLI/compare/v0.0.2...HEAD +[0.0.2]: https://github.com/Hmbown/DeepSeek-CLI/releases/tag/v0.0.2 [0.0.1]: https://github.com/Hmbown/DeepSeek-CLI/releases/tag/v0.0.1 [0.1.9]: https://github.com/Hmbown/DeepSeek-CLI/compare/v0.1.8...v0.1.9 [0.1.8]: https://github.com/Hmbown/DeepSeek-CLI/compare/v0.1.7...v0.1.8 diff --git a/Cargo.lock b/Cargo.lock index 5b6033a9..a09e7f77 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -611,7 +611,7 @@ dependencies = [ [[package]] name = "deepseek-tui" -version = "0.1.0" +version = "0.0.2" dependencies = [ "anyhow", "arboard", diff --git a/Cargo.toml b/Cargo.toml index c758b539..2952f1f0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "deepseek-tui" -version = "0.1.0" +version = "0.0.2" edition = "2024" description = "Unofficial DeepSeek CLI - Just run 'deepseek' to start chatting" license = "MIT" diff --git a/src/commands/config.rs b/src/commands/config.rs index 5bc3410d..fe1bc15b 100644 --- a/src/commands/config.rs +++ b/src/commands/config.rs @@ -125,6 +125,9 @@ pub fn set_config(app: &mut App, args: Option<&str>) -> CommandResult { compaction.model = app.model.clone(); action = Some(AppAction::UpdateCompaction(compaction)); } + "auto_rlm" => { + app.auto_rlm = settings.auto_rlm; + } "show_thinking" | "thinking" => { app.show_thinking = settings.show_thinking; app.mark_history_updated(); diff --git a/src/commands/core.rs b/src/commands/core.rs index dfe50d8d..360e3901 100644 --- a/src/commands/core.rs +++ b/src/commands/core.rs @@ -3,7 +3,7 @@ use std::fmt::Write; use crate::tools::plan::PlanState; -use crate::tui::app::{App, AppAction}; +use crate::tui::app::{App, AppAction, AppMode}; use crate::tui::views::{HelpView, ModalKind, SubAgentsView}; use super::CommandResult; @@ -96,3 +96,80 @@ Docs: https://platform.deepseek.com/docs\n\n\ Tip: API keys are available in the dashboard console.", ) } + +/// Show home dashboard with stats and quick actions +pub fn home_dashboard(app: &mut App) -> CommandResult { + let mut stats = String::new(); + + // Basic info + let _ = writeln!(stats, "DeepSeek CLI Home Dashboard"); + let _ = writeln!(stats, "============================================"); + + // Model & mode + let _ = writeln!(stats, "Model: {}", app.model); + let _ = writeln!(stats, "Mode: {}", app.mode.label()); + let _ = writeln!(stats, "Workspace: {}", app.workspace.display()); + + // Session stats + let history_count = app.history.len(); + let total_tokens = app.total_conversation_tokens; + let queued_messages = app.queued_messages.len(); + let _ = writeln!(stats, "History: {} messages", history_count); + let _ = writeln!(stats, "Tokens: {} (session)", total_tokens); + if queued_messages > 0 { + let _ = writeln!(stats, "Queued: {} messages", queued_messages); + } + + // Sub-agents + let subagent_count = app.subagent_cache.len(); + if subagent_count > 0 { + let _ = writeln!(stats, "Sub-agents: {} active", subagent_count); + } + + // Active skill + if let Some(skill) = &app.active_skill { + let _ = writeln!(stats, "Skill: {} (active)", skill); + } + + // Quick actions section + let _ = writeln!(stats, "\nQuick Actions"); + let _ = writeln!(stats, "--------------------------------------------"); + let _ = writeln!(stats, "/deepseek - Dashboard & API links"); + let _ = writeln!(stats, "/skills - List available skills"); + let _ = writeln!(stats, "/config - Show current configuration"); + let _ = writeln!(stats, "/settings - Show persistent settings"); + let _ = writeln!(stats, "/model - Switch or view model"); + let _ = writeln!(stats, "/subagents - List sub-agent status"); + let _ = writeln!(stats, "/help - Show help"); + + // Mode-specific tips + let _ = writeln!(stats, "\nMode Tips"); + let _ = writeln!(stats, "--------------------------------------------"); + match app.mode { + AppMode::Normal => { + let _ = writeln!(stats, "Normal mode - Chat with the assistant"); + } + AppMode::Agent => { + let _ = writeln!(stats, "Agent mode - Use tools for autonomous tasks"); + let _ = writeln!(stats, " Type /yolo to enable full tool access"); + } + AppMode::Yolo => { + let _ = writeln!(stats, "YOLO mode - Full tool access, no approvals"); + let _ = writeln!(stats, " Be careful with destructive operations!"); + } + AppMode::Plan => { + let _ = writeln!(stats, "Plan mode - Design before implementing"); + let _ = writeln!(stats, " Use /plan to create structured checklists"); + } + AppMode::Rlm => { + let _ = writeln!(stats, "RLM mode - Recursive language model sandbox"); + let _ = writeln!(stats, " Use /repl to toggle REPL input"); + } + AppMode::Duo => { + let _ = writeln!(stats, "Duo mode - Dialectical autocoding"); + let _ = writeln!(stats, " Player-coach loop for complex tasks"); + } + } + + CommandResult::message(stats) +} diff --git a/src/commands/mod.rs b/src/commands/mod.rs index b446c9fc..a625bda8 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -7,6 +7,7 @@ mod config; mod core; mod debug; mod init; +mod note; mod queue; pub mod rlm; mod session; @@ -120,6 +121,18 @@ pub const COMMANDS: &[CommandInfo] = &[ description: "Show DeepSeek dashboard and docs links", usage: "/deepseek", }, + CommandInfo { + name: "home", + aliases: &["stats", "overview"], + description: "Show home dashboard with stats and quick actions", + usage: "/home", + }, + CommandInfo { + name: "note", + aliases: &[], + description: "Append note to persistent notes file (.deepseek/notes.md)", + usage: "/note ", + }, // Session commands CommandInfo { name: "save", @@ -288,6 +301,8 @@ pub fn execute(cmd: &str, app: &mut App) -> CommandResult { "queue" | "queued" => queue::queue(app, arg), "subagents" | "agents" => core::subagents(app), "deepseek" | "dashboard" | "api" => core::deepseek_links(), + "home" | "stats" | "overview" => core::home_dashboard(app), + "note" => note::note(app, arg), // Session commands "save" => session::save(app, arg), diff --git a/src/commands/note.rs b/src/commands/note.rs new file mode 100644 index 00000000..42e710da --- /dev/null +++ b/src/commands/note.rs @@ -0,0 +1,59 @@ +//! Note command: append to persistent notes file + +use std::fs; +use std::io::Write; +use crate::tui::app::App; + +use super::CommandResult; + +/// Append a note to the persistent notes file +pub fn note(app: &mut App, content: Option<&str>) -> CommandResult { + let note_content = match content { + Some(c) => c.trim(), + None => { + return CommandResult::error("Usage: /note "); + } + }; + + if note_content.is_empty() { + return CommandResult::error("Note content cannot be empty"); + } + + // Determine notes path: workspace/.deepseek/notes.md + let notes_path = app.workspace.join(".deepseek").join("notes.md"); + + // Ensure parent directory exists + if let Some(parent) = notes_path.parent() { + if let Err(e) = fs::create_dir_all(parent) { + return CommandResult::error(format!( + "Failed to create notes directory: {e}" + )); + } + } + + // Append to notes file + let mut file = match fs::OpenOptions::new() + .create(true) + .append(true) + .open(¬es_path) + { + Ok(f) => f, + Err(e) => { + return CommandResult::error(format!( + "Failed to open notes file: {e}" + )); + } + }; + + // Write separator and note content + if let Err(e) = writeln!(file, "\n---\n{}", note_content) { + return CommandResult::error(format!( + "Failed to write note: {e}" + )); + } + + CommandResult::message(format!( + "Note appended to {}", + notes_path.display() + )) +} diff --git a/src/settings.rs b/src/settings.rs index 36807da1..784209bc 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -15,6 +15,8 @@ pub struct Settings { pub theme: String, /// Auto-compact conversations when they get long pub auto_compact: bool, + /// Auto-switch to RLM mode when large inputs are detected + pub auto_rlm: bool, /// Show thinking blocks from the model pub show_thinking: bool, /// Show detailed tool output @@ -34,6 +36,7 @@ impl Default for Settings { Self { theme: "default".to_string(), auto_compact: true, + auto_rlm: false, show_thinking: true, show_tool_details: true, default_mode: "agent".to_string(), @@ -99,6 +102,9 @@ impl Settings { "auto_compact" | "compact" => { self.auto_compact = parse_bool(value)?; } + "auto_rlm" => { + self.auto_rlm = parse_bool(value)?; + } "show_thinking" | "thinking" => { self.show_thinking = parse_bool(value)?; } @@ -154,6 +160,7 @@ impl Settings { lines.push("─────────────────────────────".to_string()); lines.push(format!(" theme: {}", self.theme)); lines.push(format!(" auto_compact: {}", self.auto_compact)); + lines.push(format!(" auto_rlm: {}", self.auto_rlm)); lines.push(format!(" show_thinking: {}", self.show_thinking)); lines.push(format!(" show_tool_details: {}", self.show_tool_details)); lines.push(format!(" default_mode: {}", self.default_mode)); @@ -179,6 +186,7 @@ impl Settings { vec![ ("theme", "Color theme: default, dark, light"), ("auto_compact", "Auto-compact conversations: on/off"), + ("auto_rlm", "Auto-switch to RLM mode for large inputs: on/off"), ("show_thinking", "Show model thinking: on/off"), ("show_tool_details", "Show detailed tool output: on/off"), ("default_mode", "Default mode: agent, plan, yolo, rlm, duo"), diff --git a/src/tui/app.rs b/src/tui/app.rs index a1447c45..2ec9b5fa 100644 --- a/src/tui/app.rs +++ b/src/tui/app.rs @@ -176,6 +176,7 @@ pub struct App { pub input_history: Vec, pub history_index: Option, pub auto_compact: bool, + pub auto_rlm: bool, pub show_thinking: bool, pub show_tool_details: bool, #[allow(dead_code)] @@ -335,6 +336,7 @@ impl App { let needs_onboarding = !has_api_key(config); let settings = Settings::load().unwrap_or_else(|_| Settings::default()); let auto_compact = settings.auto_compact; + let auto_rlm = settings.auto_rlm; let show_thinking = settings.show_thinking; let show_tool_details = settings.show_tool_details; let max_input_history = settings.max_input_history; @@ -421,6 +423,7 @@ impl App { input_history: Vec::new(), history_index: None, auto_compact, + auto_rlm, show_thinking, show_tool_details, compact_threshold: 50000, diff --git a/src/tui/ui.rs b/src/tui/ui.rs index c4e9f818..b2032cf4 100644 --- a/src/tui/ui.rs +++ b/src/tui/ui.rs @@ -1234,6 +1234,9 @@ struct AutoRlmLoaded { } fn maybe_auto_switch_to_rlm(app: &mut App, input: &str) -> Option { + if !app.auto_rlm { + return None; + } let already_rlm = app.mode == AppMode::Rlm; let decision = auto_rlm_decision(app, input, already_rlm)?; @@ -3301,6 +3304,20 @@ mod tests { } } + #[test] + fn auto_rlm_is_disabled_when_setting_off() { + let tmp = tempdir().expect("tempdir"); + let mut app = make_test_app_with_workspace(tmp.path().to_path_buf()); + app.auto_rlm = false; + + let content = "a".repeat(AUTO_RLM_PASTE_MIN_CHARS + 5); + let input = format!("Summarize this\n\n{content}"); + let override_query = maybe_auto_switch_to_rlm(&mut app, &input); + + assert!(override_query.is_none()); + assert_ne!(app.mode, AppMode::Rlm); + } + #[test] fn parse_plan_choice_accepts_numbers() { assert_eq!(parse_plan_choice("1"), Some(PlanChoice::ImplementAgent));