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
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
use clap::{Parser, Subcommand};
#[derive(Parser)]
#[command(name = "envswitch")]
#[command(about = "A tool for managing and switching environment variable configurations")]
#[command(long_about = "EnvSwitch helps you manage different sets of environment variables and quickly switch between them. Perfect for switching between different AI model configurations, development environments, or any other environment-specific settings.")]
#[command(version = "0.1.0")]
#[command(author = "EnvSwitch Team")]
pub struct Cli {
/// Enable verbose output
#[arg(short, long, global = true)]
pub verbose: bool,
#[command(subcommand)]
pub command: Commands,
}
#[derive(Subcommand, Debug)]
pub enum Commands {
/// Create or update a configuration
#[command(alias = "add")]
Set {
/// Configuration alias name
alias: String,
/// Environment variables in KEY=VALUE format
#[arg(short, long, value_parser = parse_env_var)]
env: Vec<(String, String)>,
/// Description for the configuration
#[arg(short, long)]
description: Option<String>,
/// Read environment variables from a file
#[arg(short, long, conflicts_with = "env")]
file: Option<String>,
/// Replace all variables instead of merging (only for updates)
#[arg(short, long)]
replace: bool,
/// Interactive mode to add variables one by one
#[arg(short, long, conflicts_with_all = ["env", "file"])]
interactive: bool,
},
/// Switch to a configuration
#[command(alias = "switch")]
Use {
/// Configuration alias to activate
alias: String,
/// Show commands without executing (dry run)
#[arg(short, long)]
dry_run: bool,
},
/// List all configurations
#[command(alias = "ls")]
List {
/// Show detailed information
#[arg(short, long)]
verbose: bool,
/// Display in table format
#[arg(short, long)]
table: bool,
/// Show only active configuration
#[arg(short, long)]
active: bool,
},
/// Show current active configuration and environment status
#[command(alias = "info")]
Status {
/// Show only Claude-specific variables
#[arg(short, long)]
claude: bool,
/// Display in table format
#[arg(short, long)]
table: bool,
/// Show only mismatched variables
#[arg(short, long)]
mismatched: bool,
},
/// Edit a configuration interactively
///
/// Opens an interactive editor to modify environment variables.
/// Allows adding, editing, and removing variables with real-time validation.
///
/// Example:
/// envswitch edit my-config
Edit {
/// Configuration alias to edit
/// Creates a new configuration if it doesn't exist
alias: String,
},
/// Delete a configuration
///
/// Removes a configuration permanently. Shows interactive confirmation
/// unless --force is used. Cannot delete the currently active configuration.
///
/// Examples:
/// envswitch delete old-config
/// envswitch delete temp-config --force
#[command(alias = "rm")]
Delete {
/// Configuration alias to delete
alias: String,
/// Skip confirmation prompt and delete immediately
/// Use with caution as this action cannot be undone
#[arg(short, long)]
force: bool,
/// Show verbose output during deletion
#[arg(short, long)]
verbose: bool,
},
/// Export configurations to a file
///
/// Examples:
/// envswitch export --output my-configs.json
/// envswitch export --configs dev,prod --format env --output configs.env
/// envswitch export --metadata --pretty --output detailed-configs.json
Export {
/// Output file path (default: envswitch_export.json)
/// Supports .json, .env, and .yaml extensions for format detection
#[arg(short, long)]
output: Option<String>,
/// Export only specific configurations (comma-separated)
/// Example: --configs dev,staging,prod
#[arg(short, long, value_delimiter = ',')]
configs: Vec<String>,
/// Export format: json (default), env, or yaml
/// Format is auto-detected from file extension if not specified
#[arg(short, long, default_value = "json")]
format: String,
/// Include metadata such as creation timestamps and descriptions
#[arg(short, long)]
metadata: bool,
/// Pretty print JSON output for better readability
#[arg(short, long)]
pretty: bool,
},
/// Import configurations from a file
///
/// Supports JSON, ENV, and YAML formats with automatic format detection.
/// Creates automatic backups when --backup is used.
///
/// Examples:
/// envswitch import configs.json
/// envswitch import --backup --merge team-configs.json
/// envswitch import --dry-run --verbose new-configs.yaml
Import {
/// Input file path (supports .json, .env, .yaml formats)
/// Format is automatically detected from file content and extension
file: String,
/// Overwrite existing configurations without confirmation
/// Use with caution as this will replace existing configs
#[arg(short, long)]
force: bool,
/// Merge with existing configurations instead of replacing
/// Combines variables from imported and existing configs
#[arg(short, long)]
merge: bool,
/// Preview import changes without actually importing (dry run)
/// Shows what configurations would be created or modified
#[arg(short, long)]
dry_run: bool,
/// Skip validation of imported configurations for faster import
/// Only recommended for trusted configuration files
#[arg(short, long)]
skip_validation: bool,
/// Create backup of existing configurations before import
/// Backup is saved to ~/.config/envswitch/backups/
#[arg(short, long)]
backup: bool,
},
/// Show shell integration instructions and generate setup scripts
Setup {
/// Target shell (auto-detected if not specified)
#[arg(short, long)]
shell: Option<String>,
/// Generate shell integration script
#[arg(short, long)]
generate: bool,
/// Output file for generated script
#[arg(short, long)]
output: Option<String>,
/// Install shell integration automatically
#[arg(short, long)]
install: bool,
/// Generate wrapper script with enhanced functionality
#[arg(short, long)]
wrapper: bool,
},
/// Generate shell initialization code for eval
Init {
/// Target shell (auto-detected if not specified)
#[arg(short, long)]
shell: Option<String>,
/// Generate completion scripts
#[arg(short, long)]
completions: bool,
},
/// Show getting started guide and examples
#[command(alias = "guide")]
Tutorial {
/// Show advanced usage examples
#[arg(short, long)]
advanced: bool,
/// Show examples for specific use case
#[arg(short, long)]
use_case: Option<String>,
},
}
/// Parse environment variable in KEY=VALUE format
fn parse_env_var(s: &str) -> Result<(String, String), String> {
let parts: Vec<&str> = s.splitn(2, '=').collect();
if parts.len() != 2 {
return Err(format!("Invalid format '{}'. Expected KEY=VALUE", s));
}
Ok((parts[0].to_string(), parts[1].to_string()))
}#[cfg(test
)]
mod tests {
use super::*;
#[test]
fn test_parse_env_var_valid() {
let result = parse_env_var("KEY=value");
assert!(result.is_ok());
let (key, value) = result.unwrap();
assert_eq!(key, "KEY");
assert_eq!(value, "value");
}
#[test]
fn test_parse_env_var_with_equals_in_value() {
let result = parse_env_var("URL=https://api.example.com/v1?key=value");
assert!(result.is_ok());
let (key, value) = result.unwrap();
assert_eq!(key, "URL");
assert_eq!(value, "https://api.example.com/v1?key=value");
}
#[test]
fn test_parse_env_var_empty_value() {
let result = parse_env_var("EMPTY=");
assert!(result.is_ok());
let (key, value) = result.unwrap();
assert_eq!(key, "EMPTY");
assert_eq!(value, "");
}
#[test]
fn test_parse_env_var_invalid_no_equals() {
let result = parse_env_var("INVALID");
assert!(result.is_err());
assert!(result.unwrap_err().contains("Invalid format"));
}
#[test]
fn test_parse_env_var_invalid_empty() {
let result = parse_env_var("");
assert!(result.is_err());
assert!(result.unwrap_err().contains("Invalid format"));
}
#[test]
fn test_parse_env_var_only_equals() {
let result = parse_env_var("=");
assert!(result.is_ok());
let (key, value) = result.unwrap();
assert_eq!(key, "");
assert_eq!(value, "");
}
}