Add history.log for tracking command executions
This commit is contained in:
parent
f15be840ca
commit
8d92110c58
8 changed files with 233 additions and 8 deletions
128
Cargo.lock
generated
128
Cargo.lock
generated
|
|
@ -17,6 +17,15 @@ version = "0.2.21"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
|
||||
|
||||
[[package]]
|
||||
name = "android_system_properties"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "1.0.0"
|
||||
|
|
@ -166,6 +175,16 @@ dependencies = [
|
|||
"rustversion",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.64"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dad887fd958be91b5098c0248def011f4523ab786cd411be668777e55063501f"
|
||||
dependencies = [
|
||||
"find-msvc-tools",
|
||||
"shlex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.4"
|
||||
|
|
@ -178,6 +197,19 @@ version = "0.2.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.45"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327"
|
||||
dependencies = [
|
||||
"iana-time-zone",
|
||||
"js-sys",
|
||||
"num-traits",
|
||||
"wasm-bindgen",
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.6.1"
|
||||
|
|
@ -247,6 +279,12 @@ dependencies = [
|
|||
"unicode-segmentation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation-sys"
|
||||
version = "0.8.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.17"
|
||||
|
|
@ -475,6 +513,12 @@ dependencies = [
|
|||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "find-msvc-tools"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
|
||||
|
||||
[[package]]
|
||||
name = "finl_unicode"
|
||||
version = "1.4.0"
|
||||
|
|
@ -618,6 +662,30 @@ version = "0.4.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone"
|
||||
version = "0.1.65"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470"
|
||||
dependencies = [
|
||||
"android_system_properties",
|
||||
"core-foundation-sys",
|
||||
"iana-time-zone-haiku",
|
||||
"js-sys",
|
||||
"log",
|
||||
"wasm-bindgen",
|
||||
"windows-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone-haiku"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "id-arena"
|
||||
version = "2.3.0"
|
||||
|
|
@ -760,6 +828,7 @@ name = "lines"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"chrono",
|
||||
"clap",
|
||||
"crossterm",
|
||||
"directories",
|
||||
|
|
@ -1410,6 +1479,12 @@ dependencies = [
|
|||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook"
|
||||
version = "0.3.18"
|
||||
|
|
@ -1973,12 +2048,65 @@ version = "0.4.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windows-core"
|
||||
version = "0.62.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb"
|
||||
dependencies = [
|
||||
"windows-implement",
|
||||
"windows-interface",
|
||||
"windows-link",
|
||||
"windows-result",
|
||||
"windows-strings",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-implement"
|
||||
version = "0.60.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-interface"
|
||||
version = "0.59.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||
|
||||
[[package]]
|
||||
name = "windows-result"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-strings"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.61.2"
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ edition = "2024"
|
|||
|
||||
[dependencies]
|
||||
base64 = "0.22.1"
|
||||
chrono = "0.4.45"
|
||||
clap = { version = "4.6.1", features = ["derive"] }
|
||||
crossterm = "0.29.0"
|
||||
directories = "6.0.0"
|
||||
|
|
|
|||
|
|
@ -73,6 +73,15 @@ The default terminal is `Terminal` (macOS Terminal.app). The default shell is de
|
|||
| `r` | reload data |
|
||||
| `q` / `Esc` | quit |
|
||||
|
||||
## History
|
||||
|
||||
Every command run through `lines` is appended to `~/.lines/history.log`:
|
||||
|
||||
```
|
||||
2026-06-15 08:30:15 [Servers/web.md] Main server: ssh admin@web.example.com
|
||||
2026-06-15 08:31:02 [Scripts/update.md] Run all: ssh admin@host1.example.com /opt/app/update.sh; ssh admin@host2.example.com /opt/app/update.sh
|
||||
```
|
||||
|
||||
## Backup
|
||||
|
||||
The entire `~/.lines` folder is plain text. Copy it to a disk, Git repo, or iCloud. On a new Mac, move it to the same location and `lines` works immediately.
|
||||
|
|
|
|||
10
src/app.rs
10
src/app.rs
|
|
@ -3,6 +3,14 @@ use std::path::PathBuf;
|
|||
use crate::config::Terminal;
|
||||
use crate::parser::{load_groups, CommandFile, Group, Section};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PendingCommand {
|
||||
pub group: String,
|
||||
pub file: String,
|
||||
pub section: String,
|
||||
pub commands: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Focus {
|
||||
Groups,
|
||||
|
|
@ -19,7 +27,7 @@ pub struct App {
|
|||
pub file_index: usize,
|
||||
pub section_index: usize,
|
||||
pub focus: Focus,
|
||||
pub pending_command: Option<Vec<String>>,
|
||||
pub pending_command: Option<PendingCommand>,
|
||||
pub message: Option<String>,
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,14 +7,19 @@ use std::process::Command;
|
|||
use base64::{engine::general_purpose::STANDARD, Engine};
|
||||
|
||||
use crate::config::Terminal;
|
||||
use crate::logger;
|
||||
use crate::parser::{CommandFile, Section};
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn run_section(
|
||||
section: &Section,
|
||||
file: &CommandFile,
|
||||
terminal: &Terminal,
|
||||
shell: &str,
|
||||
data_dir: &Path,
|
||||
group_name: &str,
|
||||
file_name: &str,
|
||||
section_name: &str,
|
||||
) {
|
||||
let commands = if section.is_run_all() {
|
||||
file.sections
|
||||
|
|
@ -30,6 +35,15 @@ pub fn run_section(
|
|||
return;
|
||||
}
|
||||
|
||||
logger::log_run(
|
||||
data_dir,
|
||||
group_name,
|
||||
file_name,
|
||||
section_name,
|
||||
&commands,
|
||||
true,
|
||||
);
|
||||
|
||||
match terminal {
|
||||
Terminal::Ghostty => run_in_ghostty(&commands, shell),
|
||||
_ => run_via_script(&commands, terminal, shell, data_dir),
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use crossterm::event::{self, Event, KeyCode, KeyEventKind};
|
|||
use std::io;
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::app::App;
|
||||
use crate::app::{App, PendingCommand};
|
||||
use crate::commands::{edit_file, run_section};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
|
|
@ -42,12 +42,25 @@ pub fn handle_event(app: &mut App, event: AppEvent) -> bool {
|
|||
}
|
||||
}
|
||||
KeyCode::Char('o') => {
|
||||
if let (Some(section), Some(file)) = (app.current_section(), app.current_file()) {
|
||||
run_section(section, file, &app.terminal, &app.shell, &app.data_dir);
|
||||
if let (Some(section), Some(file), Some(group)) =
|
||||
(app.current_section(), app.current_file(), app.current_group())
|
||||
{
|
||||
run_section(
|
||||
section,
|
||||
file,
|
||||
&app.terminal,
|
||||
&app.shell,
|
||||
&app.data_dir,
|
||||
&group.name,
|
||||
&file.name,
|
||||
§ion.title,
|
||||
);
|
||||
}
|
||||
}
|
||||
KeyCode::Enter => {
|
||||
if let (Some(section), Some(file)) = (app.current_section(), app.current_file()) {
|
||||
if let (Some(section), Some(file), Some(group)) =
|
||||
(app.current_section(), app.current_file(), app.current_group())
|
||||
{
|
||||
let commands = if section.is_run_all() {
|
||||
file.sections
|
||||
.iter()
|
||||
|
|
@ -58,7 +71,12 @@ pub fn handle_event(app: &mut App, event: AppEvent) -> bool {
|
|||
section.commands.clone()
|
||||
};
|
||||
if !commands.is_empty() {
|
||||
app.pending_command = Some(commands);
|
||||
app.pending_command = Some(PendingCommand {
|
||||
group: group.name.clone(),
|
||||
file: file.name.clone(),
|
||||
section: section.title.clone(),
|
||||
commands,
|
||||
});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
38
src/logger.rs
Normal file
38
src/logger.rs
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
use std::fs::{self, OpenOptions};
|
||||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
|
||||
pub fn log_run(
|
||||
data_dir: &Path,
|
||||
group: &str,
|
||||
file: &str,
|
||||
section: &str,
|
||||
commands: &[String],
|
||||
in_new_window: bool,
|
||||
) {
|
||||
let log_path = data_dir.join("history.log");
|
||||
|
||||
let timestamp = chrono::Local::now().format("%Y-%m-%d %H:%M:%S");
|
||||
let window_note = if in_new_window { " [new-window]" } else { "" };
|
||||
let commands_str = commands.join("; ");
|
||||
|
||||
let line = format!(
|
||||
"{} [{}/{}] {}{}: {}\n",
|
||||
timestamp, group, file, section, window_note, commands_str
|
||||
);
|
||||
|
||||
if let Some(parent) = log_path.parent() {
|
||||
let _ = fs::create_dir_all(parent);
|
||||
}
|
||||
|
||||
let mut file = match OpenOptions::new()
|
||||
.create(true)
|
||||
.append(true)
|
||||
.open(&log_path)
|
||||
{
|
||||
Ok(f) => f,
|
||||
Err(_) => return,
|
||||
};
|
||||
|
||||
let _ = file.write_all(line.as_bytes());
|
||||
}
|
||||
13
src/main.rs
13
src/main.rs
|
|
@ -2,6 +2,7 @@ mod app;
|
|||
mod commands;
|
||||
mod config;
|
||||
mod events;
|
||||
mod logger;
|
||||
mod parser;
|
||||
mod ui;
|
||||
|
||||
|
|
@ -66,8 +67,16 @@ fn main() -> io::Result<()> {
|
|||
)?;
|
||||
terminal.show_cursor()?;
|
||||
|
||||
if let Some(commands) = app.pending_command {
|
||||
run_in_current_terminal(&commands, &app.shell);
|
||||
if let Some(pending) = app.pending_command {
|
||||
logger::log_run(
|
||||
&app.data_dir,
|
||||
&pending.group,
|
||||
&pending.file,
|
||||
&pending.section,
|
||||
&pending.commands,
|
||||
false,
|
||||
);
|
||||
run_in_current_terminal(&pending.commands, &app.shell);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue