feat: config disable (atuinsh/atuin#4349)

I added an `atuin config disable` command because my update script looks something like this:

```powershell
if (Get-Command "atuin" -ErrorAction SilentlyContinue) {
    atuin config set daemon.enabled false
    atuin config set daemon.autostart false
    atuin config set search_mode fuzzy
    atuin daemon stop
}

cargo install --locked atuin

if (Get-Command "atuin" -ErrorAction SilentlyContinue) {
    atuin config enable daemon
}
```

This is necessary on Windows, as it doesn't allow you to overwrite a running executable, and I wanted to be able to use another console during the update.

Also, I figured if there's an `enable` command, there should probably also be a `disable` one 🙂 

The second commit is unrelated, it just fixes a few build warnings on Windows.

## Checks
- [x] I am happy for maintainers to push small adjustments to this PR, to speed up the review cycle
- [x] I have checked that there are no existing pull requests for the same thing
This commit is contained in:
Lucas Trzesniewski
2026-10-06 06:03:53 +00:00
committed by Ellie Huxtable
parent d95cd5146b
commit 9740b0a422
8 changed files with 139 additions and 13 deletions
@@ -357,6 +357,7 @@ mod tests {
OpenOptions::new().append(true).open(path).unwrap().write_all(bytes).unwrap();
}
#[cfg(unix)]
fn replace(path: &Path, contents: &[u8]) {
let tmp = path.with_file_name("tmp");
std::fs::write(&tmp, contents).unwrap();
+1
View File
@@ -308,6 +308,7 @@ pub fn try_read_pid(pidfile_path: &Path) -> Option<u32> {
mod tests {
use std::fs::OpenOptions;
#[cfg(unix)]
use proptest::prelude::*;
use rstest::{fixture, rstest};
+3 -1
View File
@@ -1,5 +1,7 @@
use atuin_client::settings::Settings;
use eyre::{Context, Result};
#[cfg(unix)]
use eyre::Context;
use eyre::Result;
use crate::components::search::SearchGrpcService;
use crate::daemon::DaemonHandle;
+5 -3
View File
@@ -118,6 +118,7 @@ arg-totp-code = The two-factor authentication code for your account, if any
cmd-config-get = Get a configuration value from your config.toml file or after defaults and overrides are applied
cmd-config-set = Set a configuration value in your config.toml file
cmd-config-enable = Enable a feature, along with everything it depends on
cmd-config-disable = Disable a feature, leaving its dependencies enabled
cmd-config-print = Print all configuration values from your config.toml file in TOML format
.long =
Print all configuration values from your config.toml file in TOML format
@@ -135,9 +136,10 @@ value-config-set-the-type-boolean = Store value as a boolean
value-config-set-the-type-integer = Store value as an integer
value-config-set-the-type-float = Store the value as a float
arg-config-enable-feature = The feature to enable
value-config-enable-feature-daemon = Run the daemon, autostart it, and use it for search
value-config-enable-feature-output-capture = Capture command output, via the daemon and the pty proxy
value-config-enable-feature-octavo = Use Octavo, once it is enabled for your account on the hub
arg-config-disable-feature = The feature to disable
value-config-feature-daemon = The background daemon and daemon-backed search
value-config-feature-output-capture = Command output capture, via the daemon and the pty proxy
value-config-feature-octavo = Use Octavo, once it is enabled for your account on the hub
config-enable-octavo-not-logged-in = You are not logged in to the hub, so it cannot say whether Octavo is enabled for your account. Run 'atuin login' first.
config-enable-octavo-not-enabled = Octavo is not enabled for your account on { $address }.
config-enable-octavo-description = Octavo is Atuin's sync for sharing and searching your history.
+101 -4
View File
@@ -32,6 +32,9 @@ pub enum Cmd {
#[command(about = fl!("cmd-config-enable"))]
Enable(EnableCmd),
#[command(about = fl!("cmd-config-disable"))]
Disable(DisableCmd),
#[command(about = fl!("cmd-config-print"), long_about = fl!("cmd-config-print", "long"))]
Print(PrintCmd),
}
@@ -43,6 +46,7 @@ impl Cmd {
Self::Get(get) => get.run(settings).await,
Self::Set(set) => set.run(settings).await,
Self::Enable(enable) => enable.run(settings).await,
Self::Disable(disable) => disable.run(settings).await,
Self::Print(print) => print.run(settings).await,
}
}
@@ -245,14 +249,20 @@ pub struct EnableCmd {
pub feature: Feature,
}
#[derive(Args, Debug)]
pub struct DisableCmd {
#[arg(value_enum, help = fl!("arg-config-disable-feature"))]
pub feature: Feature,
}
#[derive(ValueEnum, Debug, Clone, Copy, PartialEq, Eq)]
pub enum Feature {
#[value(help = fl!("value-config-enable-feature-daemon"))]
#[value(help = fl!("value-config-feature-daemon"))]
Daemon,
#[value(help = fl!("value-config-enable-feature-output-capture"))]
#[value(help = fl!("value-config-feature-output-capture"))]
OutputCapture,
#[cfg(feature = "octavo")]
#[value(help = fl!("value-config-enable-feature-octavo"))]
#[value(help = fl!("value-config-feature-octavo"))]
Octavo,
}
@@ -295,7 +305,7 @@ impl EnableCmd {
if settings.daemon.enabled && !settings.daemon.autostart {
println!("Restart the Atuin daemon and your shell for the change to take effect.");
return backfilled;
} else if let Err(e) = daemon::restart_cmd(settings).await {
} else if let Err(e) = daemon::restart_cmd(settings, false).await {
eprintln!(
"Could not restart the Atuin daemon: {e}\nRun `atuin daemon restart` manually."
);
@@ -421,6 +431,72 @@ async fn onboard_octavo(settings: &Settings) -> Result<Option<OctavoOnboarding>>
}))
}
impl DisableCmd {
pub async fn run(self, settings: &Settings) -> Result<()> {
let config_file = Settings::get_config_path()?;
let config_str = tokio::fs::read_to_string(&config_file).await?;
let updated = self.get_updated_config(&config_str)?;
tokio::fs::write(&config_file, &updated).await?;
println!("Disabled.");
#[cfg(feature = "daemon")]
match self.feature {
Feature::Daemon => {
if let Err(e) = daemon::stop_cmd(settings).await {
eprintln!(
"Could not stop the Atuin daemon: {e}\nRun `atuin daemon stop` manually."
);
return Ok(());
}
}
Feature::OutputCapture => restart_autostarted_daemon(settings).await,
#[cfg(feature = "octavo")]
Feature::Octavo => restart_autostarted_daemon(settings).await,
}
println!("Restart your shell for the change to take effect.");
Ok(())
}
fn get_updated_config(&self, config_str: &str) -> Result<String> {
let mut doc: DocumentMut = config_str.parse()?;
match self.feature {
Feature::Daemon => {
set_deep_key(&mut doc, "daemon.enabled", Value::from(false))?;
set_deep_key(&mut doc, "daemon.autostart", Value::from(false))?;
if doc.get("search_mode").and_then(Item::as_str) == Some("daemon-fuzzy") {
set_deep_key(&mut doc, "search_mode", Value::from("fuzzy"))?;
}
}
Feature::OutputCapture => {
set_deep_key(&mut doc, "output.enabled", Value::from(false))?;
}
#[cfg(feature = "octavo")]
Feature::Octavo => set_deep_key(&mut doc, "octavo.enabled", Value::from(false))?,
}
let updated = doc.to_string();
Settings::validate_str(&updated)
.map_err(|e| eyre::eyre!("cannot update config: it would be invalid\n\n{e}"))?;
Ok(updated)
}
}
#[cfg(feature = "daemon")]
async fn restart_autostarted_daemon(settings: &Settings) {
if settings.daemon.enabled
&& settings.daemon.autostart
&& let Err(e) = daemon::restart_cmd(settings, true).await
{
eprintln!("Could not restart the Atuin daemon: {e}\nRun `atuin daemon restart` manually.");
}
}
#[derive(Args, Debug)]
pub struct PrintCmd {
#[arg(help = fl!("arg-config-print-key"))]
@@ -754,6 +830,27 @@ mod tests {
assert_eq!(updated, expected);
}
#[rstest]
#[case::daemon(Feature::Daemon, "", "[daemon]\nenabled = false\nautostart = false\n")]
#[case::output_capture(Feature::OutputCapture, "", "[output]\nenabled = false\n")]
#[case::daemon_search(
Feature::Daemon,
"search_mode = \"daemon-fuzzy\"\n[daemon]\nenabled = true",
"search_mode = \"fuzzy\"\n[daemon]\nenabled = false\nautostart = false\n"
)]
#[case::non_daemon_search(
Feature::Daemon,
"search_mode = \"prefix\"\n[daemon]\nenabled = true",
"search_mode = \"prefix\"\n[daemon]\nenabled = false\nautostart = false\n"
)]
fn disable_writes(#[case] feature: Feature, #[case] input: &str, #[case] expected: &str) {
let updated = DisableCmd { feature }
.get_updated_config(input)
.expect("the update should be accepted");
assert_eq!(updated, expected);
}
/// The error should always mention every listed fragment.
#[rstest]
#[case::an_invalid_value(
+7 -3
View File
@@ -95,7 +95,7 @@ impl Cmd {
Some(SubCmd::Start { force, .. }) => run(settings, store, history_db, force).await,
Some(SubCmd::Status) => status_cmd(&settings).await,
Some(SubCmd::Stop) => stop_cmd(&settings).await,
Some(SubCmd::Restart) => restart_cmd(&settings).await,
Some(SubCmd::Restart) => restart_cmd(&settings, false).await,
}
}
}
@@ -511,7 +511,7 @@ async fn status_cmd(settings: &Settings) -> Result<()> {
Ok(())
}
async fn stop_cmd(settings: &Settings) -> Result<()> {
pub(super) async fn stop_cmd(settings: &Settings) -> Result<()> {
let Ok(mut client) = connect_client(settings).await else {
println!("Daemon is not running");
return Ok(());
@@ -535,7 +535,7 @@ async fn stop_cmd(settings: &Settings) -> Result<()> {
}
}
pub(super) async fn restart_cmd(settings: &Settings) -> Result<()> {
pub(super) async fn restart_cmd(settings: &Settings, only_if_running: bool) -> Result<()> {
// Stop if running
match probe(settings).await {
Probe::Ready(_) | Probe::NeedsRestart(_) => {
@@ -550,6 +550,10 @@ pub(super) async fn restart_cmd(settings: &Settings) -> Result<()> {
}
Probe::Unreachable(_) => {
println!("No daemon running");
if only_if_running {
return Ok(());
}
}
}
@@ -2376,9 +2376,10 @@ fn set_clipboard(_s: String) -> Result<(), std::convert::Infallible> {
mod tests {
use atuin_client::database::Context;
use atuin_client::history::History;
#[cfg(unix)]
use atuin_client::settings::RequestedSearchMode;
use atuin_client::settings::{
FilterMode, KeymapMode, Preview, PreviewStrategy, RequestedSearchMode, SearchMode,
Settings, Shells,
FilterMode, KeymapMode, Preview, PreviewStrategy, SearchMode, Settings, Shells,
};
use atuin_common::shell::Shell;
use rstest::{fixture, rstest};
+18
View File
@@ -131,6 +131,24 @@ service manager such as systemd or `launchd` may run it. With `autostart` off,
Atuin asks you to restart the daemon rather than doing it itself. Open a new
shell afterward.
### `atuin config disable <feature>`
Turn off a feature.
```shell
$ atuin config disable output-capture
```
| Feature | What it sets |
|------------------|--------------------------------------------------------------------------------------------------------------|
| `daemon` | `daemon.enabled = false`, `daemon.autostart = false`, and `search_mode = "fuzzy"` if it was `"daemon-fuzzy"` |
| `output-capture` | `output.enabled = false`. Leaves the daemon and search mode unchanged |
Disabling the daemon stops it if it was running.
Disabling output capture restarts an enabled daemon with `autostart` on so
it picks up the change. Open a new shell afterward.
### `atuin config print [key]`
Print configuration values from your config file in TOML format. Without a