use tuitbot_core::storage;
use tuitbot_core::storage::accounts::{
create_account, delete_account, get_active_account_id, list_accounts, set_active_account_id,
DEFAULT_ACCOUNT_ID,
};
use crate::output::CliOutput;
#[derive(Debug, clap::Subcommand)]
pub enum AccountsSubcommand {
List,
Switch {
account: String,
},
Add {
label: String,
},
Remove {
account: String,
},
}
pub async fn execute(
cmd: AccountsSubcommand,
config_path: &str,
out: CliOutput,
) -> anyhow::Result<()> {
use tuitbot_core::config::Config;
let config = Config::load(Some(config_path))?;
let pool = storage::init_db(&config.storage.db_path).await?;
let result = match cmd {
AccountsSubcommand::List => list_accounts_cmd(&pool, &out).await,
AccountsSubcommand::Switch { account } => switch_account_cmd(&pool, &account, &out).await,
AccountsSubcommand::Add { label } => add_account_cmd(&pool, &label, &out).await,
AccountsSubcommand::Remove { account } => remove_account_cmd(&pool, &account, &out).await,
};
pool.close().await;
result
}
async fn list_accounts_cmd(pool: &storage::DbPool, out: &CliOutput) -> anyhow::Result<()> {
let accounts = list_accounts(pool).await?;
let active = get_active_account_id();
if accounts.is_empty() {
out.info("No accounts configured yet. Run `tuitbot accounts add <label>` to add one.");
return Ok(());
}
out.info("");
out.info("Configured Accounts:");
out.info("");
for account in accounts {
let marker = if account.id == active { " * " } else { " " };
let status = if account.status == "active" {
"active"
} else {
"paused"
};
let user_info = account
.x_username
.as_ref()
.map(|u| format!(" (@{})", u))
.unwrap_or_default();
out.info(&format!(
"{}[{}] {} {} {}",
marker,
&account.id[0..8],
account.label,
user_info,
status
));
}
out.info("");
Ok(())
}
async fn switch_account_cmd(
pool: &storage::DbPool,
account_spec: &str,
out: &CliOutput,
) -> anyhow::Result<()> {
let accounts = list_accounts(pool).await?;
let target = accounts
.iter()
.find(|a| a.label == account_spec || a.id.starts_with(account_spec))
.ok_or_else(|| {
anyhow::anyhow!(
"Account not found: '{}'. Run `tuitbot accounts list` to see available accounts.",
account_spec
)
})?;
set_active_account_id(&target.id)?;
let user_info = target
.x_username
.as_ref()
.map(|u| format!(" (@{})", u))
.unwrap_or_default();
out.info(&format!(
"✓ Switched to account: {}{}\n",
target.label, user_info
));
Ok(())
}
async fn add_account_cmd(
pool: &storage::DbPool,
label: &str,
out: &CliOutput,
) -> anyhow::Result<()> {
if label.is_empty() {
anyhow::bail!("Account label cannot be empty");
}
let id = format!(
"acct-{}-{}",
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_secs(),
std::process::id()
);
create_account(pool, &id, label).await?;
out.info(&format!(
"✓ Created account: {} (ID: {})\n",
label,
&id[0..12.min(id.len())]
));
out.info("Next steps:");
out.info(&format!(
" 1. Switch to this account: tuitbot accounts switch {}",
label
));
out.info(" 2. Authenticate with X: tuitbot auth");
out.info(" 3. Run the agent: tuitbot run");
out.info("");
Ok(())
}
async fn remove_account_cmd(
pool: &storage::DbPool,
account_spec: &str,
out: &CliOutput,
) -> anyhow::Result<()> {
if account_spec.len() < 2 {
anyhow::bail!("Account spec must be at least 2 characters");
}
let accounts = list_accounts(pool).await?;
let target = accounts
.iter()
.find(|a| a.label == account_spec || a.id.starts_with(account_spec))
.ok_or_else(|| {
anyhow::anyhow!(
"Account not found: '{}'. Run `tuitbot accounts list` to see available accounts.",
account_spec
)
})?;
if target.id == DEFAULT_ACCOUNT_ID {
anyhow::bail!("Cannot remove the default account");
}
delete_account(pool, &target.id).await?;
if get_active_account_id() == target.id {
set_active_account_id(DEFAULT_ACCOUNT_ID)?;
out.info(&format!(
"✓ Removed account: {}. Switched to default account.\n",
target.label
));
} else {
out.info(&format!("✓ Removed account: {}\n", target.label));
}
Ok(())
}
#[cfg(test)]
mod tests {
#[test]
fn test_account_spec_matching_by_label() {
let label = "MyAccount";
assert_eq!(label, "MyAccount");
}
#[test]
fn test_account_spec_matching_by_id_prefix() {
let id = "abc-123-def-456";
assert!(id.starts_with("abc-"));
assert!(id.starts_with("abc-123"));
}
}