pitboard-core 0.2.0

The engine behind pitboard: parking and restoring Claude Code logins. Serves pitboard's own front ends.
Documentation
//! Taking pitboard off a machine without leaving credentials behind.

use super::{Result, Settled, purge};
use crate::context::Context;
use crate::{home, state};

/// What was removed, for the report.
pub struct Removed {
    /// Parked logins deleted from the keychain or the vault.
    pub parks: usize,
    /// Parked logins that could not be deleted, which is why the home was kept.
    pub pending: usize,
    /// Whether ~/.pitboard itself is gone.
    pub home_removed: bool,
}

/// Deletes every parked login, then pitboard's own directory. Claude Code's login is left
/// exactly as it is: whoever is signed in stays signed in.
///
/// The directory is removed last and only when every parked login is gone, because
/// state.json is the only index of those keychain items. Deleting it first would leave live
/// refresh tokens on the machine with no way left to name them.
pub fn uninstall(settled: Settled) -> Result<Removed> {
    let Settled {
        _exclusive,
        mut state,
        ctx,
    } = settled;
    let parks = state.accounts.iter().filter(|a| a.parked.is_some()).count();
    for label in state
        .accounts
        .iter()
        .map(|a| a.label.clone())
        .collect::<Vec<_>>()
    {
        state.remove(&label);
    }
    state.active = None;
    state::save(&ctx, &state)?;
    let pending = purge(&ctx, &mut state);
    let home_removed = pending == 0 && remove_home(&ctx);
    Ok(Removed {
        parks: parks - pending,
        pending,
        home_removed,
    })
}

/// The lock file this run holds lives in here too; on Unix an open file goes on existing
/// until the last handle closes, so removing the directory now is safe.
fn remove_home(ctx: &Context) -> bool {
    std::fs::remove_dir_all(home::dir(ctx)).is_ok()
}