use kindling_types::{RetrieveOptions, RetrievedEntity};
use crate::cli::SearchArgs;
use crate::commands::scope_from;
use crate::output::{format_json, format_timestamp, truncate};
use crate::{build_client, open_service, runtime, CliResult};
pub fn run(args: SearchArgs, via_daemon: bool) -> CliResult {
let scope = scope_from(args.session.as_deref(), args.repo.as_deref());
let options = RetrieveOptions {
query: args.query.clone(),
scope_ids: scope,
token_budget: None,
max_candidates: Some(args.max),
include_redacted: None,
};
let result = if via_daemon {
let client = build_client()?;
runtime()?.block_on(async { client.retrieve(options).await })?
} else {
let (service, _db) = open_service(args.common.db.as_deref())?;
service.retrieve(options)?
};
if args.common.json {
println!("{}", format_json(&result, true)?);
} else {
println!("\nSearch Results for: \"{}\"", args.query);
println!("{}\n", "=".repeat(50));
if !result.pins.is_empty() {
println!("Pins ({}):", result.pins.len());
for (i, pin) in result.pins.iter().enumerate() {
let (id, type_label, content) = entity_fields(&pin.target);
println!("\n{}. [PIN] {id}", i + 1);
println!(" Type: {type_label}");
println!(" Content: {}", truncate(content, 100));
if let Some(reason) = &pin.pin.reason {
println!(" Note: {reason}");
}
}
println!();
}
if let Some(summary) = &result.current_summary {
println!("Current Summary:");
println!(" {}", truncate(&summary.content, 200));
println!(" Confidence: {}", summary.confidence);
println!();
}
if !result.candidates.is_empty() {
println!("Candidates ({}):", result.candidates.len());
for (i, candidate) in result.candidates.iter().enumerate() {
let (id, type_label, content) = entity_fields(&candidate.entity);
println!("\n{}. {id} (score: {:.2})", i + 1, candidate.score);
println!(" Type: {type_label}");
println!(" Content: {}", truncate(content, 100));
if let RetrievedEntity::Observation(obs) = &candidate.entity {
println!(" Time: {}", format_timestamp(obs.ts));
}
}
} else if result.pins.is_empty() && result.current_summary.is_none() {
println!("No results found.");
}
println!();
}
Ok(())
}
fn entity_fields(entity: &RetrievedEntity) -> (&str, String, &str) {
match entity {
RetrievedEntity::Observation(obs) => {
(obs.id.as_str(), kind_label(obs.kind), obs.content.as_str())
}
RetrievedEntity::Summary(summary) => (
summary.id.as_str(),
"summary".to_string(),
summary.content.as_str(),
),
}
}
fn kind_label(kind: kindling_types::ObservationKind) -> String {
use kindling_types::ObservationKind::*;
match kind {
ToolCall => "tool_call",
Command => "command",
FileDiff => "file_diff",
Error => "error",
Message => "message",
NodeStart => "node_start",
NodeEnd => "node_end",
NodeOutput => "node_output",
NodeError => "node_error",
}
.to_string()
}