stynx-code 3.2.1

stynx-code — interactive AI coding assistant
use std::sync::Arc;
use std::sync::atomic::AtomicU8;

use stynx_code_commands::{CommandResult, SlashCommand, execute_command, parse_command};
use stynx_code_provider::AnthropicProvider;
use stynx_code_types::{Conversation, PermissionMode};

use crate::infrastructure::command_extras::{
    git_diff, handle_commit_prompt, handle_effort_cmd, handle_export, handle_memory,
    handle_model_cmd, handle_plan_task, handle_rewind, handle_review_prompt, try_skill,
};
use crate::infrastructure::command_types::CommandAction;
use crate::infrastructure::command_usage::render_usage;
use crate::infrastructure::skills::Skill;
use crate::infrastructure::terminal::{BOLD, CYAN, DIM, GREEN, MAGENTA, RED, RESET, YELLOW};

const FAST_MODEL: &str = "claude-haiku-4-5-20251001";

#[allow(clippy::too_many_arguments)]
pub async fn handle_slash_command(
    input: &str,
    provider: &AnthropicProvider,
    config: &stynx_code_config::Settings,
    mode_flag: &Arc<AtomicU8>,
    system_prompt: &str,
    cwd: &str,
    conversation: &Conversation,
    skills: &[Skill],
) -> Option<CommandAction> {
    let trimmed = input.trim();

    if let Some(action) = try_skill(trimmed, skills) { return Some(action); }

    if let Some(task) = trimmed.strip_prefix("/plan ").map(str::trim).filter(|s| !s.is_empty()) {
        return Some(handle_plan_task(task, mode_flag));
    }

    let cmd = parse_command(input)?;

    if let SlashCommand::Model(ref name) = cmd {
        return handle_model_cmd(name, provider, mode_flag);
    }

    if matches!(cmd, SlashCommand::Fast) {
        let current = provider.model_name();
        if current.contains("haiku") {
            let restore = config.model.as_deref().unwrap_or("claude-sonnet-4-6");
            provider.set_model(restore);
            return Some(CommandAction::Output(format!("\n  {DIM}Fast mode off →{RESET} {BOLD}{CYAN}{restore}{RESET}\n")));
        } else {
            provider.set_model(FAST_MODEL);
            return Some(CommandAction::Output(format!("\n  {CYAN}{BOLD}⚡ Fast mode on →{RESET} {BOLD}{CYAN}{FAST_MODEL}{RESET}\n")));
        }
    }

    if matches!(cmd, SlashCommand::Mode) {
        let current = PermissionMode::load(mode_flag);
        let next = current.next();
        next.store(mode_flag);
        let (color, icon) = match next {
            PermissionMode::Normal => (GREEN, ""),
            PermissionMode::AutoAccept => (YELLOW, ""),
            PermissionMode::Plan => (MAGENTA, ""),
            PermissionMode::Bypass => (RED, ""),
        };
        return Some(CommandAction::Output(format!(
            "\n  {color}{BOLD}{icon} {}{RESET} {DIM}{}{RESET}\n", next.label(), next.description()
        )));
    }

    if matches!(cmd, SlashCommand::Config) {
        let json = serde_json::to_string_pretty(&config).unwrap_or_default();
        let result = stynx_code_commands::infrastructure::handlers::handle_config(&json);
        if let CommandResult::Output(text) = result { return Some(CommandAction::Output(format!("\n{text}\n"))); }
        return Some(CommandAction::Continue);
    }

    if matches!(cmd, SlashCommand::Permissions) {
        let result = stynx_code_commands::infrastructure::handlers::handle_permissions(
            &config.permissions.allow, &config.permissions.deny,
        );
        if let CommandResult::Output(text) = result { return Some(CommandAction::Output(format!("\n{text}\n"))); }
        return Some(CommandAction::Continue);
    }

    if matches!(cmd, SlashCommand::Think) {
        let enabled = provider.toggle_thinking();
        return Some(CommandAction::Output(if enabled {
            format!("\n  {CYAN}◆  Thinking enabled{RESET}  {DIM}— extended reasoning active{RESET}\n")
        } else {
            format!("\n  {DIM}◆  Thinking disabled{RESET}\n")
        }));
    }

    if matches!(cmd, SlashCommand::Usage) { return Some(CommandAction::Output(render_usage(provider).await)); }

    if let SlashCommand::Effort(ref level) = cmd { return Some(handle_effort_cmd(level, provider)); }

    if matches!(cmd, SlashCommand::Plan) {
        let current = PermissionMode::load(mode_flag);
        let next = if current == PermissionMode::Plan { PermissionMode::Normal } else { PermissionMode::Plan };
        next.store(mode_flag);
        return Some(CommandAction::Output(if next == PermissionMode::Plan {
            format!("\n  {MAGENTA}{BOLD}◆ Plan mode activated{RESET} {DIM}— only read-only tools available{RESET}\n")
        } else {
            format!("\n  {GREEN}{BOLD}✓ Plan mode deactivated{RESET} {DIM}— all tools available{RESET}\n")
        }));
    }

    if matches!(cmd, SlashCommand::Memory) { return Some(CommandAction::Output(handle_memory(cwd))); }
    if matches!(cmd, SlashCommand::Export) { return Some(CommandAction::Output(handle_export(conversation, cwd))); }
    if let SlashCommand::Rewind(n) = cmd { return Some(CommandAction::ReplaceConversation(handle_rewind(conversation, system_prompt, n))); }

    if matches!(cmd, SlashCommand::Review) {
        return Some(match handle_review_prompt(cwd) {
            Some(msg) => CommandAction::SendToEngine(msg, vec![]),
            None => CommandAction::Output(format!("\n  {DIM}No changes to review.{RESET}\n")),
        });
    }

    if matches!(cmd, SlashCommand::Commit) {
        return Some(match handle_commit_prompt(cwd) {
            Some(msg) => CommandAction::SendToEngine(msg, vec![]),
            None => CommandAction::Output(format!("\n  {DIM}No changes to commit.{RESET}\n")),
        });
    }

    let _ = git_diff;

    match execute_command(cmd).await {
        CommandResult::Output(text) => Some(CommandAction::Output(format!("\n{text}\n"))),
        CommandResult::ReplaceConversation(mut c) => {
            c.system = Some(system_prompt.to_string());
            Some(CommandAction::ReplaceConversation(c))
        }
        CommandResult::Quit => Some(CommandAction::Quit),
    }
}