feat(cli): add --wait flag for consolidate and --date filter for document list (#244)
- bank consolidate: add --wait flag to poll for completion status - bank consolidate: add --poll-interval option (default 10s) - document list: add --date filter (yesterday, today, YYYY-MM-DD, or all) [skip ci] Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
751f99a82f
commit
ff20bf9dc7
3 changed files with 227 additions and 7 deletions
|
|
@ -500,6 +500,8 @@ pub fn delete(
|
|||
pub fn consolidate(
|
||||
client: &ApiClient,
|
||||
bank_id: &str,
|
||||
wait: bool,
|
||||
poll_interval: u64,
|
||||
verbose: bool,
|
||||
output_format: OutputFormat,
|
||||
) -> Result<()> {
|
||||
|
|
@ -517,17 +519,82 @@ pub fn consolidate(
|
|||
|
||||
match response {
|
||||
Ok(result) => {
|
||||
let operation_id = result.operation_id.clone();
|
||||
|
||||
if output_format == OutputFormat::Pretty {
|
||||
ui::print_success("Consolidation triggered");
|
||||
println!(" {} {}", ui::dim("Operation ID:"), result.operation_id);
|
||||
println!(" {} {}", ui::dim("Operation ID:"), operation_id);
|
||||
if result.deduplicated {
|
||||
println!(" {} {}", ui::dim("Note:"), "Reusing existing pending consolidation task");
|
||||
}
|
||||
println!();
|
||||
println!("{}", ui::dim("Use 'hindsight operation get' to check the operation status."));
|
||||
} else {
|
||||
output::print_output(&result, output_format)?;
|
||||
}
|
||||
|
||||
if !wait {
|
||||
if output_format == OutputFormat::Pretty {
|
||||
println!();
|
||||
println!("{}", ui::dim("Use --wait to poll for completion, or 'hindsight operation get' to check status."));
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Poll for completion
|
||||
if output_format == OutputFormat::Pretty {
|
||||
println!();
|
||||
println!("{}", ui::dim(&format!("Polling every {}s for completion...", poll_interval)));
|
||||
}
|
||||
|
||||
let start = std::time::Instant::now();
|
||||
loop {
|
||||
std::thread::sleep(std::time::Duration::from_secs(poll_interval));
|
||||
let elapsed = start.elapsed().as_secs();
|
||||
|
||||
let ops_result = client.list_operations(bank_id, verbose);
|
||||
match ops_result {
|
||||
Ok(ops) => {
|
||||
// Find the operation by ID
|
||||
let op = ops.operations.iter().find(|o| o.id == operation_id);
|
||||
|
||||
match op.map(|o| o.status.as_str()) {
|
||||
Some("completed") => {
|
||||
if output_format == OutputFormat::Pretty {
|
||||
ui::print_success(&format!("Consolidation completed ({}s)", elapsed));
|
||||
}
|
||||
break;
|
||||
}
|
||||
Some("failed") => {
|
||||
let error_msg = op
|
||||
.and_then(|o| o.error_message.as_ref())
|
||||
.map(|s| s.as_str())
|
||||
.unwrap_or("Unknown error");
|
||||
if output_format == OutputFormat::Pretty {
|
||||
ui::print_error(&format!("Consolidation failed: {}", error_msg));
|
||||
}
|
||||
std::process::exit(1);
|
||||
}
|
||||
Some(status) => {
|
||||
if output_format == OutputFormat::Pretty {
|
||||
println!(" ⏳ {} ({}s elapsed)", status, elapsed);
|
||||
}
|
||||
}
|
||||
None => {
|
||||
if output_format == OutputFormat::Pretty {
|
||||
ui::print_warning(&format!("Operation {} not found in list", operation_id));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
if output_format == OutputFormat::Pretty {
|
||||
ui::print_error(&format!("Failed to check operation status: {}", e));
|
||||
}
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
use anyhow::Result;
|
||||
use chrono::{Duration as ChronoDuration, NaiveDate, Utc};
|
||||
use std::collections::BTreeMap;
|
||||
use crate::api::ApiClient;
|
||||
use crate::output::{self, OutputFormat};
|
||||
use crate::ui;
|
||||
|
|
@ -7,11 +9,17 @@ pub fn list(
|
|||
client: &ApiClient,
|
||||
agent_id: &str,
|
||||
query: Option<String>,
|
||||
date: Option<String>,
|
||||
limit: i32,
|
||||
offset: i32,
|
||||
verbose: bool,
|
||||
output_format: OutputFormat,
|
||||
) -> Result<()> {
|
||||
// If date filter is provided, use the date-aware listing
|
||||
if date.is_some() {
|
||||
return list_with_date(client, agent_id, date.as_deref(), verbose, output_format);
|
||||
}
|
||||
|
||||
let spinner = if output_format == OutputFormat::Pretty {
|
||||
Some(ui::create_spinner("Fetching documents..."))
|
||||
} else {
|
||||
|
|
@ -50,6 +58,139 @@ pub fn list(
|
|||
}
|
||||
}
|
||||
|
||||
/// List documents with date filtering
|
||||
fn list_with_date(
|
||||
client: &ApiClient,
|
||||
bank_id: &str,
|
||||
date_filter: Option<&str>,
|
||||
verbose: bool,
|
||||
output_format: OutputFormat,
|
||||
) -> Result<()> {
|
||||
let spinner = if output_format == OutputFormat::Pretty {
|
||||
Some(ui::create_spinner("Fetching all documents..."))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Fetch all documents with pagination
|
||||
let all_docs = fetch_all_documents(client, bank_id, verbose)?;
|
||||
|
||||
if let Some(mut sp) = spinner {
|
||||
sp.finish();
|
||||
}
|
||||
|
||||
// Parse the date filter
|
||||
let target_date = parse_date_filter(date_filter)?;
|
||||
|
||||
// Filter and group documents by date
|
||||
let mut by_date: BTreeMap<String, Vec<serde_json::Value>> = BTreeMap::new();
|
||||
let mut filtered_count = 0;
|
||||
|
||||
for doc in all_docs {
|
||||
let created_at = doc.get("created_at")
|
||||
.and_then(|v| v.as_str())
|
||||
.unwrap_or("");
|
||||
|
||||
// Parse the date part (YYYY-MM-DD) from created_at
|
||||
let doc_date = created_at.split('T').next().unwrap_or("");
|
||||
|
||||
// Apply date filter if specified
|
||||
if let Some(ref target) = target_date {
|
||||
let target_str = target.format("%Y-%m-%d").to_string();
|
||||
if doc_date != target_str {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
filtered_count += 1;
|
||||
by_date.entry(doc_date.to_string()).or_default().push(doc);
|
||||
}
|
||||
|
||||
// Output
|
||||
if output_format == OutputFormat::Pretty {
|
||||
let filter_desc = match date_filter {
|
||||
None | Some("yesterday") => "yesterday".to_string(),
|
||||
Some("today") => "today".to_string(),
|
||||
Some("all") => "all dates".to_string(),
|
||||
Some(d) => d.to_string(),
|
||||
};
|
||||
|
||||
ui::print_info(&format!(
|
||||
"Documents for bank '{}' (filter: {}, showing: {})",
|
||||
bank_id, filter_desc, filtered_count
|
||||
));
|
||||
println!();
|
||||
|
||||
// Show documents grouped by date (reverse order - newest first)
|
||||
for (date_str, docs) in by_date.iter().rev() {
|
||||
println!(" {} ({} documents)", date_str, docs.len());
|
||||
for doc in docs {
|
||||
let id = doc.get("id").and_then(|v| v.as_str()).unwrap_or("unknown");
|
||||
let mem_count = doc.get("memory_unit_count").and_then(|v| v.as_i64()).unwrap_or(0);
|
||||
println!(" - {} ({} memories)", id, mem_count);
|
||||
}
|
||||
println!();
|
||||
}
|
||||
} else {
|
||||
// JSON/YAML output - convert to a list structure
|
||||
let output: Vec<serde_json::Value> = by_date.values().flatten().cloned().collect();
|
||||
output::print_output(&output, output_format)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Fetch all documents with pagination
|
||||
fn fetch_all_documents(
|
||||
client: &ApiClient,
|
||||
bank_id: &str,
|
||||
verbose: bool,
|
||||
) -> Result<Vec<serde_json::Value>> {
|
||||
let mut all_docs = Vec::new();
|
||||
let mut offset = 0;
|
||||
let limit = 500;
|
||||
|
||||
loop {
|
||||
let response = client.list_documents(bank_id, None, Some(limit), Some(offset), verbose)?;
|
||||
|
||||
if response.items.is_empty() {
|
||||
break;
|
||||
}
|
||||
|
||||
// Convert Map<String, Value> to Value for each item
|
||||
for item in response.items {
|
||||
all_docs.push(serde_json::Value::Object(item));
|
||||
}
|
||||
|
||||
offset += limit;
|
||||
|
||||
// Check if we've fetched everything
|
||||
if all_docs.len() >= response.total as usize {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(all_docs)
|
||||
}
|
||||
|
||||
/// Parse date filter string into a NaiveDate
|
||||
fn parse_date_filter(filter: Option<&str>) -> Result<Option<NaiveDate>> {
|
||||
match filter {
|
||||
None | Some("yesterday") => {
|
||||
// Default to yesterday
|
||||
Ok(Some(Utc::now().date_naive() - ChronoDuration::days(1)))
|
||||
}
|
||||
Some("today") => Ok(Some(Utc::now().date_naive())),
|
||||
Some("all") => Ok(None), // No filtering
|
||||
Some(date_str) => {
|
||||
// Try to parse as YYYY-MM-DD
|
||||
NaiveDate::parse_from_str(date_str, "%Y-%m-%d")
|
||||
.map(Some)
|
||||
.map_err(|e| anyhow::anyhow!("Invalid date format '{}': {}. Use YYYY-MM-DD, 'yesterday', 'today', or 'all'", date_str, e))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get(
|
||||
client: &ApiClient,
|
||||
agent_id: &str,
|
||||
|
|
|
|||
|
|
@ -260,6 +260,14 @@ enum BankCommands {
|
|||
Consolidate {
|
||||
/// Bank ID
|
||||
bank_id: String,
|
||||
|
||||
/// Wait for consolidation to complete (poll for status)
|
||||
#[arg(long)]
|
||||
wait: bool,
|
||||
|
||||
/// Poll interval in seconds (only used with --wait)
|
||||
#[arg(long, default_value = "10")]
|
||||
poll_interval: u64,
|
||||
},
|
||||
|
||||
/// Clear all observations for a bank
|
||||
|
|
@ -441,6 +449,10 @@ enum DocumentCommands {
|
|||
#[arg(short = 'q', long)]
|
||||
query: Option<String>,
|
||||
|
||||
/// Filter by date (yesterday, today, YYYY-MM-DD, or all)
|
||||
#[arg(short = 'd', long)]
|
||||
date: Option<String>,
|
||||
|
||||
/// Maximum number of results
|
||||
#[arg(short = 'l', long, default_value = "100")]
|
||||
limit: i32,
|
||||
|
|
@ -754,8 +766,8 @@ fn run() -> Result<()> {
|
|||
BankCommands::Delete { bank_id, yes } => {
|
||||
commands::bank::delete(&client, &bank_id, yes, verbose, output_format)
|
||||
}
|
||||
BankCommands::Consolidate { bank_id } => {
|
||||
commands::bank::consolidate(&client, &bank_id, verbose, output_format)
|
||||
BankCommands::Consolidate { bank_id, wait, poll_interval } => {
|
||||
commands::bank::consolidate(&client, &bank_id, wait, poll_interval, verbose, output_format)
|
||||
}
|
||||
BankCommands::ClearObservations { bank_id, yes } => {
|
||||
commands::bank::clear_observations(&client, &bank_id, yes, verbose, output_format)
|
||||
|
|
@ -792,8 +804,8 @@ fn run() -> Result<()> {
|
|||
|
||||
// Document commands
|
||||
Commands::Document(doc_cmd) => match doc_cmd {
|
||||
DocumentCommands::List { bank_id, query, limit, offset } => {
|
||||
commands::document::list(&client, &bank_id, query, limit, offset, verbose, output_format)
|
||||
DocumentCommands::List { bank_id, query, date, limit, offset } => {
|
||||
commands::document::list(&client, &bank_id, query, date, limit, offset, verbose, output_format)
|
||||
}
|
||||
DocumentCommands::Get { bank_id, document_id } => {
|
||||
commands::document::get(&client, &bank_id, &document_id, verbose, output_format)
|
||||
|
|
|
|||
Loading…
Reference in a new issue