cc-switch-tui 0.1.2

All-in-One Assistant for Claude Code, Codex, Gemini, OpenCode, OpenClaw & Hermes
Documentation
use crate::app_config::{AppType, SkillApps};
use crate::cli::i18n::texts;
use crate::error::AppError;
use crate::services::{skill::SyncMethod, SkillService};

use super::super::app::{LoadingKind, Overlay, ToastKind};
use super::super::route::Route;
use super::super::runtime_skills::{
    finish_skills_import_with, open_agent_skills_import_picker, open_skills_import_picker,
    parse_repo_spec, scan_unmanaged_skills,
};
use super::RuntimeActionContext;

pub(super) fn toggle(
    ctx: &mut RuntimeActionContext<'_>,
    directory: String,
    enabled: bool,
) -> Result<(), AppError> {
    SkillService::toggle_app(&directory, &ctx.app.app_type, enabled)?;
    *ctx.data = super::super::data::UiData::load(&ctx.app.app_type)?;
    ctx.app.push_toast(
        texts::tui_toast_skill_toggled(&directory, enabled),
        ToastKind::Success,
    );
    Ok(())
}

pub(super) fn toggle_many(
    ctx: &mut RuntimeActionContext<'_>,
    directories: Vec<String>,
    enabled: bool,
) -> Result<(), AppError> {
    for directory in &directories {
        SkillService::toggle_app(directory, &ctx.app.app_type, enabled)?;
    }
    *ctx.data = super::super::data::UiData::load(&ctx.app.app_type)?;
    ctx.app.push_toast(
        texts::tui_toast_skills_toggled(directories.len(), enabled),
        ToastKind::Success,
    );
    Ok(())
}

pub(super) fn set_apps(
    ctx: &mut RuntimeActionContext<'_>,
    directory: String,
    apps: SkillApps,
) -> Result<(), AppError> {
    let Some(before) = ctx
        .data
        .skills
        .installed
        .iter()
        .find(|skill| skill.directory == directory)
        .map(|skill| skill.apps.clone())
    else {
        ctx.app
            .push_toast(texts::tui_skill_not_found(), ToastKind::Warning);
        return Ok(());
    };

    let mut changed = false;
    for app_type in AppType::all() {
        let next_enabled = apps.is_enabled_for(&app_type);
        if before.is_enabled_for(&app_type) == next_enabled {
            continue;
        }
        changed = true;
        SkillService::toggle_app(&directory, &app_type, next_enabled)?;
    }

    *ctx.data = super::super::data::UiData::load(&ctx.app.app_type)?;
    if changed {
        ctx.app
            .push_toast(texts::tui_toast_skill_apps_updated(), ToastKind::Success);
    }
    Ok(())
}

pub(super) fn set_apps_many(
    ctx: &mut RuntimeActionContext<'_>,
    directories: Vec<String>,
    apps: SkillApps,
) -> Result<(), AppError> {
    let mut changed = false;
    for directory in &directories {
        let Some(before) = ctx
            .data
            .skills
            .installed
            .iter()
            .find(|skill| skill.directory == *directory)
            .map(|skill| skill.apps.clone())
        else {
            continue;
        };

        for app_type in AppType::all() {
            let next_enabled = apps.is_enabled_for(&app_type);
            if before.is_enabled_for(&app_type) == next_enabled {
                continue;
            }
            changed = true;
            SkillService::toggle_app(directory, &app_type, next_enabled)?;
        }
    }

    *ctx.data = super::super::data::UiData::load(&ctx.app.app_type)?;
    if changed {
        ctx.app
            .push_toast(texts::tui_toast_skill_apps_updated(), ToastKind::Success);
    }
    Ok(())
}

pub(super) fn install(ctx: &mut RuntimeActionContext<'_>, spec: String) -> Result<(), AppError> {
    let Some(tx) = ctx.skills_req_tx else {
        return Err(AppError::Message(
            texts::tui_error_skills_worker_unavailable().to_string(),
        ));
    };
    ctx.app.overlay = Overlay::Loading {
        kind: LoadingKind::Generic,
        title: texts::tui_skills_install_title().to_string(),
        message: texts::tui_loading().to_string(),
    };
    tx.send(super::super::runtime_systems::SkillsReq::Install {
        spec: spec.clone(),
        app: ctx.app.app_type.clone(),
    })
    .map_err(|e| AppError::Message(e.to_string()))?;
    Ok(())
}

pub(super) fn uninstall(
    ctx: &mut RuntimeActionContext<'_>,
    directory: String,
) -> Result<(), AppError> {
    SkillService::uninstall(&directory)?;
    *ctx.data = super::super::data::UiData::load(&ctx.app.app_type)?;
    ctx.app.push_toast(
        texts::tui_toast_skill_uninstalled(&directory),
        ToastKind::Success,
    );
    if matches!(&ctx.app.route, Route::SkillDetail { directory: current } if current.eq_ignore_ascii_case(&directory))
    {
        if matches!(ctx.app.route_stack.last(), Some(Route::Skills)) {
            ctx.app.route_stack.pop();
        }
        ctx.app.route = Route::Skills;
    }
    Ok(())
}

