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
//! CLI command definitions and handlers
pub mod analytics;
pub mod audit;
pub mod autoremove;
pub mod bottle;
pub mod bundle;
pub mod cask;
pub mod cleanup;
pub mod completions;
pub mod config;
pub mod create;
pub mod deps;
pub mod doctor;
pub mod first_run;
pub mod history;
pub mod home;
pub mod import;
pub mod info;
pub mod install;
pub mod link;
pub mod list;
pub mod lock;
pub mod mirror;
pub mod outdated;
pub mod pin;
pub mod prefix;
pub mod reinstall;
pub mod rollback;
pub mod search;
pub mod services;
pub mod snapshot;
pub mod switch;
pub mod sync;
pub mod tap;
pub mod test;
pub mod uninstall;
pub mod unlink;
pub mod unpin;
pub mod update;
pub mod upgrade;
pub mod uses;
pub mod why;
use clap::{Parser, Subcommand};
use std::path::PathBuf;
/// Detect the current platform string for bottle selection.
pub fn detect_platform() -> String {
let arch = if cfg!(target_arch = "aarch64") {
"arm64"
} else {
"x86_64"
};
if cfg!(target_os = "macos") {
let codename = detect_macos_codename().unwrap_or("sonoma");
format!("{}_{}", arch, codename)
} else {
format!("{}_linux", arch)
}
}
/// Detect the macOS version codename from the kernel version.
#[cfg(target_os = "macos")]
fn detect_macos_codename() -> Option<&'static str> {
let output = std::process::Command::new("sw_vers")
.arg("-productVersion")
.output()
.ok()?;
let version = String::from_utf8_lossy(&output.stdout);
let major: u32 = version.trim().split('.').next()?.parse().ok()?;
Some(match major {
15 => "sequoia",
14 => "sonoma",
13 => "ventura",
12 => "monterey",
11 => "big_sur",
_ => return None,
})
}
#[cfg(not(target_os = "macos"))]
fn detect_macos_codename() -> Option<&'static str> {
None
}
#[derive(Parser)]
#[command(
name = "stout",
about = "A fast, Rust-based Homebrew-compatible package manager",
version,
author
)]
pub struct Cli {
#[command(subcommand)]
pub command: Command,
/// Enable verbose output
#[arg(short, long, global = true)]
pub verbose: bool,
/// Suppress output
#[arg(short, long, global = true)]
pub quiet: bool,
/// Use a custom installation prefix
#[arg(long, global = true, env = "STOUT_PREFIX")]
pub prefix: Option<PathBuf>,
}
#[derive(Subcommand)]
pub enum Command {
/// Install packages
Install(install::Args),
/// Uninstall packages
Uninstall(uninstall::Args),
/// Reinstall packages
Reinstall(reinstall::Args),
/// Search for packages
Search(search::Args),
/// Show package information
Info(info::Args),
/// List installed packages
List(list::Args),
/// Show outdated packages
Outdated(outdated::Args),
/// Update the formula index
Update(update::Args),
/// Upgrade installed packages
Upgrade(upgrade::Args),
/// Remove unused dependencies
Autoremove(autoremove::Args),
/// Remove old downloads and cache files
Cleanup(cleanup::Args),
/// Show dependencies of a package
Deps(deps::Args),
/// Show packages that depend on a given package
Uses(uses::Args),
/// Show why a package is installed (reverse dependency chain)
Why(why::Args),
/// Show package version history
History(history::Args),
/// Rollback a package to a previous version
Rollback(rollback::Args),
/// Switch between installed versions of a package
Switch(switch::Args),
/// Pin packages to prevent upgrades
Pin(pin::Args),
/// Unpin packages to allow upgrades
Unpin(unpin::Args),
/// Link a package (create symlinks)
Link(link::Args),
/// Unlink a package (remove symlinks)
Unlink(unlink::Args),
/// Open package homepage in browser
Home(home::Args),
/// Import existing Homebrew packages into Stout tracking
Import(import::Args),
/// Manage taps (custom formula repositories)
Tap(tap::Args),
/// Manage lockfiles for reproducible environments
Lock(lock::Args),
/// Manage background services
Services(services::Args),
/// Check system health
Doctor(doctor::Args),
/// Show stout configuration
Config(config::Args),
/// Generate shell completions
Completions(completions::Args),
/// Manage casks (applications)
Cask(cask::Args),
/// Manage Brewfile bundles
Bundle(bundle::Args),
/// Manage system snapshots
Snapshot(snapshot::Args),
/// Reconcile Stout state with the Homebrew Cellar
Sync(sync::Args),
/// Audit packages for known vulnerabilities
Audit(audit::Args),
/// Manage offline mirrors
Mirror(mirror::Args),
/// Create and manage bottles (binary packages)
Bottle(bottle::Args),
/// Create a new formula or cask from a URL
Create(create::Args),
/// Test installed formulas
Test(test::Args),
/// Manage anonymous usage analytics
Analytics(analytics::Args),
/// Manage multiple installation prefixes
Prefix(prefix::Args),
}