taskter 0.1.0

Taskter is a terminal Kanban board CLI tool built with Rust.
Documentation
use chrono::Local;
use clap::Parser;
use std::fs;
use std::io::Write;
use std::path::Path;

use taskter::cli::*;
mod agent;
mod store;
mod tools;
mod tui;


#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let cli = Cli::parse();

    match &cli.command {
        Commands::Init => {
            let path = Path::new(".taskter");
            if path.exists() {
                println!("Taskter board already initialized.");
            } else {
                fs::create_dir(path)?;
                fs::write(path.join("description.md"), "# Project Description")?;
                fs::write(path.join("okrs.json"), "[]")?;
                fs::write(path.join("logs.log"), "")?;
                fs::write(path.join("board.json"), r#"{ "tasks": [] }"#)?;
                fs::write(path.join("agents.json"), "[]")?;
                println!("Taskter board initialized.");
            }
        }
        Commands::Add { title, description } => {
            let mut board = store::load_board()?;
            let new_task = store::Task {
                id: board.tasks.len() + 1,
                title: title.clone(),
                description: description.clone(),
                status: store::TaskStatus::ToDo,
                agent_id: None,
                comment: None,
            };
            board.tasks.push(new_task);
            store::save_board(&board)?;
            println!("Task added successfully.");
        }
        Commands::List => {
            let board = store::load_board()?;
            for task in board.tasks {
                println!(
                    "[{}] {} - {:?} - {:?}",
                    task.id,
                    task.title,
                    task.status,
                    task.description.unwrap_or_default()
                );
            }
        }
        Commands::Done { id } => {
            let mut board = store::load_board()?;
            if let Some(task) = board.tasks.iter_mut().find(|t| t.id == *id) {
                task.status = store::TaskStatus::Done;
                store::save_board(&board)?;
                println!("Task {id} marked as done.");
            } else {
                println!("Task with id {id} not found.");
            }
        }
        Commands::Comment { task_id, comment } => {
            let mut board = store::load_board()?;
            if let Some(task) = board.tasks.iter_mut().find(|t| t.id == *task_id) {
                task.comment = Some(comment.clone());
                store::save_board(&board)?;
                println!("Comment added to task {task_id}.");
            } else {
                println!("Task with id {task_id} not found.");
            }
        }
        Commands::Show { what } => match what {
            ShowCommands::Description => {
                let description = fs::read_to_string(".taskter/description.md")?;
                println!("{description}");
            }
            ShowCommands::Okrs => {
                let okrs = fs::read_to_string(".taskter/okrs.json")?;
                println!("{okrs}");
            }
            ShowCommands::Logs => {
                let logs = fs::read_to_string(".taskter/logs.log")?;
                println!("{logs}");
            }
            ShowCommands::Agents => {
                let agents = agent::list_agents()?;
                for a in agents {
                    println!("{}: {}", a.id, a.system_prompt);
                }
            }
        },
        Commands::AddOkr {
            objective,
            key_results,
        } => {
            let mut okrs = store::load_okrs()?;
            let new_okr = store::Okr {
                objective: objective.clone(),
                key_results: key_results
                    .iter()
                    .map(|kr| store::KeyResult {
                        name: kr.clone(),
                        progress: 0.0,
                    })
                    .collect(),
            };
            okrs.push(new_okr);
            store::save_okrs(&okrs)?;
            println!("OKR added successfully.");
        }
        Commands::Log { message } => {
            let mut file = fs::OpenOptions::new()
                .create(true)
                .append(true)
                .open(".taskter/logs.log")?;
            let timestamp = Local::now().format("%Y-%m-%d %H:%M:%S");
            writeln!(file, "[{timestamp}] {message}")?;
            println!("Log added successfully.");
        }
        Commands::Board => {
            tui::run_tui()?;
        }
        Commands::Description { description } => {
            fs::write(".taskter/description.md", description)?;
            println!("Project description updated successfully.");
        }
        Commands::AddAgent {
            prompt,
            tools,
            model,
        } => {
            let mut agents = agent::load_agents()?;
            let mut function_declarations = Vec::new();
            for spec in tools {
                let decl = if Path::new(spec).exists() {
                    let tool_content = fs::read_to_string(spec)?;
                    let tool_json: serde_json::Value = serde_json::from_str(&tool_content)?;
                    serde_json::from_value(tool_json)?
                } else if let Some(built) = tools::builtin_declaration(spec) {
                    built
                } else {
                    return Err(anyhow::anyhow!(format!("Unknown tool: {spec}")));
                };
                function_declarations.push(decl);
            }

            let new_agent = agent::Agent {
                id: agents.len() + 1,
                system_prompt: prompt.clone(),
                tools: function_declarations,
                model: model.clone(),
            };
            agents.push(new_agent);
            agent::save_agents(&agents)?;
            println!("Agent added successfully.");
        }
        Commands::Execute { task_id } => {
            let mut board = store::load_board()?;
            let agents = agent::load_agents()?;

            if let Some(task) = board.tasks.iter_mut().find(|t| t.id == *task_id) {
                if let Some(agent_id) = task.agent_id {
                    if let Some(agent) = agents.iter().find(|a| a.id == agent_id) {
                        match agent::execute_task(agent, task).await {
                            Ok(result) => match result {
                                agent::ExecutionResult::Success { comment } => {
                                    task.status = store::TaskStatus::Done;
                                    task.comment = Some(comment);
                                    println!("Task {task_id} executed successfully.");
                                }
                                agent::ExecutionResult::Failure { comment } => {
                                    task.status = store::TaskStatus::ToDo;
                                    task.comment = Some(comment);
                                    task.agent_id = None;
                                    println!("Task {task_id} failed to execute.");
                                }
                            },
                            Err(e) => {
                                println!("Error executing task {task_id}: {e}");
                            }
                        }
                    } else {
                        println!("Agent with id {agent_id} not found.");
                    }
                } else {
                    println!("Task {task_id} is not assigned to an agent.");
                }
            } else {
                println!("Task with id {task_id} not found.");
            }

            store::save_board(&board)?;
        }
        Commands::Assign { task_id, agent_id } => {
            let mut board = store::load_board()?;
            if let Some(task) = board.tasks.iter_mut().find(|t| t.id == *task_id) {
                task.agent_id = Some(*agent_id);
                store::save_board(&board)?;
                println!("Agent {agent_id} assigned to task {task_id}.");
            } else {
                println!("Task with id {task_id} not found.");
            }
        }
        Commands::DeleteAgent { agent_id } => {
            agent::delete_agent(*agent_id)?;
            println!("Agent {agent_id} deleted.");
        }
    }

    Ok(())
}