pub(super) fn uninstall_many(
    ctx: &mut RuntimeActionContext<'_>,
    directories: Vec<String>,
) -> Result<(), AppError> {
    for directory in &directories {
        SkillService::uninstall(directory)?;
    }
    *ctx.data = super::super::data::UiData::load(&ctx.app.app_type)?;
    ctx.app.push_toast(
        texts::tui_toast_skills_uninstalled(directories.len()),
        ToastKind::Success,
    );
    Ok(())
}

pub(super) fn sync(
    ctx: &mut RuntimeActionContext<'_>,
    scope: Option<AppType>,
) -> Result<(), AppError> {
    SkillService::sync_all_enabled(scope.as_ref())?;
    *ctx.data = super::super::data::UiData::load(&ctx.app.app_type)?;
    ctx.app
        .push_toast(texts::tui_toast_skills_synced(), ToastKind::Success);
    Ok(())
}

pub(super) fn set_sync_method(
    ctx: &mut RuntimeActionContext<'_>,
    method: SyncMethod,
) -> Result<(), AppError> {
    SkillService::set_sync_method(method)?;
    *ctx.data = super::super::data::UiData::load(&ctx.app.app_type)?;
    ctx.app.push_toast(
        texts::tui_toast_skills_sync_method_set(texts::tui_skills_sync_method_name(method)),
        ToastKind::Success,
    );
    Ok(())
}

pub(super) fn discover(ctx: &mut RuntimeActionContext<'_>, query: String) -> Result<(), AppError> {
    let Some(tx) = ctx.skills_req_tx else {
        return Err(AppError::Message(
            texts::tui_error_skills_worker_unavailable().to_string(),
        ));
    };
    ctx.app.overlay = Overlay::Loading {
        kind: LoadingKind::Generic,
        title: texts::tui_skills_discover_title().to_string(),
        message: texts::tui_loading().to_string(),
    };
    tx.send(super::super::runtime_systems::SkillsReq::Discover { query })
        .map_err(|e| AppError::Message(e.to_string()))?;
    Ok(())
}

pub(super) fn repo_add(ctx: &mut RuntimeActionContext<'_>, spec: String) -> Result<(), AppError> {
    let repo = parse_repo_spec(&spec)?;
    SkillService::upsert_repo(repo)?;
    *ctx.data = super::super::data::UiData::load(&ctx.app.app_type)?;
    ctx.app
        .push_toast(texts::tui_toast_repo_added(), ToastKind::Success);
    Ok(())
}

pub(super) fn repo_remove(
    ctx: &mut RuntimeActionContext<'_>,
    owner: String,
    name: String,
) -> Result<(), AppError> {
    SkillService::remove_repo(&owner, &name)?;
    *ctx.data = super::super::data::UiData::load(&ctx.app.app_type)?;
    ctx.app
        .push_toast(texts::tui_toast_repo_removed(), ToastKind::Success);
    Ok(())
}

pub(super) fn repo_toggle_enabled(
    ctx: &mut RuntimeActionContext<'_>,
    owner: String,
    name: String,
    enabled: bool,
) -> Result<(), AppError> {
    let mut index = SkillService::load_index()?;
    if let Some(repo) = index
        .repos
        .iter_mut()
        .find(|r| r.owner == owner && r.name == name)
    {
        repo.enabled = enabled;
        SkillService::save_index(&index)?;
    }
    *ctx.data = super::super::data::UiData::load(&ctx.app.app_type)?;
    ctx.app
        .push_toast(texts::tui_toast_repo_toggled(enabled), ToastKind::Success);
    Ok(())
}

pub(super) fn open_import(ctx: &mut RuntimeActionContext<'_>) -> Result<(), AppError> {
    open_skills_import_picker(ctx.app)
}

pub(super) fn open_agent_import(ctx: &mut RuntimeActionContext<'_>) -> Result<(), AppError> {
    open_agent_skills_import_picker(ctx.app)
}

pub(super) fn scan_unmanaged(ctx: &mut RuntimeActionContext<'_>) -> Result<(), AppError> {
    scan_unmanaged_skills(ctx.app)
}

pub(super) fn import_from_apps(
    ctx: &mut RuntimeActionContext<'_>,
    directories: Vec<String>,
) -> Result<(), AppError> {
    finish_skills_import_with(
        ctx.app,
        ctx.data,
        || SkillService::import_from_apps(directories),
        super::super::data::UiData::load,
    )?;
    ctx.app.skills_unmanaged_results = SkillService::scan_unmanaged()?;
    ctx.app.skills_unmanaged_selected.clear();
    ctx.app.skills_unmanaged_idx = 0;
    Ok(())
}

pub(super) fn import_from_agent(
    ctx: &mut RuntimeActionContext<'_>,
    directories: Vec<String>,
) -> Result<(), AppError> {
    finish_skills_import_with(
        ctx.app,
        ctx.data,
        || SkillService::import_from_agent(directories),
        super::super::data::UiData::load,
    )?;
    ctx.app.skills_unmanaged_results = SkillService::scan_agent_installed()?;
    ctx.app.skills_unmanaged_selected.clear();
    ctx.app.skills_unmanaged_idx = 0;
    Ok(())
}