1048 lines
36 KiB
Text
1048 lines
36 KiB
Text
mod api;
|
|
mod config;
|
|
mod errors;
|
|
mod output;
|
|
mod ui;
|
|
|
|
use anyhow::{Context, Result};
|
|
use api::{ApiClient, BatchMemoryRequest, MemoryItem, SearchRequest, ThinkRequest};
|
|
use clap::{Parser, Subcommand, ValueEnum};
|
|
use config::Config;
|
|
use output::OutputFormat;
|
|
use std::fs;
|
|
use std::path::PathBuf;
|
|
use walkdir::WalkDir;
|
|
|
|
#[derive(Debug, Clone, Copy, ValueEnum)]
|
|
enum Format {
|
|
Pretty,
|
|
Json,
|
|
Yaml,
|
|
}
|
|
|
|
impl From<Format> for OutputFormat {
|
|
fn from(f: Format) -> Self {
|
|
match f {
|
|
Format::Pretty => OutputFormat::Pretty,
|
|
Format::Json => OutputFormat::Json,
|
|
Format::Yaml => OutputFormat::Yaml,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Parser)]
|
|
#[command(name = "memora")]
|
|
#[command(about = "Memora CLI - Semantic memory system", long_about = None)]
|
|
#[command(version)]
|
|
struct Cli {
|
|
/// Output format (pretty, json, yaml)
|
|
#[arg(short = 'o', long, global = true, default_value = "pretty")]
|
|
output: Format,
|
|
|
|
/// Show verbose output including full requests and responses
|
|
#[arg(short = 'v', long, global = true)]
|
|
verbose: bool,
|
|
|
|
#[command(subcommand)]
|
|
command: Commands,
|
|
}
|
|
|
|
#[derive(Subcommand)]
|
|
enum Commands {
|
|
/// Manage agents (list, profile, stats)
|
|
#[command(subcommand)]
|
|
Agent(AgentCommands),
|
|
|
|
/// Manage memories (search, think, put, delete)
|
|
#[command(subcommand)]
|
|
Memory(MemoryCommands),
|
|
|
|
/// Manage documents (list, get, delete)
|
|
#[command(subcommand)]
|
|
Document(DocumentCommands),
|
|
|
|
/// Manage async operations (list, cancel)
|
|
#[command(subcommand)]
|
|
Operation(OperationCommands),
|
|
}
|
|
|
|
#[derive(Subcommand)]
|
|
enum AgentCommands {
|
|
/// List all agents
|
|
List,
|
|
|
|
/// Get agent profile (personality + background)
|
|
Profile {
|
|
/// Agent ID
|
|
agent_id: String,
|
|
},
|
|
|
|
/// Get memory statistics for an agent
|
|
Stats {
|
|
/// Agent ID
|
|
agent_id: String,
|
|
},
|
|
|
|
/// Update agent personality traits
|
|
SetPersonality {
|
|
/// Agent ID
|
|
agent_id: String,
|
|
|
|
/// Openness to experience (0.0-1.0)
|
|
#[arg(long)]
|
|
openness: f32,
|
|
|
|
/// Conscientiousness (0.0-1.0)
|
|
#[arg(long)]
|
|
conscientiousness: f32,
|
|
|
|
/// Extraversion (0.0-1.0)
|
|
#[arg(long)]
|
|
extraversion: f32,
|
|
|
|
/// Agreeableness (0.0-1.0)
|
|
#[arg(long)]
|
|
agreeableness: f32,
|
|
|
|
/// Neuroticism (0.0-1.0)
|
|
#[arg(long)]
|
|
neuroticism: f32,
|
|
|
|
/// Bias strength (0.0-1.0)
|
|
#[arg(long)]
|
|
bias_strength: f32,
|
|
},
|
|
|
|
/// Set or merge agent background
|
|
SetBackground {
|
|
/// Agent ID
|
|
agent_id: String,
|
|
|
|
/// Background content
|
|
content: String,
|
|
|
|
/// Skip automatic personality inference
|
|
#[arg(long)]
|
|
no_update_personality: bool,
|
|
},
|
|
}
|
|
|
|
#[derive(Subcommand)]
|
|
enum MemoryCommands {
|
|
/// Search for memories using semantic search
|
|
Search {
|
|
/// Agent ID
|
|
agent_id: String,
|
|
|
|
/// Search query
|
|
query: String,
|
|
|
|
/// Fact types to search (world, agent, opinion)
|
|
#[arg(short = 't', long, value_delimiter = ',', default_values = &["world", "agent", "opinion"])]
|
|
fact_type: Vec<String>,
|
|
|
|
/// Thinking budget
|
|
#[arg(short = 'b', long, default_value = "100")]
|
|
budget: i32,
|
|
|
|
/// Maximum tokens for results
|
|
#[arg(long, default_value = "4096")]
|
|
max_tokens: i32,
|
|
|
|
/// Show trace information
|
|
#[arg(long)]
|
|
trace: bool,
|
|
},
|
|
|
|
/// Generate answers using agent identity
|
|
Think {
|
|
/// Agent ID
|
|
agent_id: String,
|
|
|
|
/// Query to think about
|
|
query: String,
|
|
|
|
/// Thinking budget
|
|
#[arg(short = 'b', long, default_value = "50")]
|
|
budget: i32,
|
|
|
|
/// Additional context
|
|
#[arg(short = 'c', long)]
|
|
context: Option<String>,
|
|
},
|
|
|
|
/// Store a single memory
|
|
Put {
|
|
/// Agent ID
|
|
agent_id: String,
|
|
|
|
/// Memory content
|
|
content: String,
|
|
|
|
/// Document ID (auto-generated if not provided)
|
|
#[arg(short = 'd', long)]
|
|
doc_id: Option<String>,
|
|
|
|
/// Context for the memory
|
|
#[arg(short = 'c', long)]
|
|
context: Option<String>,
|
|
|
|
/// Queue for background processing
|
|
#[arg(long)]
|
|
r#async: bool,
|
|
},
|
|
|
|
/// Bulk import memories from files
|
|
PutFiles {
|
|
/// Agent ID
|
|
agent_id: String,
|
|
|
|
/// Path to file or directory
|
|
path: PathBuf,
|
|
|
|
/// Search directories recursively
|
|
#[arg(short = 'r', long, default_value = "true")]
|
|
recursive: bool,
|
|
|
|
/// Context for all memories
|
|
#[arg(short = 'c', long)]
|
|
context: Option<String>,
|
|
|
|
/// Queue for background processing
|
|
#[arg(long)]
|
|
r#async: bool,
|
|
},
|
|
|
|
/// Delete a memory unit
|
|
Delete {
|
|
/// Agent ID
|
|
agent_id: String,
|
|
|
|
/// Memory unit ID
|
|
unit_id: String,
|
|
},
|
|
}
|
|
|
|
#[derive(Subcommand)]
|
|
enum DocumentCommands {
|
|
/// List documents for an agent
|
|
List {
|
|
/// Agent ID
|
|
agent_id: String,
|
|
|
|
/// Search query to filter documents
|
|
#[arg(short = 'q', long)]
|
|
query: Option<String>,
|
|
|
|
/// Maximum number of results
|
|
#[arg(short = 'l', long, default_value = "100")]
|
|
limit: i32,
|
|
|
|
/// Offset for pagination
|
|
#[arg(short = 's', long, default_value = "0")]
|
|
offset: i32,
|
|
},
|
|
|
|
/// Get a specific document by ID
|
|
Get {
|
|
/// Agent ID
|
|
agent_id: String,
|
|
|
|
/// Document ID
|
|
document_id: String,
|
|
},
|
|
|
|
/// Delete a document and all its memory units
|
|
Delete {
|
|
/// Agent ID
|
|
agent_id: String,
|
|
|
|
/// Document ID
|
|
document_id: String,
|
|
},
|
|
}
|
|
|
|
#[derive(Subcommand)]
|
|
enum OperationCommands {
|
|
/// List async operations for an agent
|
|
List {
|
|
/// Agent ID
|
|
agent_id: String,
|
|
},
|
|
|
|
/// Cancel a pending async operation
|
|
Cancel {
|
|
/// Agent ID
|
|
agent_id: String,
|
|
|
|
/// Operation ID
|
|
operation_id: String,
|
|
},
|
|
}
|
|
|
|
fn main() {
|
|
if let Err(e) = run() {
|
|
std::process::exit(1);
|
|
}
|
|
}
|
|
|
|
fn run() -> Result<()> {
|
|
let cli = Cli::parse();
|
|
|
|
let output_format: OutputFormat = cli.output.into();
|
|
let verbose = cli.verbose;
|
|
|
|
// Load configuration
|
|
let config = Config::from_env().unwrap_or_else(|e| {
|
|
ui::print_error(&format!("Configuration error: {}", e));
|
|
errors::print_config_help();
|
|
std::process::exit(1);
|
|
});
|
|
|
|
let api_url = config.api_url().to_string();
|
|
|
|
// Create API client
|
|
let client = ApiClient::new(api_url.clone()).unwrap_or_else(|e| {
|
|
errors::handle_api_error(e, &api_url);
|
|
});
|
|
|
|
// Execute command and handle errors
|
|
let result: Result<()> = match cli.command {
|
|
Commands::Agent(agent_cmd) => match agent_cmd {
|
|
AgentCommands::List => {
|
|
let spinner = if output_format == OutputFormat::Pretty {
|
|
Some(ui::create_spinner("Fetching agents..."))
|
|
} else {
|
|
None
|
|
};
|
|
|
|
let response = client.list_agents(verbose);
|
|
|
|
if let Some(sp) = spinner {
|
|
sp.finish_and_clear();
|
|
}
|
|
|
|
match response {
|
|
Ok(agents_list) => {
|
|
if output_format == OutputFormat::Pretty {
|
|
if agents_list.agents.is_empty() {
|
|
ui::print_warning("No agents found");
|
|
} else {
|
|
ui::print_info(&format!("Found {} agent(s)", agents_list.agents.len()));
|
|
for agent in &agents_list.agents {
|
|
println!(" - {}", agent);
|
|
}
|
|
}
|
|
} else {
|
|
output::print_output(&agents_list, output_format)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
Err(e) => Err(e)
|
|
}
|
|
}
|
|
|
|
AgentCommands::Profile { agent_id } => {
|
|
let spinner = if output_format == OutputFormat::Pretty {
|
|
Some(ui::create_spinner("Fetching profile..."))
|
|
} else {
|
|
None
|
|
};
|
|
|
|
let response = client.get_profile(&agent_id, verbose);
|
|
|
|
if let Some(sp) = spinner {
|
|
sp.finish_and_clear();
|
|
}
|
|
|
|
match response {
|
|
Ok(profile) => {
|
|
if output_format == OutputFormat::Pretty {
|
|
ui::print_info(&format!("Profile for agent '{}'", agent_id));
|
|
|
|
if let Some(personality) = &profile.personality {
|
|
println!("\n Personality Traits:");
|
|
println!(" Openness: {:.2}", personality.openness);
|
|
println!(" Conscientiousness: {:.2}", personality.conscientiousness);
|
|
println!(" Extraversion: {:.2}", personality.extraversion);
|
|
println!(" Agreeableness: {:.2}", personality.agreeableness);
|
|
println!(" Neuroticism: {:.2}", personality.neuroticism);
|
|
println!(" Bias Strength: {:.2}", personality.bias_strength);
|
|
}
|
|
|
|
if !profile.background.is_empty() {
|
|
println!("\n Background:\n{}", profile.background);
|
|
}
|
|
} else {
|
|
output::print_output(&profile, output_format)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
Err(e) => Err(e)
|
|
}
|
|
}
|
|
|
|
AgentCommands::Stats { agent_id } => {
|
|
let spinner = if output_format == OutputFormat::Pretty {
|
|
Some(ui::create_spinner("Fetching statistics..."))
|
|
} else {
|
|
None
|
|
};
|
|
|
|
let response = client.get_stats(&agent_id, verbose);
|
|
|
|
if let Some(sp) = spinner {
|
|
sp.finish_and_clear();
|
|
}
|
|
|
|
match response {
|
|
Ok(stats) => {
|
|
if output_format == OutputFormat::Pretty {
|
|
ui::print_info(&format!("Statistics for agent '{}'", agent_id));
|
|
println!();
|
|
|
|
println!(" 📊 Overview");
|
|
println!(" Total Memory Units: {}", stats.total_nodes);
|
|
println!(" Total Links: {}", stats.total_links);
|
|
println!(" Total Documents: {}", stats.total_documents);
|
|
println!();
|
|
|
|
println!(" 🧠 Memory Units by Type");
|
|
let mut fact_types: Vec<_> = stats.nodes_by_fact_type.iter().collect();
|
|
fact_types.sort_by_key(|(k, _)| *k);
|
|
for (fact_type, count) in fact_types {
|
|
let icon = match fact_type.as_str() {
|
|
"world" => "🌍",
|
|
"agent" => "🤖",
|
|
"opinion" => "💭",
|
|
_ => "•"
|
|
};
|
|
println!(" {} {:<10} {}", icon, fact_type, count);
|
|
}
|
|
println!();
|
|
|
|
println!(" 🔗 Links by Type");
|
|
let mut link_types: Vec<_> = stats.links_by_link_type.iter().collect();
|
|
link_types.sort_by_key(|(k, _)| *k);
|
|
for (link_type, count) in link_types {
|
|
let icon = match link_type.as_str() {
|
|
"temporal" => "⏰",
|
|
"semantic" => "🔤",
|
|
"entity" => "🏷️",
|
|
_ => "•"
|
|
};
|
|
println!(" {} {:<10} {}", icon, link_type, count);
|
|
}
|
|
println!();
|
|
|
|
println!(" 🔗 Links by Fact Type");
|
|
let mut fact_type_links: Vec<_> = stats.links_by_fact_type.iter().collect();
|
|
fact_type_links.sort_by_key(|(k, _)| *k);
|
|
for (fact_type, count) in fact_type_links {
|
|
let icon = match fact_type.as_str() {
|
|
"world" => "🌍",
|
|
"agent" => "🤖",
|
|
"opinion" => "💭",
|
|
_ => "•"
|
|
};
|
|
println!(" {} {:<10} {}", icon, fact_type, count);
|
|
}
|
|
println!();
|
|
|
|
if !stats.links_breakdown.is_empty() {
|
|
println!(" 📈 Detailed Link Breakdown");
|
|
let mut fact_types: Vec<_> = stats.links_breakdown.iter().collect();
|
|
fact_types.sort_by_key(|(k, _)| *k);
|
|
for (fact_type, link_types) in fact_types {
|
|
let icon = match fact_type.as_str() {
|
|
"world" => "🌍",
|
|
"agent" => "🤖",
|
|
"opinion" => "💭",
|
|
_ => "•"
|
|
};
|
|
println!(" {} {}", icon, fact_type);
|
|
let mut sorted_links: Vec<_> = link_types.iter().collect();
|
|
sorted_links.sort_by_key(|(k, _)| *k);
|
|
for (link_type, count) in sorted_links {
|
|
println!(" - {:<10} {}", link_type, count);
|
|
}
|
|
}
|
|
println!();
|
|
}
|
|
|
|
if stats.pending_operations > 0 || stats.failed_operations > 0 {
|
|
println!(" ⚙️ Operations");
|
|
if stats.pending_operations > 0 {
|
|
println!(" ⏳ Pending: {}", stats.pending_operations);
|
|
}
|
|
if stats.failed_operations > 0 {
|
|
println!(" ❌ Failed: {}", stats.failed_operations);
|
|
}
|
|
}
|
|
} else {
|
|
output::print_output(&stats, output_format)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
Err(e) => Err(e)
|
|
}
|
|
}
|
|
|
|
AgentCommands::SetPersonality {
|
|
agent_id,
|
|
openness,
|
|
conscientiousness,
|
|
extraversion,
|
|
agreeableness,
|
|
neuroticism,
|
|
bias_strength,
|
|
} => {
|
|
let spinner = if output_format == OutputFormat::Pretty {
|
|
Some(ui::create_spinner("Updating personality..."))
|
|
} else {
|
|
None
|
|
};
|
|
|
|
let personality = PersonalityTraits {
|
|
openness,
|
|
conscientiousness,
|
|
extraversion,
|
|
agreeableness,
|
|
neuroticism,
|
|
bias_strength,
|
|
};
|
|
|
|
let response = client.set_personality(&agent_id, personality, verbose);
|
|
|
|
if let Some(sp) = spinner {
|
|
sp.finish_and_clear();
|
|
}
|
|
|
|
match response {
|
|
Ok(profile) => {
|
|
if output_format == OutputFormat::Pretty {
|
|
ui::print_success("Personality updated successfully");
|
|
if let Some(p) = &profile.personality {
|
|
println!(" Openness: {:.2}", p.openness);
|
|
println!(" Conscientiousness: {:.2}", p.conscientiousness);
|
|
println!(" Extraversion: {:.2}", p.extraversion);
|
|
println!(" Agreeableness: {:.2}", p.agreeableness);
|
|
println!(" Neuroticism: {:.2}", p.neuroticism);
|
|
println!(" Bias Strength: {:.2}", p.bias_strength);
|
|
}
|
|
} else {
|
|
output::print_output(&profile, output_format)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
Err(e) => Err(e)
|
|
}
|
|
}
|
|
|
|
AgentCommands::SetBackground {
|
|
agent_id,
|
|
content,
|
|
no_update_personality,
|
|
} => {
|
|
let current_profile = if !no_update_personality {
|
|
client.get_profile(&agent_id, verbose).ok()
|
|
} else {
|
|
None
|
|
};
|
|
|
|
let spinner = if output_format == OutputFormat::Pretty {
|
|
Some(ui::create_spinner("Merging background..."))
|
|
} else {
|
|
None
|
|
};
|
|
|
|
let response = client.add_background(
|
|
&agent_id,
|
|
&content,
|
|
!no_update_personality,
|
|
verbose,
|
|
);
|
|
|
|
if let Some(sp) = spinner {
|
|
sp.finish_and_clear();
|
|
}
|
|
|
|
match response {
|
|
Ok(profile) => {
|
|
if output_format == OutputFormat::Pretty {
|
|
ui::print_success("Background updated successfully");
|
|
println!("\n{}", profile.background);
|
|
|
|
if !no_update_personality {
|
|
if let (Some(old_p), Some(new_p)) =
|
|
(current_profile.as_ref().map(|p| p.personality), &profile.personality)
|
|
{
|
|
println!("\nPersonality changes:");
|
|
println!(" Openness: {:.2} → {:.2}", old_p.openness, new_p.openness);
|
|
println!(" Conscientiousness: {:.2} → {:.2}", old_p.conscientiousness, new_p.conscientiousness);
|
|
println!(" Extraversion: {:.2} → {:.2}", old_p.extraversion, new_p.extraversion);
|
|
println!(" Agreeableness: {:.2} → {:.2}", old_p.agreeableness, new_p.agreeableness);
|
|
println!(" Neuroticism: {:.2} → {:.2}", old_p.neuroticism, new_p.neuroticism);
|
|
}
|
|
}
|
|
} else {
|
|
output::print_output(&profile, output_format)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
Err(e) => Err(e)
|
|
}
|
|
}
|
|
},
|
|
|
|
Commands::Memory(memory_cmd) => match memory_cmd {
|
|
MemoryCommands::Search {
|
|
agent_id,
|
|
query,
|
|
fact_type,
|
|
budget,
|
|
max_tokens,
|
|
trace,
|
|
} => {
|
|
let spinner = if output_format == OutputFormat::Pretty {
|
|
Some(ui::create_spinner("Searching memories..."))
|
|
} else {
|
|
None
|
|
};
|
|
|
|
let request = SearchRequest {
|
|
query,
|
|
fact_type,
|
|
thinking_budget: budget,
|
|
max_tokens,
|
|
trace,
|
|
};
|
|
|
|
let response = client.search(&agent_id, request, verbose);
|
|
|
|
if let Some(sp) = spinner {
|
|
sp.finish_and_clear();
|
|
}
|
|
|
|
match response {
|
|
Ok(result) => {
|
|
output::print_output(&result, output_format)?;
|
|
Ok(())
|
|
}
|
|
Err(e) => Err(e)
|
|
}
|
|
}
|
|
|
|
MemoryCommands::Think {
|
|
agent_id,
|
|
query,
|
|
budget,
|
|
context,
|
|
} => {
|
|
let spinner = if output_format == OutputFormat::Pretty {
|
|
Some(ui::create_spinner("Thinking..."))
|
|
} else {
|
|
None
|
|
};
|
|
|
|
let request = ThinkRequest {
|
|
query,
|
|
thinking_budget: budget,
|
|
context,
|
|
};
|
|
|
|
let response = client.think(&agent_id, request, verbose);
|
|
|
|
if let Some(sp) = spinner {
|
|
sp.finish_and_clear();
|
|
}
|
|
|
|
match response {
|
|
Ok(result) => {
|
|
output::print_output(&result, output_format)?;
|
|
Ok(())
|
|
}
|
|
Err(e) => Err(e)
|
|
}
|
|
}
|
|
|
|
MemoryCommands::Put {
|
|
agent_id,
|
|
content,
|
|
doc_id,
|
|
context,
|
|
r#async,
|
|
} => {
|
|
let doc_id = doc_id.unwrap_or_else(config::generate_doc_id);
|
|
|
|
let spinner = if output_format == OutputFormat::Pretty {
|
|
Some(ui::create_spinner("Storing memory..."))
|
|
} else {
|
|
None
|
|
};
|
|
|
|
let item = MemoryItem {
|
|
content: content.clone(),
|
|
context,
|
|
};
|
|
|
|
let request = BatchMemoryRequest {
|
|
items: vec![item],
|
|
document_id: Some(doc_id.clone()),
|
|
};
|
|
|
|
let response = client.put_memories(&agent_id, request, r#async, verbose);
|
|
|
|
if let Some(sp) = spinner {
|
|
sp.finish_and_clear();
|
|
}
|
|
|
|
match response {
|
|
Ok(result) => {
|
|
if output_format == OutputFormat::Pretty {
|
|
ui::print_success(&format!(
|
|
"Memory stored successfully (document: {})",
|
|
doc_id
|
|
));
|
|
if let Some(op_id) = result.job_id {
|
|
println!(" Operation ID: {}", op_id);
|
|
println!(" Status: queued for background processing");
|
|
} else {
|
|
println!(" Stored count: {}", result.stored_count.unwrap_or(0));
|
|
}
|
|
} else {
|
|
output::print_output(&result, output_format)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
Err(e) => Err(e)
|
|
}
|
|
}
|
|
|
|
MemoryCommands::PutFiles {
|
|
agent_id,
|
|
path,
|
|
recursive,
|
|
context,
|
|
r#async,
|
|
} => {
|
|
if !path.exists() {
|
|
anyhow::bail!("Path does not exist: {}", path.display());
|
|
}
|
|
|
|
let mut files = Vec::new();
|
|
|
|
if path.is_file() {
|
|
files.push(path);
|
|
} else if path.is_dir() {
|
|
if recursive {
|
|
for entry in WalkDir::new(&path)
|
|
.into_iter()
|
|
.filter_map(|e| e.ok())
|
|
.filter(|e| e.file_type().is_file())
|
|
{
|
|
let path = entry.path();
|
|
if let Some(ext) = path.extension() {
|
|
if ext == "txt" || ext == "md" {
|
|
files.push(path.to_path_buf());
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
for entry in fs::read_dir(&path)? {
|
|
let entry = entry?;
|
|
let path = entry.path();
|
|
if path.is_file() {
|
|
if let Some(ext) = path.extension() {
|
|
if ext == "txt" || ext == "md" {
|
|
files.push(path);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if files.is_empty() {
|
|
ui::print_warning("No .txt or .md files found");
|
|
return Ok(());
|
|
}
|
|
|
|
ui::print_info(&format!("Found {} files to import", files.len()));
|
|
|
|
let pb = ui::create_progress_bar(files.len() as u64, "Processing files");
|
|
|
|
let mut items = Vec::new();
|
|
let mut document_id = None;
|
|
|
|
for file_path in &files {
|
|
let content = fs::read_to_string(file_path)
|
|
.with_context(|| format!("Failed to read file: {}", file_path.display()))?;
|
|
|
|
let doc_id = file_path
|
|
.file_stem()
|
|
.and_then(|s| s.to_str())
|
|
.map(|s| s.to_string())
|
|
.unwrap_or_else(config::generate_doc_id);
|
|
|
|
if document_id.is_none() {
|
|
document_id = Some(doc_id);
|
|
}
|
|
|
|
items.push(MemoryItem {
|
|
content,
|
|
context: context.clone(),
|
|
});
|
|
|
|
pb.inc(1);
|
|
}
|
|
|
|
pb.finish_with_message("Files processed");
|
|
|
|
let spinner = if output_format == OutputFormat::Pretty {
|
|
Some(ui::create_spinner("Uploading memories..."))
|
|
} else {
|
|
None
|
|
};
|
|
|
|
let request = BatchMemoryRequest {
|
|
items,
|
|
document_id,
|
|
};
|
|
|
|
let response = client.put_memories(&agent_id, request, r#async, verbose);
|
|
|
|
if let Some(sp) = spinner {
|
|
sp.finish_and_clear();
|
|
}
|
|
|
|
match response {
|
|
Ok(result) => {
|
|
if output_format == OutputFormat::Pretty {
|
|
ui::print_success("Files imported successfully");
|
|
if let Some(op_id) = result.job_id {
|
|
println!(" Operation ID: {}", op_id);
|
|
println!(" Status: queued for background processing");
|
|
} else {
|
|
println!(" Total units created: {}", result.stored_count.unwrap_or(0));
|
|
}
|
|
} else {
|
|
output::print_output(&result, output_format)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
Err(e) => Err(e)
|
|
}
|
|
}
|
|
|
|
MemoryCommands::Delete { agent_id, unit_id } => {
|
|
let spinner = if output_format == OutputFormat::Pretty {
|
|
Some(ui::create_spinner("Deleting memory unit..."))
|
|
} else {
|
|
None
|
|
};
|
|
|
|
let response = client.delete_memory(&agent_id, &unit_id, verbose);
|
|
|
|
if let Some(sp) = spinner {
|
|
sp.finish_and_clear();
|
|
}
|
|
|
|
match response {
|
|
Ok(result) => {
|
|
if output_format == OutputFormat::Pretty {
|
|
if result.success {
|
|
ui::print_success(&result.message);
|
|
} else {
|
|
ui::print_error(&result.message);
|
|
}
|
|
} else {
|
|
output::print_output(&result, output_format)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
Err(e) => Err(e)
|
|
}
|
|
}
|
|
},
|
|
|
|
Commands::Document(doc_cmd) => match doc_cmd {
|
|
DocumentCommands::List {
|
|
agent_id,
|
|
query,
|
|
limit,
|
|
offset,
|
|
} => {
|
|
let spinner = if output_format == OutputFormat::Pretty {
|
|
Some(ui::create_spinner("Fetching documents..."))
|
|
} else {
|
|
None
|
|
};
|
|
|
|
let response = client.list_documents(&agent_id, query.as_deref(), Some(limit), Some(offset), verbose);
|
|
|
|
if let Some(sp) = spinner {
|
|
sp.finish_and_clear();
|
|
}
|
|
|
|
match response {
|
|
Ok(docs_response) => {
|
|
if output_format == OutputFormat::Pretty {
|
|
ui::print_info(&format!("Documents for agent '{}' (total: {})", agent_id, docs_response.total));
|
|
for doc in &docs_response.items {
|
|
println!("\n Document ID: {}", doc.id);
|
|
println!(" Created: {}", doc.created_at);
|
|
println!(" Updated: {}", doc.updated_at);
|
|
println!(" Text Length: {}", doc.text_length);
|
|
println!(" Memory Units: {}", doc.memory_unit_count);
|
|
}
|
|
} else {
|
|
output::print_output(&docs_response, output_format)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
Err(e) => Err(e)
|
|
}
|
|
}
|
|
|
|
DocumentCommands::Get { agent_id, document_id } => {
|
|
let spinner = if output_format == OutputFormat::Pretty {
|
|
Some(ui::create_spinner("Fetching document..."))
|
|
} else {
|
|
None
|
|
};
|
|
|
|
let response = client.get_document(&agent_id, &document_id, verbose);
|
|
|
|
if let Some(sp) = spinner {
|
|
sp.finish_and_clear();
|
|
}
|
|
|
|
match response {
|
|
Ok(doc) => {
|
|
if output_format == OutputFormat::Pretty {
|
|
ui::print_info(&format!("Document: {}", doc.id));
|
|
println!(" Agent ID: {}", doc.agent_id);
|
|
println!(" Created: {}", doc.created_at);
|
|
println!(" Updated: {}", doc.updated_at);
|
|
println!(" Memory Units: {}", doc.memory_unit_count);
|
|
println!("\n Text:\n{}", doc.original_text);
|
|
} else {
|
|
output::print_output(&doc, output_format)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
Err(e) => Err(e)
|
|
}
|
|
}
|
|
|
|
DocumentCommands::Delete { agent_id, document_id } => {
|
|
let spinner = if output_format == OutputFormat::Pretty {
|
|
Some(ui::create_spinner("Deleting document..."))
|
|
} else {
|
|
None
|
|
};
|
|
|
|
let response = client.delete_document(&agent_id, &document_id, verbose);
|
|
|
|
if let Some(sp) = spinner {
|
|
sp.finish_and_clear();
|
|
}
|
|
|
|
match response {
|
|
Ok(result) => {
|
|
if output_format == OutputFormat::Pretty {
|
|
if result.success {
|
|
ui::print_success(&result.message);
|
|
} else {
|
|
ui::print_error(&result.message);
|
|
}
|
|
} else {
|
|
output::print_output(&result, output_format)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
Err(e) => Err(e)
|
|
}
|
|
}
|
|
},
|
|
|
|
Commands::Operation(op_cmd) => match op_cmd {
|
|
OperationCommands::List { agent_id } => {
|
|
let spinner = if output_format == OutputFormat::Pretty {
|
|
Some(ui::create_spinner("Fetching operations..."))
|
|
} else {
|
|
None
|
|
};
|
|
|
|
let response = client.list_operations(&agent_id, verbose);
|
|
|
|
if let Some(sp) = spinner {
|
|
sp.finish_and_clear();
|
|
}
|
|
|
|
match response {
|
|
Ok(ops_response) => {
|
|
if output_format == OutputFormat::Pretty {
|
|
if ops_response.operations.is_empty() {
|
|
ui::print_info("No operations found");
|
|
} else {
|
|
ui::print_info(&format!("Found {} operation(s)", ops_response.operations.len()));
|
|
for op in &ops_response.operations {
|
|
println!("\n Operation ID: {}", op.id);
|
|
println!(" Type: {}", op.task_type);
|
|
println!(" Status: {}", op.status);
|
|
println!(" Items: {}", op.items_count);
|
|
if let Some(doc_id) = &op.document_id {
|
|
println!(" Document ID: {}", doc_id);
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
output::print_output(&ops_response, output_format)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
Err(e) => Err(e)
|
|
}
|
|
}
|
|
|
|
OperationCommands::Cancel { agent_id, operation_id } => {
|
|
let spinner = if output_format == OutputFormat::Pretty {
|
|
Some(ui::create_spinner("Cancelling operation..."))
|
|
} else {
|
|
None
|
|
};
|
|
|
|
let response = client.cancel_operation(&agent_id, &operation_id, verbose);
|
|
|
|
if let Some(sp) = spinner {
|
|
sp.finish_and_clear();
|
|
}
|
|
|
|
match response {
|
|
Ok(result) => {
|
|
if output_format == OutputFormat::Pretty {
|
|
if result.success {
|
|
ui::print_success(&result.message);
|
|
} else {
|
|
ui::print_error(&result.message);
|
|
}
|
|
} else {
|
|
output::print_output(&result, output_format)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
Err(e) => Err(e)
|
|
}
|
|
}
|
|
},
|
|
};
|
|
|
|
// Handle API errors with nice messages
|
|
if let Err(e) = result {
|
|
errors::handle_api_error(e, &api_url);
|
|
}
|
|
|
|
Ok(())
|
|
}
|