mirror of
https://github.com/atuinsh/atuin.git
synced 2024-11-26 02:04:49 +01:00
feat: support syncing aliases (#1721)
* feat: support syncing aliases This is definitely not yet finished, but works for zsh right now. TODO: 1. Support other shells 2. Cache the alias generation, so we don't have to do a bunch of work at shell init time * correct imports * fix clippy errors * fix tests * add the other shells * support xonsh * add delete * update rust, then make clippy happy once more * omfg fmt too
This commit is contained in:
parent
f8d01eef99
commit
20f3296468
14
Cargo.lock
generated
14
Cargo.lock
generated
@ -185,6 +185,7 @@ dependencies = [
|
|||||||
"async-trait",
|
"async-trait",
|
||||||
"atuin-client",
|
"atuin-client",
|
||||||
"atuin-common",
|
"atuin-common",
|
||||||
|
"atuin-config",
|
||||||
"atuin-server",
|
"atuin-server",
|
||||||
"atuin-server-postgres",
|
"atuin-server-postgres",
|
||||||
"base64 0.21.7",
|
"base64 0.21.7",
|
||||||
@ -286,6 +287,19 @@ dependencies = [
|
|||||||
"uuid",
|
"uuid",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "atuin-config"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"atuin-client",
|
||||||
|
"atuin-common",
|
||||||
|
"crypto_secretbox",
|
||||||
|
"eyre",
|
||||||
|
"rand",
|
||||||
|
"rmp",
|
||||||
|
"tokio",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "atuin-server"
|
name = "atuin-server"
|
||||||
version = "18.0.1"
|
version = "18.0.1"
|
||||||
|
@ -6,6 +6,7 @@ members = [
|
|||||||
"atuin-server-postgres",
|
"atuin-server-postgres",
|
||||||
"atuin-server-database",
|
"atuin-server-database",
|
||||||
"atuin-common",
|
"atuin-common",
|
||||||
|
"atuin-config",
|
||||||
]
|
]
|
||||||
|
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
|
23
atuin-config/Cargo.toml
Normal file
23
atuin-config/Cargo.toml
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
[package]
|
||||||
|
name = "atuin-config"
|
||||||
|
edition = "2021"
|
||||||
|
version = "0.1.0" # intentionally not the same as the rest
|
||||||
|
|
||||||
|
authors.workspace = true
|
||||||
|
rust-version.workspace = true
|
||||||
|
license.workspace = true
|
||||||
|
homepage.workspace = true
|
||||||
|
repository.workspace = true
|
||||||
|
readme.workspace = true
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
atuin-common = { path = "../atuin-common", version = "18.0.1" }
|
||||||
|
atuin-client = { path = "../atuin-client", version = "18.0.1" }
|
||||||
|
|
||||||
|
eyre = { workspace = true }
|
||||||
|
tokio = { workspace = true }
|
||||||
|
rmp = { version = "0.8.11" }
|
||||||
|
rand = { workspace = true }
|
||||||
|
crypto_secretbox = "0.1.1"
|
2
atuin-config/src/lib.rs
Normal file
2
atuin-config/src/lib.rs
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
pub mod shell;
|
||||||
|
pub mod store;
|
10
atuin-config/src/shell.rs
Normal file
10
atuin-config/src/shell.rs
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
pub mod bash;
|
||||||
|
pub mod fish;
|
||||||
|
pub mod xonsh;
|
||||||
|
pub mod zsh;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct Alias {
|
||||||
|
pub name: String,
|
||||||
|
pub value: String,
|
||||||
|
}
|
12
atuin-config/src/shell/bash.rs
Normal file
12
atuin-config/src/shell/bash.rs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
use super::Alias;
|
||||||
|
|
||||||
|
// Configuration for bash
|
||||||
|
pub fn build(aliases: &[Alias]) -> String {
|
||||||
|
let mut config = String::new();
|
||||||
|
|
||||||
|
for alias in aliases {
|
||||||
|
config.push_str(&format!("alias {}='{}'\n", alias.name, alias.value));
|
||||||
|
}
|
||||||
|
|
||||||
|
config
|
||||||
|
}
|
12
atuin-config/src/shell/fish.rs
Normal file
12
atuin-config/src/shell/fish.rs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
use super::Alias;
|
||||||
|
|
||||||
|
// Configuration for fish
|
||||||
|
pub fn build(aliases: &[Alias]) -> String {
|
||||||
|
let mut config = String::new();
|
||||||
|
|
||||||
|
for alias in aliases {
|
||||||
|
config.push_str(&format!("alias {}='{}'\n", alias.name, alias.value));
|
||||||
|
}
|
||||||
|
|
||||||
|
config
|
||||||
|
}
|
12
atuin-config/src/shell/xonsh.rs
Normal file
12
atuin-config/src/shell/xonsh.rs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
use super::Alias;
|
||||||
|
|
||||||
|
// Configuration for xonsh
|
||||||
|
pub fn build(aliases: &[Alias]) -> String {
|
||||||
|
let mut config = String::new();
|
||||||
|
|
||||||
|
for alias in aliases {
|
||||||
|
config.push_str(&format!("aliases['{}'] ='{}'\n", alias.name, alias.value));
|
||||||
|
}
|
||||||
|
|
||||||
|
config
|
||||||
|
}
|
12
atuin-config/src/shell/zsh.rs
Normal file
12
atuin-config/src/shell/zsh.rs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
use super::Alias;
|
||||||
|
|
||||||
|
// Configuration for zsh
|
||||||
|
pub fn build(aliases: &[Alias]) -> String {
|
||||||
|
let mut config = String::new();
|
||||||
|
|
||||||
|
for alias in aliases {
|
||||||
|
config.push_str(&format!("alias {}='{}'\n", alias.name, alias.value));
|
||||||
|
}
|
||||||
|
|
||||||
|
config
|
||||||
|
}
|
310
atuin-config/src/store.rs
Normal file
310
atuin-config/src/store.rs
Normal file
@ -0,0 +1,310 @@
|
|||||||
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
|
use atuin_client::record::sqlite_store::SqliteStore;
|
||||||
|
// Sync aliases
|
||||||
|
// This will be noticeable similar to the kv store, though I expect the two shall diverge
|
||||||
|
// While we will support a range of shell config, I'd rather have a larger number of small records
|
||||||
|
// + stores, rather than one mega config store.
|
||||||
|
use atuin_common::record::{DecryptedData, Host, HostId};
|
||||||
|
use eyre::{bail, ensure, eyre, Result};
|
||||||
|
|
||||||
|
use atuin_client::record::encryption::PASETO_V4;
|
||||||
|
use atuin_client::record::store::Store;
|
||||||
|
|
||||||
|
use crate::shell::Alias;
|
||||||
|
|
||||||
|
const CONFIG_SHELL_ALIAS_VERSION: &str = "v0";
|
||||||
|
const CONFIG_SHELL_ALIAS_TAG: &str = "config-shell-alias";
|
||||||
|
const CONFIG_SHELL_ALIAS_FIELD_MAX_LEN: usize = 20000; // 20kb max total len, way more than should be needed.
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub enum AliasRecord {
|
||||||
|
Create(Alias), // create a full record
|
||||||
|
Delete(String), // delete by name
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AliasRecord {
|
||||||
|
pub fn serialize(&self) -> Result<DecryptedData> {
|
||||||
|
use rmp::encode;
|
||||||
|
|
||||||
|
let mut output = vec![];
|
||||||
|
|
||||||
|
match self {
|
||||||
|
AliasRecord::Create(alias) => {
|
||||||
|
encode::write_u8(&mut output, 0)?; // create
|
||||||
|
encode::write_array_len(&mut output, 2)?; // 2 fields
|
||||||
|
|
||||||
|
encode::write_str(&mut output, alias.name.as_str())?;
|
||||||
|
encode::write_str(&mut output, alias.value.as_str())?;
|
||||||
|
}
|
||||||
|
AliasRecord::Delete(name) => {
|
||||||
|
encode::write_u8(&mut output, 1)?; // delete
|
||||||
|
encode::write_array_len(&mut output, 1)?; // 1 field
|
||||||
|
|
||||||
|
encode::write_str(&mut output, name.as_str())?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(DecryptedData(output))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deserialize(data: &DecryptedData, version: &str) -> Result<Self> {
|
||||||
|
use rmp::decode;
|
||||||
|
|
||||||
|
fn error_report<E: std::fmt::Debug>(err: E) -> eyre::Report {
|
||||||
|
eyre!("{err:?}")
|
||||||
|
}
|
||||||
|
|
||||||
|
match version {
|
||||||
|
CONFIG_SHELL_ALIAS_VERSION => {
|
||||||
|
let mut bytes = decode::Bytes::new(&data.0);
|
||||||
|
|
||||||
|
let record_type = decode::read_u8(&mut bytes).map_err(error_report)?;
|
||||||
|
|
||||||
|
match record_type {
|
||||||
|
// create
|
||||||
|
0 => {
|
||||||
|
let nfields = decode::read_array_len(&mut bytes).map_err(error_report)?;
|
||||||
|
ensure!(
|
||||||
|
nfields == 2,
|
||||||
|
"too many entries in v0 shell alias create record"
|
||||||
|
);
|
||||||
|
|
||||||
|
let bytes = bytes.remaining_slice();
|
||||||
|
|
||||||
|
let (key, bytes) =
|
||||||
|
decode::read_str_from_slice(bytes).map_err(error_report)?;
|
||||||
|
let (value, bytes) =
|
||||||
|
decode::read_str_from_slice(bytes).map_err(error_report)?;
|
||||||
|
|
||||||
|
if !bytes.is_empty() {
|
||||||
|
bail!("trailing bytes in encoded shell alias record. malformed")
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(AliasRecord::Create(Alias {
|
||||||
|
name: key.to_owned(),
|
||||||
|
value: value.to_owned(),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
// delete
|
||||||
|
1 => {
|
||||||
|
let nfields = decode::read_array_len(&mut bytes).map_err(error_report)?;
|
||||||
|
ensure!(
|
||||||
|
nfields == 1,
|
||||||
|
"too many entries in v0 shell alias delete record"
|
||||||
|
);
|
||||||
|
|
||||||
|
let bytes = bytes.remaining_slice();
|
||||||
|
|
||||||
|
let (key, bytes) =
|
||||||
|
decode::read_str_from_slice(bytes).map_err(error_report)?;
|
||||||
|
|
||||||
|
if !bytes.is_empty() {
|
||||||
|
bail!("trailing bytes in encoded shell alias record. malformed")
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(AliasRecord::Delete(key.to_owned()))
|
||||||
|
}
|
||||||
|
|
||||||
|
n => {
|
||||||
|
bail!("unknown AliasRecord type {n}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
bail!("unknown version {version:?}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct AliasStore {
|
||||||
|
pub store: SqliteStore,
|
||||||
|
pub host_id: HostId,
|
||||||
|
pub encryption_key: [u8; 32],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AliasStore {
|
||||||
|
// will want to init the actual kv store when that is done
|
||||||
|
pub fn new(store: SqliteStore, host_id: HostId, encryption_key: [u8; 32]) -> AliasStore {
|
||||||
|
AliasStore {
|
||||||
|
store,
|
||||||
|
host_id,
|
||||||
|
encryption_key,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn set(&self, name: &str, value: &str) -> Result<()> {
|
||||||
|
if name.len() + value.len() > CONFIG_SHELL_ALIAS_FIELD_MAX_LEN {
|
||||||
|
return Err(eyre!(
|
||||||
|
"alias record too large: max len {} bytes",
|
||||||
|
CONFIG_SHELL_ALIAS_FIELD_MAX_LEN
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let record = AliasRecord::Create(Alias {
|
||||||
|
name: name.to_string(),
|
||||||
|
value: value.to_string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
let bytes = record.serialize()?;
|
||||||
|
|
||||||
|
let idx = self
|
||||||
|
.store
|
||||||
|
.last(self.host_id, CONFIG_SHELL_ALIAS_TAG)
|
||||||
|
.await?
|
||||||
|
.map_or(0, |entry| entry.idx + 1);
|
||||||
|
|
||||||
|
let record = atuin_common::record::Record::builder()
|
||||||
|
.host(Host::new(self.host_id))
|
||||||
|
.version(CONFIG_SHELL_ALIAS_VERSION.to_string())
|
||||||
|
.tag(CONFIG_SHELL_ALIAS_TAG.to_string())
|
||||||
|
.idx(idx)
|
||||||
|
.data(bytes)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
self.store
|
||||||
|
.push(&record.encrypt::<PASETO_V4>(&self.encryption_key))
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn delete(&self, name: &str) -> Result<()> {
|
||||||
|
if name.len() > CONFIG_SHELL_ALIAS_FIELD_MAX_LEN {
|
||||||
|
return Err(eyre!(
|
||||||
|
"alias record too large: max len {} bytes",
|
||||||
|
CONFIG_SHELL_ALIAS_FIELD_MAX_LEN
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let record = AliasRecord::Delete(name.to_string());
|
||||||
|
|
||||||
|
let bytes = record.serialize()?;
|
||||||
|
|
||||||
|
let idx = self
|
||||||
|
.store
|
||||||
|
.last(self.host_id, CONFIG_SHELL_ALIAS_TAG)
|
||||||
|
.await?
|
||||||
|
.map_or(0, |entry| entry.idx + 1);
|
||||||
|
|
||||||
|
let record = atuin_common::record::Record::builder()
|
||||||
|
.host(Host::new(self.host_id))
|
||||||
|
.version(CONFIG_SHELL_ALIAS_VERSION.to_string())
|
||||||
|
.tag(CONFIG_SHELL_ALIAS_TAG.to_string())
|
||||||
|
.idx(idx)
|
||||||
|
.data(bytes)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
self.store
|
||||||
|
.push(&record.encrypt::<PASETO_V4>(&self.encryption_key))
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn aliases(&self) -> Result<Vec<Alias>> {
|
||||||
|
let mut build = BTreeMap::new();
|
||||||
|
|
||||||
|
// this is sorted, oldest to newest
|
||||||
|
let tagged = self.store.all_tagged(CONFIG_SHELL_ALIAS_TAG).await?;
|
||||||
|
|
||||||
|
for record in tagged {
|
||||||
|
let version = record.version.clone();
|
||||||
|
|
||||||
|
let decrypted = match version.as_str() {
|
||||||
|
CONFIG_SHELL_ALIAS_VERSION => record.decrypt::<PASETO_V4>(&self.encryption_key)?,
|
||||||
|
version => bail!("unknown version {version:?}"),
|
||||||
|
};
|
||||||
|
|
||||||
|
let ar = AliasRecord::deserialize(&decrypted.data, version.as_str())?;
|
||||||
|
|
||||||
|
match ar {
|
||||||
|
AliasRecord::Create(a) => {
|
||||||
|
build.insert(a.name.clone(), a);
|
||||||
|
}
|
||||||
|
AliasRecord::Delete(d) => {
|
||||||
|
build.remove(&d);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(build.into_values().collect())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
pub(crate) fn test_sqlite_store_timeout() -> f64 {
|
||||||
|
std::env::var("ATUIN_TEST_SQLITE_STORE_TIMEOUT")
|
||||||
|
.ok()
|
||||||
|
.and_then(|x| x.parse().ok())
|
||||||
|
.unwrap_or(0.1)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use rand::rngs::OsRng;
|
||||||
|
|
||||||
|
use atuin_client::record::sqlite_store::SqliteStore;
|
||||||
|
|
||||||
|
use crate::shell::Alias;
|
||||||
|
|
||||||
|
use super::{test_sqlite_store_timeout, AliasRecord, AliasStore, CONFIG_SHELL_ALIAS_VERSION};
|
||||||
|
use crypto_secretbox::{KeyInit, XSalsa20Poly1305};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn encode_decode() {
|
||||||
|
let record = Alias {
|
||||||
|
name: "k".to_owned(),
|
||||||
|
value: "kubectl".to_owned(),
|
||||||
|
};
|
||||||
|
let record = AliasRecord::Create(record);
|
||||||
|
|
||||||
|
let snapshot = [204, 0, 146, 161, 107, 167, 107, 117, 98, 101, 99, 116, 108];
|
||||||
|
|
||||||
|
let encoded = record.serialize().unwrap();
|
||||||
|
let decoded = AliasRecord::deserialize(&encoded, CONFIG_SHELL_ALIAS_VERSION).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(encoded.0, &snapshot);
|
||||||
|
assert_eq!(decoded, record);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn build_aliases() {
|
||||||
|
let store = SqliteStore::new(":memory:", test_sqlite_store_timeout())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
let key: [u8; 32] = XSalsa20Poly1305::generate_key(&mut OsRng).into();
|
||||||
|
let host_id = atuin_common::record::HostId(atuin_common::utils::uuid_v7());
|
||||||
|
|
||||||
|
let alias = AliasStore::new(store, host_id, key);
|
||||||
|
|
||||||
|
alias.set("k", "kubectl").await.unwrap();
|
||||||
|
|
||||||
|
alias.set("gp", "git push").await.unwrap();
|
||||||
|
|
||||||
|
let mut aliases = alias.aliases().await.unwrap();
|
||||||
|
|
||||||
|
aliases.sort_by_key(|a| a.name.clone());
|
||||||
|
|
||||||
|
assert_eq!(aliases.len(), 2);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
aliases[0],
|
||||||
|
Alias {
|
||||||
|
name: String::from("gp"),
|
||||||
|
value: String::from("git push")
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
aliases[1],
|
||||||
|
Alias {
|
||||||
|
name: String::from("k"),
|
||||||
|
value: String::from("kubectl")
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -45,6 +45,7 @@ atuin-server-postgres = { path = "../atuin-server-postgres", version = "18.0.1",
|
|||||||
atuin-server = { path = "../atuin-server", version = "18.0.1", optional = true }
|
atuin-server = { path = "../atuin-server", version = "18.0.1", optional = true }
|
||||||
atuin-client = { path = "../atuin-client", version = "18.0.1", optional = true, default-features = false }
|
atuin-client = { path = "../atuin-client", version = "18.0.1", optional = true, default-features = false }
|
||||||
atuin-common = { path = "../atuin-common", version = "18.0.1" }
|
atuin-common = { path = "../atuin-common", version = "18.0.1" }
|
||||||
|
atuin-config = { path = "../atuin-config", version = "0.1.0" }
|
||||||
|
|
||||||
log = { workspace = true }
|
log = { workspace = true }
|
||||||
env_logger = "0.10.0"
|
env_logger = "0.10.0"
|
||||||
|
@ -13,8 +13,10 @@ mod sync;
|
|||||||
mod account;
|
mod account;
|
||||||
|
|
||||||
mod config;
|
mod config;
|
||||||
|
mod default_config;
|
||||||
mod history;
|
mod history;
|
||||||
mod import;
|
mod import;
|
||||||
|
mod init;
|
||||||
mod kv;
|
mod kv;
|
||||||
mod search;
|
mod search;
|
||||||
mod stats;
|
mod stats;
|
||||||
@ -50,6 +52,12 @@ pub enum Cmd {
|
|||||||
#[command(subcommand)]
|
#[command(subcommand)]
|
||||||
Store(store::Cmd),
|
Store(store::Cmd),
|
||||||
|
|
||||||
|
#[command(subcommand)]
|
||||||
|
Config(config::Cmd),
|
||||||
|
|
||||||
|
#[command()]
|
||||||
|
Init(init::Cmd),
|
||||||
|
|
||||||
/// Print example configuration
|
/// Print example configuration
|
||||||
#[command()]
|
#[command()]
|
||||||
DefaultConfig,
|
DefaultConfig,
|
||||||
@ -101,8 +109,12 @@ impl Cmd {
|
|||||||
|
|
||||||
Self::Store(store) => store.run(&settings, &db, sqlite_store).await,
|
Self::Store(store) => store.run(&settings, &db, sqlite_store).await,
|
||||||
|
|
||||||
|
Self::Config(config) => config.run(&settings, sqlite_store).await,
|
||||||
|
|
||||||
|
Self::Init(init) => init.run(&settings).await,
|
||||||
|
|
||||||
Self::DefaultConfig => {
|
Self::DefaultConfig => {
|
||||||
config::run();
|
default_config::run();
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,21 @@
|
|||||||
use atuin_client::settings::Settings;
|
use clap::Subcommand;
|
||||||
|
use eyre::Result;
|
||||||
|
|
||||||
pub fn run() {
|
use atuin_client::{record::sqlite_store::SqliteStore, settings::Settings};
|
||||||
println!("{}", Settings::example_config());
|
|
||||||
|
mod alias;
|
||||||
|
|
||||||
|
#[derive(Subcommand, Debug)]
|
||||||
|
#[command(infer_subcommands = true)]
|
||||||
|
pub enum Cmd {
|
||||||
|
#[command(subcommand)]
|
||||||
|
Alias(alias::Cmd),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Cmd {
|
||||||
|
pub async fn run(self, settings: &Settings, store: SqliteStore) -> Result<()> {
|
||||||
|
match self {
|
||||||
|
Self::Alias(cmd) => cmd.run(settings, store).await,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
42
atuin/src/command/client/config/alias.rs
Normal file
42
atuin/src/command/client/config/alias.rs
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
use clap::Subcommand;
|
||||||
|
use eyre::{Context, Result};
|
||||||
|
|
||||||
|
use atuin_client::{encryption, record::sqlite_store::SqliteStore, settings::Settings};
|
||||||
|
|
||||||
|
use atuin_config::store::AliasStore;
|
||||||
|
|
||||||
|
#[derive(Subcommand, Debug)]
|
||||||
|
#[command(infer_subcommands = true)]
|
||||||
|
pub enum Cmd {
|
||||||
|
Set { name: String, value: String },
|
||||||
|
Delete { name: String },
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Cmd {
|
||||||
|
async fn set(&self, store: AliasStore, name: String, value: String) -> Result<()> {
|
||||||
|
store.set(&name, &value).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn delete(&self, store: AliasStore, name: String) -> Result<()> {
|
||||||
|
store.delete(&name).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn run(&self, settings: &Settings, store: SqliteStore) -> Result<()> {
|
||||||
|
let encryption_key: [u8; 32] = encryption::load_key(settings)
|
||||||
|
.context("could not load encryption key")?
|
||||||
|
.into();
|
||||||
|
let host_id = Settings::host_id().expect("failed to get host_id");
|
||||||
|
|
||||||
|
let alias_store = AliasStore::new(store, host_id, encryption_key);
|
||||||
|
|
||||||
|
match self {
|
||||||
|
Self::Set { name, value } => self.set(alias_store, name.clone(), value.clone()).await,
|
||||||
|
|
||||||
|
Self::Delete { name } => self.delete(alias_store, name.clone()).await,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
5
atuin/src/command/client/default_config.rs
Normal file
5
atuin/src/command/client/default_config.rs
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
use atuin_client::settings::Settings;
|
||||||
|
|
||||||
|
pub fn run() {
|
||||||
|
println!("{}", Settings::example_config());
|
||||||
|
}
|
112
atuin/src/command/client/init.rs
Normal file
112
atuin/src/command/client/init.rs
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use atuin_client::{encryption, record::sqlite_store::SqliteStore, settings::Settings};
|
||||||
|
use atuin_config::store::AliasStore;
|
||||||
|
use clap::{Parser, ValueEnum};
|
||||||
|
use eyre::{Result, WrapErr};
|
||||||
|
|
||||||
|
mod bash;
|
||||||
|
mod fish;
|
||||||
|
mod xonsh;
|
||||||
|
mod zsh;
|
||||||
|
|
||||||
|
#[derive(Parser, Debug)]
|
||||||
|
pub struct Cmd {
|
||||||
|
shell: Shell,
|
||||||
|
|
||||||
|
/// Disable the binding of CTRL-R to atuin
|
||||||
|
#[clap(long)]
|
||||||
|
disable_ctrl_r: bool,
|
||||||
|
|
||||||
|
/// Disable the binding of the Up Arrow key to atuin
|
||||||
|
#[clap(long)]
|
||||||
|
disable_up_arrow: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, ValueEnum, Debug)]
|
||||||
|
pub enum Shell {
|
||||||
|
/// Zsh setup
|
||||||
|
Zsh,
|
||||||
|
/// Bash setup
|
||||||
|
Bash,
|
||||||
|
/// Fish setup
|
||||||
|
Fish,
|
||||||
|
/// Nu setup
|
||||||
|
Nu,
|
||||||
|
/// Xonsh setup
|
||||||
|
Xonsh,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Cmd {
|
||||||
|
fn init_nu(&self) {
|
||||||
|
let full = include_str!("../../shell/atuin.nu");
|
||||||
|
println!("{full}");
|
||||||
|
|
||||||
|
if std::env::var("ATUIN_NOBIND").is_err() {
|
||||||
|
const BIND_CTRL_R: &str = r"$env.config = (
|
||||||
|
$env.config | upsert keybindings (
|
||||||
|
$env.config.keybindings
|
||||||
|
| append {
|
||||||
|
name: atuin
|
||||||
|
modifier: control
|
||||||
|
keycode: char_r
|
||||||
|
mode: [emacs, vi_normal, vi_insert]
|
||||||
|
event: { send: executehostcommand cmd: (_atuin_search_cmd) }
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)";
|
||||||
|
const BIND_UP_ARROW: &str = r"
|
||||||
|
# The up arrow keybinding has surprising behavior in Nu, and is disabled by default.
|
||||||
|
# See https://github.com/atuinsh/atuin/issues/1025 for details
|
||||||
|
# $env.config = (
|
||||||
|
# $env.config | upsert keybindings (
|
||||||
|
# $env.config.keybindings
|
||||||
|
# | append {
|
||||||
|
# name: atuin
|
||||||
|
# modifier: none
|
||||||
|
# keycode: up
|
||||||
|
# mode: [emacs, vi_normal, vi_insert]
|
||||||
|
# event: { send: executehostcommand cmd: (_atuin_search_cmd '--shell-up-key-binding') }
|
||||||
|
# }
|
||||||
|
# )
|
||||||
|
# )
|
||||||
|
";
|
||||||
|
if !self.disable_ctrl_r {
|
||||||
|
println!("{BIND_CTRL_R}");
|
||||||
|
}
|
||||||
|
if !self.disable_up_arrow {
|
||||||
|
println!("{BIND_UP_ARROW}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn run(self, settings: &Settings) -> Result<()> {
|
||||||
|
let record_store_path = PathBuf::from(settings.record_store_path.as_str());
|
||||||
|
let sqlite_store = SqliteStore::new(record_store_path, settings.local_timeout).await?;
|
||||||
|
|
||||||
|
let encryption_key: [u8; 32] = encryption::load_key(settings)
|
||||||
|
.context("could not load encryption key")?
|
||||||
|
.into();
|
||||||
|
let host_id = Settings::host_id().expect("failed to get host_id");
|
||||||
|
|
||||||
|
let alias_store = AliasStore::new(sqlite_store, host_id, encryption_key);
|
||||||
|
|
||||||
|
match self.shell {
|
||||||
|
Shell::Zsh => {
|
||||||
|
zsh::init(alias_store, self.disable_up_arrow, self.disable_ctrl_r).await?;
|
||||||
|
}
|
||||||
|
Shell::Bash => {
|
||||||
|
bash::init(alias_store, self.disable_up_arrow, self.disable_ctrl_r).await?;
|
||||||
|
}
|
||||||
|
Shell::Fish => {
|
||||||
|
fish::init(alias_store, self.disable_up_arrow, self.disable_ctrl_r).await?;
|
||||||
|
}
|
||||||
|
Shell::Nu => self.init_nu(),
|
||||||
|
Shell::Xonsh => {
|
||||||
|
xonsh::init(alias_store, self.disable_up_arrow, self.disable_ctrl_r).await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
23
atuin/src/command/client/init/bash.rs
Normal file
23
atuin/src/command/client/init/bash.rs
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
use atuin_config::store::AliasStore;
|
||||||
|
use eyre::Result;
|
||||||
|
|
||||||
|
pub async fn init(store: AliasStore, disable_up_arrow: bool, disable_ctrl_r: bool) -> Result<()> {
|
||||||
|
let base = include_str!("../../../shell/atuin.bash");
|
||||||
|
|
||||||
|
let aliases = store.aliases().await?;
|
||||||
|
|
||||||
|
let aliases = atuin_config::shell::bash::build(&aliases[..]);
|
||||||
|
|
||||||
|
let (bind_ctrl_r, bind_up_arrow) = if std::env::var("ATUIN_NOBIND").is_ok() {
|
||||||
|
(false, false)
|
||||||
|
} else {
|
||||||
|
(!disable_ctrl_r, !disable_up_arrow)
|
||||||
|
};
|
||||||
|
|
||||||
|
println!("__atuin_bind_ctrl_r={bind_ctrl_r}");
|
||||||
|
println!("__atuin_bind_up_arrow={bind_up_arrow}");
|
||||||
|
println!("{base}");
|
||||||
|
println!("{aliases}");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
42
atuin/src/command/client/init/fish.rs
Normal file
42
atuin/src/command/client/init/fish.rs
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
use atuin_config::store::AliasStore;
|
||||||
|
use eyre::Result;
|
||||||
|
|
||||||
|
pub async fn init(store: AliasStore, disable_up_arrow: bool, disable_ctrl_r: bool) -> Result<()> {
|
||||||
|
let base = include_str!("../../../shell/atuin.zsh");
|
||||||
|
|
||||||
|
println!("{base}");
|
||||||
|
|
||||||
|
if std::env::var("ATUIN_NOBIND").is_err() {
|
||||||
|
const BIND_CTRL_R: &str = r"bind \cr _atuin_search";
|
||||||
|
const BIND_UP_ARROW: &str = r"bind -k up _atuin_bind_up
|
||||||
|
bind \eOA _atuin_bind_up
|
||||||
|
bind \e\[A _atuin_bind_up";
|
||||||
|
const BIND_CTRL_R_INS: &str = r"bind -M insert \cr _atuin_search";
|
||||||
|
const BIND_UP_ARROW_INS: &str = r"bind -M insert -k up _atuin_bind_up
|
||||||
|
bind -M insert \eOA _atuin_bind_up
|
||||||
|
bind -M insert \e\[A _atuin_bind_up";
|
||||||
|
|
||||||
|
if !disable_ctrl_r {
|
||||||
|
println!("{BIND_CTRL_R}");
|
||||||
|
}
|
||||||
|
if !disable_up_arrow {
|
||||||
|
println!("{BIND_UP_ARROW}");
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("if bind -M insert > /dev/null 2>&1");
|
||||||
|
if !disable_ctrl_r {
|
||||||
|
println!("{BIND_CTRL_R_INS}");
|
||||||
|
}
|
||||||
|
if !disable_up_arrow {
|
||||||
|
println!("{BIND_UP_ARROW_INS}");
|
||||||
|
}
|
||||||
|
println!("end");
|
||||||
|
}
|
||||||
|
|
||||||
|
let aliases = store.aliases().await?;
|
||||||
|
let aliases = atuin_config::shell::fish::build(&aliases[..]);
|
||||||
|
|
||||||
|
println!("{aliases}");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
28
atuin/src/command/client/init/xonsh.rs
Normal file
28
atuin/src/command/client/init/xonsh.rs
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
use atuin_config::store::AliasStore;
|
||||||
|
use eyre::Result;
|
||||||
|
|
||||||
|
pub async fn init(store: AliasStore, disable_up_arrow: bool, disable_ctrl_r: bool) -> Result<()> {
|
||||||
|
let base = include_str!("../../../shell/atuin.xsh");
|
||||||
|
|
||||||
|
let (bind_ctrl_r, bind_up_arrow) = if std::env::var("ATUIN_NOBIND").is_ok() {
|
||||||
|
(false, false)
|
||||||
|
} else {
|
||||||
|
(!disable_ctrl_r, !disable_up_arrow)
|
||||||
|
};
|
||||||
|
println!(
|
||||||
|
"_ATUIN_BIND_CTRL_R={}",
|
||||||
|
if bind_ctrl_r { "True" } else { "False" }
|
||||||
|
);
|
||||||
|
println!(
|
||||||
|
"_ATUIN_BIND_UP_ARROW={}",
|
||||||
|
if bind_up_arrow { "True" } else { "False" }
|
||||||
|
);
|
||||||
|
println!("{base}");
|
||||||
|
|
||||||
|
let aliases = store.aliases().await?;
|
||||||
|
let aliases = atuin_config::shell::xonsh::build(&aliases[..]);
|
||||||
|
|
||||||
|
println!("{aliases}");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
36
atuin/src/command/client/init/zsh.rs
Normal file
36
atuin/src/command/client/init/zsh.rs
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
use atuin_config::store::AliasStore;
|
||||||
|
use eyre::Result;
|
||||||
|
|
||||||
|
pub async fn init(store: AliasStore, disable_up_arrow: bool, disable_ctrl_r: bool) -> Result<()> {
|
||||||
|
let base = include_str!("../../../shell/atuin.zsh");
|
||||||
|
|
||||||
|
println!("{base}");
|
||||||
|
|
||||||
|
if std::env::var("ATUIN_NOBIND").is_err() {
|
||||||
|
const BIND_CTRL_R: &str = r"bindkey -M emacs '^r' atuin-search
|
||||||
|
bindkey -M viins '^r' atuin-search-viins
|
||||||
|
bindkey -M vicmd '/' atuin-search";
|
||||||
|
|
||||||
|
const BIND_UP_ARROW: &str = r"bindkey -M emacs '^[[A' atuin-up-search
|
||||||
|
bindkey -M vicmd '^[[A' atuin-up-search-vicmd
|
||||||
|
bindkey -M viins '^[[A' atuin-up-search-viins
|
||||||
|
bindkey -M emacs '^[OA' atuin-up-search
|
||||||
|
bindkey -M vicmd '^[OA' atuin-up-search-vicmd
|
||||||
|
bindkey -M viins '^[OA' atuin-up-search-viins
|
||||||
|
bindkey -M vicmd 'k' atuin-up-search-vicmd";
|
||||||
|
|
||||||
|
if !disable_ctrl_r {
|
||||||
|
println!("{BIND_CTRL_R}");
|
||||||
|
}
|
||||||
|
if !disable_up_arrow {
|
||||||
|
println!("{BIND_UP_ARROW}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let aliases = store.aliases().await?;
|
||||||
|
let aliases = atuin_config::shell::zsh::build(&aliases[..]);
|
||||||
|
|
||||||
|
println!("{aliases}");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
@ -1,172 +0,0 @@
|
|||||||
use clap::{Parser, ValueEnum};
|
|
||||||
|
|
||||||
#[derive(Parser)]
|
|
||||||
pub struct Cmd {
|
|
||||||
shell: Shell,
|
|
||||||
|
|
||||||
/// Disable the binding of CTRL-R to atuin
|
|
||||||
#[clap(long)]
|
|
||||||
disable_ctrl_r: bool,
|
|
||||||
|
|
||||||
/// Disable the binding of the Up Arrow key to atuin
|
|
||||||
#[clap(long)]
|
|
||||||
disable_up_arrow: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, ValueEnum)]
|
|
||||||
pub enum Shell {
|
|
||||||
/// Zsh setup
|
|
||||||
Zsh,
|
|
||||||
/// Bash setup
|
|
||||||
Bash,
|
|
||||||
/// Fish setup
|
|
||||||
Fish,
|
|
||||||
/// Nu setup
|
|
||||||
Nu,
|
|
||||||
/// Xonsh setup
|
|
||||||
Xonsh,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Cmd {
|
|
||||||
fn init_zsh(&self) {
|
|
||||||
let base = include_str!("../shell/atuin.zsh");
|
|
||||||
|
|
||||||
println!("{base}");
|
|
||||||
|
|
||||||
if std::env::var("ATUIN_NOBIND").is_err() {
|
|
||||||
const BIND_CTRL_R: &str = r"bindkey -M emacs '^r' atuin-search
|
|
||||||
bindkey -M viins '^r' atuin-search-viins
|
|
||||||
bindkey -M vicmd '/' atuin-search";
|
|
||||||
|
|
||||||
const BIND_UP_ARROW: &str = r"bindkey -M emacs '^[[A' atuin-up-search
|
|
||||||
bindkey -M vicmd '^[[A' atuin-up-search-vicmd
|
|
||||||
bindkey -M viins '^[[A' atuin-up-search-viins
|
|
||||||
bindkey -M emacs '^[OA' atuin-up-search
|
|
||||||
bindkey -M vicmd '^[OA' atuin-up-search-vicmd
|
|
||||||
bindkey -M viins '^[OA' atuin-up-search-viins
|
|
||||||
bindkey -M vicmd 'k' atuin-up-search-vicmd";
|
|
||||||
|
|
||||||
if !self.disable_ctrl_r {
|
|
||||||
println!("{BIND_CTRL_R}");
|
|
||||||
}
|
|
||||||
if !self.disable_up_arrow {
|
|
||||||
println!("{BIND_UP_ARROW}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn init_bash(&self) {
|
|
||||||
let base = include_str!("../shell/atuin.bash");
|
|
||||||
let (bind_ctrl_r, bind_up_arrow) = if std::env::var("ATUIN_NOBIND").is_ok() {
|
|
||||||
(false, false)
|
|
||||||
} else {
|
|
||||||
(!self.disable_ctrl_r, !self.disable_up_arrow)
|
|
||||||
};
|
|
||||||
|
|
||||||
println!("__atuin_bind_ctrl_r={bind_ctrl_r}");
|
|
||||||
println!("__atuin_bind_up_arrow={bind_up_arrow}");
|
|
||||||
println!("{base}");
|
|
||||||
}
|
|
||||||
|
|
||||||
fn init_fish(&self) {
|
|
||||||
let full = include_str!("../shell/atuin.fish");
|
|
||||||
println!("{full}");
|
|
||||||
|
|
||||||
if std::env::var("ATUIN_NOBIND").is_err() {
|
|
||||||
const BIND_CTRL_R: &str = r"bind \cr _atuin_search";
|
|
||||||
const BIND_UP_ARROW: &str = r"bind -k up _atuin_bind_up
|
|
||||||
bind \eOA _atuin_bind_up
|
|
||||||
bind \e\[A _atuin_bind_up";
|
|
||||||
const BIND_CTRL_R_INS: &str = r"bind -M insert \cr _atuin_search";
|
|
||||||
const BIND_UP_ARROW_INS: &str = r"bind -M insert -k up _atuin_bind_up
|
|
||||||
bind -M insert \eOA _atuin_bind_up
|
|
||||||
bind -M insert \e\[A _atuin_bind_up";
|
|
||||||
|
|
||||||
if !self.disable_ctrl_r {
|
|
||||||
println!("{BIND_CTRL_R}");
|
|
||||||
}
|
|
||||||
if !self.disable_up_arrow {
|
|
||||||
println!("{BIND_UP_ARROW}");
|
|
||||||
}
|
|
||||||
|
|
||||||
println!("if bind -M insert > /dev/null 2>&1");
|
|
||||||
if !self.disable_ctrl_r {
|
|
||||||
println!("{BIND_CTRL_R_INS}");
|
|
||||||
}
|
|
||||||
if !self.disable_up_arrow {
|
|
||||||
println!("{BIND_UP_ARROW_INS}");
|
|
||||||
}
|
|
||||||
println!("end");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn init_nu(&self) {
|
|
||||||
let full = include_str!("../shell/atuin.nu");
|
|
||||||
println!("{full}");
|
|
||||||
|
|
||||||
if std::env::var("ATUIN_NOBIND").is_err() {
|
|
||||||
const BIND_CTRL_R: &str = r"$env.config = (
|
|
||||||
$env.config | upsert keybindings (
|
|
||||||
$env.config.keybindings
|
|
||||||
| append {
|
|
||||||
name: atuin
|
|
||||||
modifier: control
|
|
||||||
keycode: char_r
|
|
||||||
mode: [emacs, vi_normal, vi_insert]
|
|
||||||
event: { send: executehostcommand cmd: (_atuin_search_cmd) }
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)";
|
|
||||||
const BIND_UP_ARROW: &str = r"
|
|
||||||
# The up arrow keybinding has surprising behavior in Nu, and is disabled by default.
|
|
||||||
# See https://github.com/atuinsh/atuin/issues/1025 for details
|
|
||||||
# $env.config = (
|
|
||||||
# $env.config | upsert keybindings (
|
|
||||||
# $env.config.keybindings
|
|
||||||
# | append {
|
|
||||||
# name: atuin
|
|
||||||
# modifier: none
|
|
||||||
# keycode: up
|
|
||||||
# mode: [emacs, vi_normal, vi_insert]
|
|
||||||
# event: { send: executehostcommand cmd: (_atuin_search_cmd '--shell-up-key-binding') }
|
|
||||||
# }
|
|
||||||
# )
|
|
||||||
# )
|
|
||||||
";
|
|
||||||
if !self.disable_ctrl_r {
|
|
||||||
println!("{BIND_CTRL_R}");
|
|
||||||
}
|
|
||||||
if !self.disable_up_arrow {
|
|
||||||
println!("{BIND_UP_ARROW}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn init_xonsh(&self) {
|
|
||||||
let base = include_str!("../shell/atuin.xsh");
|
|
||||||
let (bind_ctrl_r, bind_up_arrow) = if std::env::var("ATUIN_NOBIND").is_ok() {
|
|
||||||
(false, false)
|
|
||||||
} else {
|
|
||||||
(!self.disable_ctrl_r, !self.disable_up_arrow)
|
|
||||||
};
|
|
||||||
println!(
|
|
||||||
"_ATUIN_BIND_CTRL_R={}",
|
|
||||||
if bind_ctrl_r { "True" } else { "False" }
|
|
||||||
);
|
|
||||||
println!(
|
|
||||||
"_ATUIN_BIND_UP_ARROW={}",
|
|
||||||
if bind_up_arrow { "True" } else { "False" }
|
|
||||||
);
|
|
||||||
println!("{base}");
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn run(self) {
|
|
||||||
match self.shell {
|
|
||||||
Shell::Zsh => self.init_zsh(),
|
|
||||||
Shell::Bash => self.init_bash(),
|
|
||||||
Shell::Fish => self.init_fish(),
|
|
||||||
Shell::Nu => self.init_nu(),
|
|
||||||
Shell::Xonsh => self.init_xonsh(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -11,8 +11,6 @@ mod client;
|
|||||||
#[cfg(feature = "server")]
|
#[cfg(feature = "server")]
|
||||||
mod server;
|
mod server;
|
||||||
|
|
||||||
mod init;
|
|
||||||
|
|
||||||
mod contributors;
|
mod contributors;
|
||||||
|
|
||||||
#[derive(Subcommand)]
|
#[derive(Subcommand)]
|
||||||
@ -27,9 +25,6 @@ pub enum AtuinCmd {
|
|||||||
#[command(subcommand)]
|
#[command(subcommand)]
|
||||||
Server(server::Cmd),
|
Server(server::Cmd),
|
||||||
|
|
||||||
/// Output shell setup
|
|
||||||
Init(init::Cmd),
|
|
||||||
|
|
||||||
/// Generate a UUID
|
/// Generate a UUID
|
||||||
Uuid,
|
Uuid,
|
||||||
|
|
||||||
@ -67,10 +62,6 @@ impl AtuinCmd {
|
|||||||
contributors::run();
|
contributors::run();
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
Self::Init(init) => {
|
|
||||||
init.run();
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
Self::Uuid => {
|
Self::Uuid => {
|
||||||
println!("{}", atuin_common::utils::uuid_v7().as_simple());
|
println!("{}", atuin_common::utils::uuid_v7().as_simple());
|
||||||
Ok(())
|
Ok(())
|
||||||
|
Loading…
Reference in New Issue
Block a user