Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions crates/cli/src/commands/decode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@ pub async fn run(
reports
};

if !args.raw {
if let Err(e) = crate::commands::history::append_to_history(&args.tx_hash) {
eprintln!("Warning: failed to update command history: {e}");
}
}

for (i, report) in reports.iter().enumerate() {
if reports.len() > 1 {
println!("\n=== Operation {} ===", i + 1);
Expand Down
100 changes: 100 additions & 0 deletions crates/cli/src/commands/history.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
use std::{
env,
fs,
path::PathBuf,
time::{SystemTime, UNIX_EPOCH},
};

use serde::{Deserialize, Serialize};

const HISTORY_DIR: &str = ".grat";
const HISTORY_FILE: &str = "history.json";
const MAX_ENTRIES: usize = 10;

#[derive(Debug, Clone, Serialize, Deserialize)]
struct HistoryEntry {
tx_hash: String,
timestamp: u64,
}

pub async fn run(_output_format: &str) -> anyhow::Result<()> {
let entries = load_history()?;
if entries.is_empty() {
println!("No transaction history found.");
return Ok(());
}

println!("{:<4} {:<66} {}", "#", "Transaction Hash", "Decoded");
println!("{}", "-".repeat(100));
for (i, entry) in entries.iter().enumerate() {
println!("{:<4} {:<66} {}", i + 1, entry.tx_hash, relative_time(entry.timestamp));
}
Ok(())
}

pub fn append_to_history(tx_hash: &str) -> anyhow::Result<()> {
let mut entries = load_history()?;
// Remove any existing entry with the same hash to keep unique.
entries.retain(|e| e.tx_hash != tx_hash);
entries.insert(
0,
HistoryEntry {
tx_hash: tx_hash.to_string(),
timestamp: now_unix(),
},
);
entries.truncate(MAX_ENTRIES);
save_history(&entries)
}

fn history_path() -> anyhow::Result<PathBuf> {
let home = if let Some(home) = env::var_os("HOME") {
PathBuf::from(home)
} else if let Some(profile) = env::var_os("USERPROFILE") {
PathBuf::from(profile)
} else {
PathBuf::from(".")
};
Ok(home.join(HISTORY_DIR).join(HISTORY_FILE))
}

fn load_history() -> anyhow::Result<Vec<HistoryEntry>> {
let path = history_path()?;
if !path.exists() {
return Ok(Vec::new());
}
let contents = fs::read_to_string(&path)?;
let entries = serde_json::from_str(&contents)?;
Ok(entries)
}

fn save_history(entries: &[HistoryEntry]) -> anyhow::Result<()> {
let path = history_path()?;
if let Some(dir) = path.parent() {
fs::create_dir_all(dir)?;
}
let json = serde_json::to_string_pretty(entries)?;
fs::write(path, json)?;
Ok(())
}

fn now_unix() -> u64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_secs())
.unwrap_or(0)
}

fn relative_time(timestamp: u64) -> String {
let now = now_unix();
let diff = now.saturating_sub(timestamp);
if diff < 60 {
format!("{diff} seconds ago")
} else if diff < 3600 {
format!("{} minutes ago", diff / 60)
} else if diff < 86400 {
format!("{} hours ago", diff / 3600)
} else {
format!("{} days ago", diff / 86400)
}
}
1 change: 1 addition & 0 deletions crates/cli/src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ pub mod decode;
pub mod diagnostic;
pub mod diff;
pub mod export;
pub mod history;
pub mod inspect;
pub mod profile;
pub mod replay;
Expand Down
4 changes: 4 additions & 0 deletions crates/cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@ enum Commands {

Diagnostic(commands::diagnostic::DiagnosticArgs),

#[command(next_help_heading = "System & Data Commands")]
History,

Completions {
shell: clap_complete::Shell,
},
Expand Down Expand Up @@ -156,6 +159,7 @@ async fn main() -> anyhow::Result<()> {
Commands::Db(args) => commands::db::run(args, &cli.output).await?,
Commands::Auth(args) => commands::auth::run(args, &cli.output).await?,
Commands::Diagnostic(args) => commands::diagnostic::run(args).await?,
Commands::History => commands::history::run(&cli.output).await?,
Commands::Serve(args) => commands::serve::run(args, &network).await?,
Commands::Completions { shell } => {
let mut cmd = Cli::command();
Expand Down
Loading