1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
use clap::{CommandFactory, Parser};
use swapdex::commands::{self, ToolSel};
use swapdex::paths::Paths;
#[derive(Parser)]
#[command(
name = "swapdex",
version,
about = "Switch Claude Code / Codex login accounts, locally and safely."
)]
struct Cli {
#[command(subcommand)]
cmd: Option<Cmd>,
}
#[derive(clap::Subcommand)]
enum Cmd {
/// Save the current live login as a named profile
Add {
name: String,
#[arg(long, value_enum)]
tool: Option<ToolSel>,
/// Replace an existing snapshot for the tool
#[arg(long)]
update: bool,
},
/// Switch the active login to a saved profile
Use {
name: String,
#[arg(long, value_enum)]
tool: Option<ToolSel>,
/// Show what would change without writing
#[arg(long)]
dry_run: bool,
},
/// List saved profiles (active marked from the live login)
Ls {
#[arg(long)]
json: bool,
},
/// Show the active account per tool
Status {
#[arg(long)]
json: bool,
},
/// Remove a saved profile (never touches a live login)
Rm {
name: String,
/// Confirm the deletion
#[arg(long)]
yes: bool,
},
/// Guided first-time setup: save your logins, add more, learn to switch
Setup,
/// Log in to a tool and save the result as a profile in one step
Login {
name: String,
#[arg(long, value_enum)]
tool: Option<ToolSel>,
},
/// Rename a saved profile
Rename { old: String, new: String },
/// Sessions grouped by the account that was active when they ran
Sessions,
/// Run as a read-only MCP server over stdio
Mcp,
/// Print a shell completion script (bash, zsh, fish, ...)
Completions { shell: clap_complete::Shell },
}
fn main() {
let cli = Cli::parse();
let paths = match Paths::resolve() {
Ok(p) => p,
Err(e) => {
eprintln!("swapdex: {}", swapdex::util::redact_path(&format!("{e:#}")));
std::process::exit(1);
}
};
let Some(cmd) = &cli.cmd else {
// No subcommand: print the ASCII wordmark + a short hint.
swapdex::banner::print_banner();
return;
};
// Completions generate swapdex's OWN tab-completion - they do NOT wrap or
// intercept `claude`/`codex` (that is llmux's territory, deliberately out of
// scope). Pure codegen, no account access.
if let Cmd::Completions { shell } = cmd {
clap_complete::generate(
*shell,
&mut Cli::command(),
"swapdex",
&mut std::io::stdout(),
);
return;
}
let result = match cmd {
Cmd::Add { name, tool, update } => commands::add(&paths, name, *tool, *update),
Cmd::Use {
name,
tool,
dry_run,
} => commands::use_account(&paths, name, *tool, *dry_run),
Cmd::Ls { json } => commands::ls(&paths, *json),
Cmd::Status { json } => commands::status(&paths, *json),
Cmd::Rm { name, yes } => commands::rm(&paths, name, *yes),
Cmd::Setup => commands::setup(&paths),
Cmd::Login { name, tool } => commands::login(&paths, name, *tool),
Cmd::Rename { old, new } => commands::rename(&paths, old, new),
Cmd::Sessions => commands::sessions(&paths),
Cmd::Mcp => {
swapdex::mcp::serve();
return;
}
Cmd::Completions { .. } => unreachable!("handled before path resolution"),
};
match result {
Ok(code) => std::process::exit(code),
Err(e) => {
// Redact home paths from any error before it reaches a terminal/log.
eprintln!("swapdex: {}", swapdex::util::redact_path(&format!("{e:#}")));
std::process::exit(1);
}
}
}