-
Notifications
You must be signed in to change notification settings - Fork 128
feat: implement CLI history command #427
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
37865d5
759e36c
c678dd9
2f2b564
222ee2c
7f44981
abc5d42
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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<()> { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. π― Functional Correctness | π‘ Minor | β‘ Quick win π Supported by static analysisπ Script executed: printf '%s\n' '--- changed file ---'
git diff -- crates/cli/src/commands/history.rs
printf '%s\n' '--- history implementation ---'
cat -n crates/cli/src/commands/history.rs
printf '%s\n' '--- output option and history references ---'
rg -n -S --glob '*.rs' 'output_format|output-format|OutputFormat|History|history' crates/cliRepository: Toolbox-Lab/Grat Length of output: 12222 π Script executed: printf '%s\n' '--- CLI option and dispatch ---'
cat -n crates/cli/src/main.rs | sed -n '1,185p'
printf '%s\n' '--- output format contract ---'
cat -n crates/cli/src/output/mod.rs | sed -n '1,75p'
printf '%s\n' '--- user-facing output documentation ---'
rg -n -S --glob '!target/**' --glob '!node_modules/**' 'output|json|short|compact|history' README.md docs crates/cli 2>/dev/null | head -160Repository: Toolbox-Lab/Grat Length of output: 25216 Honor the global The global option accepts π€ Prompt for AI Agents |
||
| 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) | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -80,6 +80,9 @@ enum Commands { | |
|
|
||
| Diagnostic(commands::diagnostic::DiagnosticArgs), | ||
|
|
||
| #[command(next_help_heading = "System & Data Commands")] | ||
| History, | ||
|
Comment on lines
+83
to
+84
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. π― Functional Correctness | π‘ Minor | β‘ Quick win π Supported by static analysisπ Script executed: printf '%s\n' '--- crates/cli/src/main.rs ---'
sed -n '1,125p' crates/cli/src/main.rs
printf '%s\n' '--- clap declarations and help-grouping attributes ---'
rg -n --glob 'Cargo.toml' --glob 'Cargo.lock' --glob '*.rs' 'clap|next_help_heading|subcommand_help_heading|help_heading|System & Data Commands|enum .*Command' crates Cargo.toml Cargo.lock 2>/dev/null | head -200Repository: Toolbox-Lab/Grat Length of output: 5282 π Web query:
π‘ Result: In clap version 4, Citations:
Use the subcommand heading mechanism for
π€ Prompt for AI Agents |
||
|
|
||
| Completions { | ||
| shell: clap_complete::Shell, | ||
| }, | ||
|
|
@@ -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(); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
π― Functional Correctness | π‘ Minor | β‘ Quick win
π Supported by static analysis
π Script executed:
Repository: Toolbox-Lab/Grat
Length of output: 154
π Script executed:
Repository: Toolbox-Lab/Grat
Length of output: 1147
Keep
Check formattingin check mode.The preceding
Format codestep modifies the workspace. Line 31 repeatscargo fmt --allwithout--check, so CI can pass with formatting changes. Restorecargo fmt --all -- --check.π§° Tools
πͺ zizmor (1.29.0)
[warning] 1-90: overly broad permissions (excessive-permissions): default permissions used due to no permissions: block
(excessive-permissions)
[warning] 10-40: overly broad permissions (excessive-permissions): default permissions used due to no permissions: block
(excessive-permissions)
π€ Prompt for AI Agents
Source: MCP tools