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
use clap::{Parser, Subcommand};
#[derive(Parser, Debug)]
#[command(
name = "amph",
version,
about = "Reclaim memory and win scheduler contention on Apple Silicon, safely.",
long_about = "Amphetamine frees memory by closing apps you have explicitly listed, clears \
caches belonging to installed applications, and deprioritises noisy background processes so \
your editor gets more of the CPU.\n\n\
It closes nothing until you name it in the config file, asks apps to quit rather than killing \
them, and refuses to make any change it cannot undo.\n\n\
Note: there is no clock control on Apple Silicon. `amph status` will tell you whether anything \
is actually throttling you; what this tool wins back is memory, swap, and CPU contention."
)]
pub struct Cli {
#[command(subcommand)]
pub command: Option<Cmd>,
}
#[derive(Subcommand, Debug)]
pub enum Cmd {
/// Show memory, swap, thermal state and the heaviest apps. Changes nothing.
Status {
/// Emit machine-readable JSON.
#[arg(long)]
json: bool,
/// How many apps to list.
#[arg(long, default_value_t = 10)]
top: usize,
},
/// Close the apps in your config and sweep stale caches.
Boost {
/// Show exactly what would happen without doing any of it.
#[arg(long, short = 'n')]
dry_run: bool,
/// Leave caches alone; only close apps.
#[arg(long)]
no_caches: bool,
/// Only sweep caches; close nothing.
#[arg(long, conflicts_with = "no_caches")]
only_caches: bool,
/// Escalate to a hard kill if an app will not quit in time.
/// This discards unsaved work — an app that stalls is usually asking
/// you to save something.
#[arg(long)]
force: bool,
},
/// Hold a focus session: noisy apps deprioritised, idle sleep held off.
/// Everything is restored when the session ends. Requires `amph setup`.
Focus {
/// Minutes to hold the session. Runs until Ctrl-C if omitted.
#[arg(long = "for", value_name = "MINUTES")]
minutes: Option<u64>,
/// Show what would be deprioritised without changing anything.
#[arg(long, short = 'n')]
dry_run: bool,
},
/// Return any deprioritised process to normal priority. Use this if a
/// focus session was killed and never got to clean up after itself.
Restore,
/// Install the one privilege focus mode needs: permission to return a
/// process to normal priority without a password prompt. Asks for your
/// password once, then never again.
Setup {
/// Print the exact sudoers rule without installing anything.
#[arg(long)]
print: bool,
/// Remove the rule.
#[arg(long, conflicts_with = "print")]
remove: bool,
},
/// Add apps to a list. Names match a bundle ID, an app name, or the last
/// part of a bundle ID, case-insensitively.
///
/// Examples:
/// amph add Slack Spotify close these on `amph boost`
/// amph add --demote Dropbox deprioritise during `amph focus`
#[command(visible_alias = "a")]
Add {
/// App names or bundle IDs.
#[arg(required = true, value_name = "NAME")]
names: Vec<String>,
#[command(flatten)]
list: ListSelector,
},
/// Remove apps from a list.
#[command(visible_alias = "remove")]
Rm {
#[arg(required = true, value_name = "NAME")]
names: Vec<String>,
#[command(flatten)]
list: ListSelector,
},
/// Choose apps interactively from what is running, ranked by memory.
#[command(visible_alias = "p")]
Pick {
#[command(flatten)]
list: ListSelector,
},
/// Inspect or create the config file.
Config {
#[command(subcommand)]
action: Option<ConfigCmd>,
},
}
/// Which list a command applies to. Defaults to the close list, since that is
/// what most edits are.
#[derive(clap::Args, Debug, Clone, Copy)]
#[group(multiple = false)]
pub struct ListSelector {
/// Apply to the focus deprioritise list instead.
#[arg(long, short = 'd')]
pub demote: bool,
/// Apply to the protect list instead.
#[arg(long, short = 'p')]
pub protect: bool,
}
impl From<ListSelector> for crate::manage::List {
fn from(s: ListSelector) -> Self {
match s {
ListSelector { demote: true, .. } => Self::Demote,
ListSelector { protect: true, .. } => Self::Protect,
_ => Self::Close,
}
}
}
#[derive(Subcommand, Debug)]
pub enum ConfigCmd {
/// Show the current config, annotated with what is running.
Show,
/// Print the config file path.
Path,
/// Write a commented starter config.
Init,
/// Open the config in $EDITOR.
Edit,
/// Print the raw file contents.
Raw,
}