feat(v2): complete multi-LLM providers, TUI redesign, and advanced agent features

Multi-LLM Provider Support:
- Add llm-core crate with LlmProvider trait abstraction
- Implement Anthropic Claude API client with streaming
- Implement OpenAI API client with streaming
- Add token counting with SimpleTokenCounter and ClaudeTokenCounter
- Add retry logic with exponential backoff and jitter

Borderless TUI Redesign:
- Rewrite theme system with terminal capability detection (Full/Unicode256/Basic)
- Add provider tabs component with keybind switching [1]/[2]/[3]
- Implement vim-modal input (Normal/Insert/Visual/Command modes)
- Redesign chat panel with timestamps and streaming indicators
- Add multi-provider status bar with cost tracking
- Add Nerd Font icons with graceful ASCII fallbacks
- Add syntax highlighting (syntect) and markdown rendering (pulldown-cmark)

Advanced Agent Features:
- Add system prompt builder with configurable components
- Enhance subagent orchestration with parallel execution
- Add git integration module for safe command detection
- Add streaming tool results via channels
- Expand tool set: AskUserQuestion, TodoWrite, LS, MultiEdit, BashOutput, KillShell
- Add WebSearch with provider abstraction

Plugin System Enhancement:
- Add full agent definition parsing from YAML frontmatter
- Add skill system with progressive disclosure
- Wire plugin hooks into HookManager

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-12-02 17:24:14 +01:00
parent 09c8c9d83e
commit 10c8e2baae
67 changed files with 11444 additions and 626 deletions

View File

@@ -0,0 +1,12 @@
[package]
name = "tools-todo"
version = "0.1.0"
edition.workspace = true
license.workspace = true
rust-version.workspace = true
[dependencies]
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
parking_lot = "0.12"
color-eyre = "0.6"

View File

@@ -0,0 +1,113 @@
//! TodoWrite tool for task list management
use parking_lot::RwLock;
use serde::{Deserialize, Serialize};
use std::sync::Arc;
/// Status of a todo item
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum TodoStatus {
Pending,
InProgress,
Completed,
}
/// A todo item
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Todo {
pub content: String,
pub status: TodoStatus,
pub active_form: String, // Present continuous form for display
}
/// Shared todo list state
#[derive(Debug, Clone, Default)]
pub struct TodoList {
inner: Arc<RwLock<Vec<Todo>>>,
}
impl TodoList {
pub fn new() -> Self {
Self::default()
}
/// Replace all todos with new list
pub fn write(&self, todos: Vec<Todo>) {
*self.inner.write() = todos;
}
/// Get current todos
pub fn read(&self) -> Vec<Todo> {
self.inner.read().clone()
}
/// Get the current in-progress task (for status display)
pub fn current_task(&self) -> Option<String> {
self.inner.read()
.iter()
.find(|t| t.status == TodoStatus::InProgress)
.map(|t| t.active_form.clone())
}
/// Get summary stats
pub fn stats(&self) -> (usize, usize, usize) {
let todos = self.inner.read();
let pending = todos.iter().filter(|t| t.status == TodoStatus::Pending).count();
let in_progress = todos.iter().filter(|t| t.status == TodoStatus::InProgress).count();
let completed = todos.iter().filter(|t| t.status == TodoStatus::Completed).count();
(pending, in_progress, completed)
}
/// Format todos for display
pub fn format_display(&self) -> String {
let todos = self.inner.read();
if todos.is_empty() {
return "No tasks".to_string();
}
todos.iter().enumerate().map(|(i, t)| {
let status_icon = match t.status {
TodoStatus::Pending => "",
TodoStatus::InProgress => "",
TodoStatus::Completed => "",
};
format!("{}. {} {}", i + 1, status_icon, t.content)
}).collect::<Vec<_>>().join("\n")
}
}
/// Parse todos from JSON tool input
pub fn parse_todos(input: &serde_json::Value) -> color_eyre::Result<Vec<Todo>> {
let todos = input.get("todos")
.ok_or_else(|| color_eyre::eyre::eyre!("Missing 'todos' field"))?;
serde_json::from_value(todos.clone())
.map_err(|e| color_eyre::eyre::eyre!("Invalid todos format: {}", e))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_todo_list() {
let list = TodoList::new();
list.write(vec![
Todo {
content: "First task".to_string(),
status: TodoStatus::Completed,
active_form: "Completing first task".to_string(),
},
Todo {
content: "Second task".to_string(),
status: TodoStatus::InProgress,
active_form: "Working on second task".to_string(),
},
]);
assert_eq!(list.current_task(), Some("Working on second task".to_string()));
assert_eq!(list.stats(), (0, 1, 1));
}
}