mod commands;
mod config;
pub mod error;
mod output;
use clap::{value_parser, Arg, ArgAction, Command};
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
use crate::commands::{
admin, agent, analytics, completion, config as config_cmd, health, index, init, keys,
knowledge, memory, namespace, ops, session, vector,
};
use crate::config::Config;
#[derive(Clone, Copy, Debug, Default)]
pub enum OutputFormat {
#[default]
Table,
Json,
Compact,
}
impl From<&str> for OutputFormat {
fn from(s: &str) -> Self {
match s.to_lowercase().as_str() {
"json" => OutputFormat::Json,
"compact" => OutputFormat::Compact,
_ => OutputFormat::Table,
}
}
}
fn build_cli() -> Command {
Command::new("dk")
.version(env!("CARGO_PKG_VERSION"))
.author("Dakera Team")
.about("Dakera CLI - Manage your AI agent memory platform from the command line")
.after_help(
"Examples:\n dk health\n dk namespace list\n dk memory store my-agent 'Completed task X' --importance 0.8\n dk memory recall my-agent 'recent tasks' --top-k 5\n dk vector upsert -n my-ns --file vectors.json\n dk completion zsh --install\n\nError exit codes:\n 0 success\n 1 general error\n 2 connection error (server unreachable)\n 3 not found\n 4 permission denied\n 5 invalid input\n 6 server error",
)
.arg(
Arg::new("url")
.short('u')
.long("url")
.env("DAKERA_URL")
.default_value("http://localhost:3000")
.help("Server URL"),
)
.arg(
Arg::new("format")
.short('f')
.long("format")
.default_value("table")
.value_parser(["table", "json", "compact"])
.help("Output format"),
)
.arg(
Arg::new("verbose")
.short('v')
.long("verbose")
.action(ArgAction::SetTrue)
.help("Enable verbose output"),
)
.arg(
Arg::new("profile")
.short('p')
.long("profile")
.env("DAKERA_PROFILE")
.help("Named server profile to use (overrides active_profile in config)"),
)
.subcommand(
Command::new("init")
.about("Interactive setup wizard — configure server URL and default namespace"),
)
.subcommand(
Command::new("health")
.about("Check server health and connectivity")
.arg(
Arg::new("detailed")
.short('d')
.long("detailed")
.action(ArgAction::SetTrue)
.help("Show detailed health information"),
),
)
.subcommand(build_namespace_command())
.subcommand(build_vector_command())
.subcommand(build_index_command())
.subcommand(build_ops_command())
.subcommand(build_memory_command())
.subcommand(build_session_command())
.subcommand(build_agent_command())
.subcommand(build_knowledge_command())
.subcommand(build_analytics_command())
.subcommand(build_admin_command())
.subcommand(build_keys_command())
.subcommand(build_config_command())
.subcommand(build_completion_command())
}
fn build_config_command() -> Command {
Command::new("config")
.about("Show configuration or manage server profiles")
.arg(
Arg::new("show")
.long("show")
.action(ArgAction::SetTrue)
.help("Show current configuration (default action)"),
)
.subcommand(
Command::new("profile")
.about("Manage named server profiles")
.subcommand(
Command::new("add")
.about("Add or update a named profile")
.arg(
Arg::new("name")
.required(true)
.help("Profile name (e.g. local, staging, prod)"),
)
.arg(
Arg::new("url")
.short('u')
.long("url")
.required(true)
.help("Server URL for this profile"),
)
.arg(
Arg::new("namespace")
.short('n')
.long("namespace")
.help("Default namespace for this profile"),
),
)
.subcommand(
Command::new("use").about("Switch the active profile").arg(
Arg::new("name")
.required(true)
.help("Profile name to activate"),
),
)
.subcommand(Command::new("list").about("List all profiles")),
)
}
fn build_completion_command() -> Command {
Command::new("completion")
.about("Generate shell completion scripts")
.long_about(
"Generate shell completion scripts for bash, zsh, or fish.\n\
\n\
Print to stdout:\n\
\n dk completion bash\n\
\n dk completion zsh\n\
\n dk completion fish\n\
\nInstall automatically:\n\
\n dk completion bash --install\n\
\n dk completion zsh --install\n\
\n dk completion fish --install\n\
\nDynamic completion provides namespace and agent names from the live server.",
)
.arg(
Arg::new("shell")
.required(true)
.value_parser(["bash", "zsh", "fish"])
.help("Shell to generate completion for"),
)
.arg(
Arg::new("install")
.long("install")
.action(ArgAction::SetTrue)
.help("Install the completion script to the appropriate location"),
)
}
fn build_namespace_command() -> Command {
Command::new("namespace")
.about("Manage namespaces")
.after_help(
"Examples:\n dk namespace list\n dk namespace get my-ns\n dk namespace create my-ns\n dk namespace delete my-ns --dry-run\n dk namespace delete my-ns --yes\n dk namespace policy get my-ns\n dk namespace policy set my-ns --consolidation-enabled true --rate-limit-enabled true --rate-limit-stores-per-minute 100",
)
.subcommand(Command::new("list").about("List all namespaces"))
.subcommand(
Command::new("get")
.about("Get namespace information")
.arg(Arg::new("name").required(true).help("Namespace name")),
)
.subcommand(
Command::new("create")
.about("Create a new namespace")
.arg(Arg::new("name").required(true).help("Namespace name"))
.arg(
Arg::new("dimension")
.short('d')
.long("dimension")
.value_parser(value_parser!(u32))
.help("Vector dimension"),
),
)
.subcommand(
Command::new("delete")
.about("Delete a namespace and all its data")
.after_help("Examples:\n dk namespace delete my-ns --dry-run\n dk namespace delete my-ns --yes")
.arg(Arg::new("name").required(true).help("Namespace name"))
.arg(
Arg::new("yes")
.short('y')
.long("yes")
.action(ArgAction::SetTrue)
.help("Skip confirmation prompt"),
)
.arg(
Arg::new("dry-run")
.long("dry-run")
.action(ArgAction::SetTrue)
.help("Show what would be deleted without making any changes"),
),
)
.subcommand(
Command::new("policy")
.about("Manage namespace memory lifecycle policy (TTLs, consolidation, rate limiting)")
.after_help("Examples:\n dk namespace policy get my-ns\n dk namespace policy set my-ns --consolidation-enabled true\n dk namespace policy set my-ns --rate-limit-enabled true --rate-limit-stores-per-minute 60")
.subcommand(
Command::new("get")
.about("Show the current memory policy for a namespace")
.arg(Arg::new("namespace").required(true).help("Namespace name")),
)
.subcommand(
Command::new("set")
.about("Update memory policy fields for a namespace (only supplied flags are changed)")
.arg(Arg::new("namespace").required(true).help("Namespace name"))
.arg(Arg::new("working-ttl").long("working-ttl").value_parser(value_parser!(u64)).help("TTL for working memories in seconds (default: 14400 = 4h)"))
.arg(Arg::new("episodic-ttl").long("episodic-ttl").value_parser(value_parser!(u64)).help("TTL for episodic memories in seconds (default: 2592000 = 30d)"))
.arg(Arg::new("semantic-ttl").long("semantic-ttl").value_parser(value_parser!(u64)).help("TTL for semantic memories in seconds (default: 31536000 = 365d)"))
.arg(Arg::new("procedural-ttl").long("procedural-ttl").value_parser(value_parser!(u64)).help("TTL for procedural memories in seconds (default: 63072000 = 730d)"))
.arg(Arg::new("working-decay").long("working-decay").value_parser(["exponential", "power_law", "logarithmic", "flat"]).help("Decay curve for working memories"))
.arg(Arg::new("episodic-decay").long("episodic-decay").value_parser(["exponential", "power_law", "logarithmic", "flat"]).help("Decay curve for episodic memories"))
.arg(Arg::new("semantic-decay").long("semantic-decay").value_parser(["exponential", "power_law", "logarithmic", "flat"]).help("Decay curve for semantic memories"))
.arg(Arg::new("procedural-decay").long("procedural-decay").value_parser(["exponential", "power_law", "logarithmic", "flat"]).help("Decay curve for procedural memories"))
.arg(Arg::new("spaced-repetition-factor").long("spaced-repetition-factor").value_parser(value_parser!(f64)).help("TTL extension multiplier per recall hit (default: 1.0; 0.0 = disabled)"))
.arg(Arg::new("spaced-repetition-base-interval").long("spaced-repetition-base-interval").value_parser(value_parser!(u64)).help("Base interval in seconds for spaced repetition TTL extension (default: 86400 = 1d)"))
.arg(Arg::new("consolidation-enabled").long("consolidation-enabled").value_parser(value_parser!(bool)).help("Enable background DBSCAN deduplication (default: false)"))
.arg(Arg::new("consolidation-threshold").long("consolidation-threshold").value_parser(value_parser!(f32)).help("DBSCAN cosine-similarity threshold (default: 0.92; higher = stricter)"))
.arg(Arg::new("consolidation-interval-hours").long("consolidation-interval-hours").value_parser(value_parser!(u32)).help("Background consolidation sweep interval in hours (default: 24)"))
.arg(Arg::new("rate-limit-enabled").long("rate-limit-enabled").value_parser(value_parser!(bool)).help("Enable per-namespace store/recall rate limiting (default: false)"))
.arg(Arg::new("rate-limit-stores-per-minute").long("rate-limit-stores-per-minute").value_parser(value_parser!(u32)).help("Max store operations per minute (omit for unlimited)"))
.arg(Arg::new("rate-limit-recalls-per-minute").long("rate-limit-recalls-per-minute").value_parser(value_parser!(u32)).help("Max recall operations per minute (omit for unlimited)")),
),
)
}
fn build_vector_command() -> Command {
Command::new("vector")
.about("Manage vectors")
.subcommand(
Command::new("upsert")
.about("Upsert vectors from a JSON file")
.arg(
Arg::new("namespace")
.short('n')
.long("namespace")
.required(true)
.help("Namespace name"),
)
.arg(
Arg::new("file")
.short('f')
.long("file")
.required(true)
.help("JSON file containing vectors"),
)
.arg(
Arg::new("batch-size")
.short('b')
.long("batch-size")
.default_value("100")
.value_parser(value_parser!(usize))
.help("Batch size for large files"),
),
)
.subcommand(
Command::new("upsert-one")
.about("Upsert a single vector")
.arg(
Arg::new("namespace")
.short('n')
.long("namespace")
.required(true)
.help("Namespace name"),
)
.arg(
Arg::new("id")
.short('i')
.long("id")
.required(true)
.help("Vector ID"),
)
.arg(
Arg::new("values")
.short('V')
.long("values")
.required(true)
.value_delimiter(',')
.value_parser(value_parser!(f32))
.help("Vector values (comma-separated floats)"),
)
.arg(
Arg::new("metadata")
.short('m')
.long("metadata")
.help("Optional metadata as JSON string"),
),
)
.subcommand(
Command::new("query")
.about("Query for similar vectors")
.arg(
Arg::new("namespace")
.short('n')
.long("namespace")
.required(true)
.help("Namespace name"),
)
.arg(
Arg::new("values")
.short('V')
.long("values")
.required(true)
.value_delimiter(',')
.value_parser(value_parser!(f32))
.help("Query vector values (comma-separated floats)"),
)
.arg(
Arg::new("top-k")
.short('k')
.long("top-k")
.default_value("10")
.value_parser(value_parser!(u32))
.help("Number of results to return"),
)
.arg(
Arg::new("include-metadata")
.short('m')
.long("include-metadata")
.action(ArgAction::SetTrue)
.help("Include metadata in results"),
)
.arg(
Arg::new("filter")
.long("filter")
.help("Filter expression as JSON"),
),
)
.subcommand(
Command::new("query-file")
.about("Query from a file")
.arg(
Arg::new("namespace")
.short('n')
.long("namespace")
.required(true)
.help("Namespace name"),
)
.arg(
Arg::new("file")
.short('f')
.long("file")
.required(true)
.help("JSON file containing query"),
),
)
.subcommand(
Command::new("delete")
.about("Delete vectors by ID")
.after_help("Examples:\n dk vector delete -n my-ns --ids id1,id2 --dry-run\n dk vector delete -n my-ns --all --dry-run\n dk vector delete -n my-ns --ids id1,id2 --yes")
.arg(
Arg::new("namespace")
.short('n')
.long("namespace")
.required(true)
.help("Namespace name"),
)
.arg(
Arg::new("ids")
.short('i')
.long("ids")
.value_delimiter(',')
.help("Vector IDs to delete"),
)
.arg(
Arg::new("all")
.long("all")
.action(ArgAction::SetTrue)
.help("Delete all vectors (dangerous!)"),
)
.arg(
Arg::new("yes")
.short('y')
.long("yes")
.action(ArgAction::SetTrue)
.help("Skip confirmation prompt"),
)
.arg(
Arg::new("dry-run")
.long("dry-run")
.action(ArgAction::SetTrue)
.help("Show what would be deleted without making any changes"),
),
)
.subcommand(
Command::new("multi-search")
.about("Multi-vector search with positive/negative vectors and MMR")
.arg(
Arg::new("namespace")
.short('n')
.long("namespace")
.required(true)
.help("Namespace name"),
)
.arg(
Arg::new("file")
.short('f')
.long("file")
.required(true)
.help("JSON file with multi-vector search request"),
),
)
.subcommand(
Command::new("unified-query")
.about("Unified query combining vector and text search")
.arg(
Arg::new("namespace")
.short('n')
.long("namespace")
.required(true)
.help("Namespace name"),
)
.arg(
Arg::new("file")
.short('f')
.long("file")
.required(true)
.help("JSON file with unified query request"),
),
)
.subcommand(
Command::new("aggregate")
.about("Aggregate vectors with grouping")
.arg(
Arg::new("namespace")
.short('n')
.long("namespace")
.required(true)
.help("Namespace name"),
)
.arg(
Arg::new("file")
.short('f')
.long("file")
.required(true)
.help("JSON file with aggregation request"),
),
)
.subcommand(
Command::new("export")
.about("Export vectors with pagination")
.arg(
Arg::new("namespace")
.short('n')
.long("namespace")
.required(true)
.help("Namespace name"),
)
.arg(
Arg::new("cursor")
.short('c')
.long("cursor")
.help("Pagination cursor from previous export"),
)
.arg(
Arg::new("limit")
.short('l')
.long("limit")
.default_value("100")
.value_parser(value_parser!(u32))
.help("Maximum number of vectors to export"),
)
.arg(
Arg::new("include-vectors")
.long("include-vectors")
.action(ArgAction::SetTrue)
.help("Include vector values in export"),
),
)
.subcommand(
Command::new("explain")
.about("Explain query execution plan")
.arg(
Arg::new("namespace")
.short('n')
.long("namespace")
.required(true)
.help("Namespace name"),
)
.arg(
Arg::new("values")
.short('V')
.long("values")
.required(true)
.value_delimiter(',')
.value_parser(value_parser!(f32))
.help("Query vector values (comma-separated floats)"),
)
.arg(
Arg::new("top-k")
.short('k')
.long("top-k")
.default_value("10")
.value_parser(value_parser!(u32))
.help("Number of results to return"),
)
.arg(
Arg::new("include-metadata")
.short('m')
.long("include-metadata")
.action(ArgAction::SetTrue)
.help("Include metadata in results"),
),
)
.subcommand(
Command::new("upsert-columns")
.about("Column-format vector upsert from JSON file")
.arg(
Arg::new("namespace")
.short('n')
.long("namespace")
.required(true)
.help("Namespace name"),
)
.arg(
Arg::new("file")
.short('f')
.long("file")
.required(true)
.help("JSON file with column upsert data"),
),
)
}
fn build_index_command() -> Command {
Command::new("index")
.about("Manage indexes")
.subcommand(
Command::new("stats")
.about("Get index statistics for a namespace")
.arg(
Arg::new("namespace")
.short('n')
.long("namespace")
.required(true)
.help("Namespace name"),
),
)
.subcommand(
Command::new("fulltext-stats")
.about("Get full-text index statistics")
.arg(
Arg::new("namespace")
.short('n')
.long("namespace")
.required(true)
.help("Namespace name"),
),
)
.subcommand(
Command::new("rebuild")
.about("Rebuild index for a namespace")
.after_help("Examples:\n dk index rebuild -n my-ns --dry-run\n dk index rebuild -n my-ns --index-type vector --yes")
.arg(
Arg::new("namespace")
.short('n')
.long("namespace")
.required(true)
.help("Namespace name"),
)
.arg(
Arg::new("index-type")
.short('t')
.long("index-type")
.default_value("all")
.help("Index type to rebuild (vector, fulltext, all)"),
)
.arg(
Arg::new("yes")
.short('y')
.long("yes")
.action(ArgAction::SetTrue)
.help("Skip confirmation prompt"),
)
.arg(
Arg::new("dry-run")
.long("dry-run")
.action(ArgAction::SetTrue)
.help("Show what would be rebuilt without making any changes"),
),
)
}
fn build_ops_command() -> Command {
Command::new("ops")
.about("Operations and maintenance")
.subcommand(Command::new("diagnostics").about("Get system diagnostics"))
.subcommand(Command::new("jobs").about("List background jobs"))
.subcommand(
Command::new("job")
.about("Get specific job status")
.arg(Arg::new("id").required(true).help("Job ID")),
)
.subcommand(
Command::new("compact")
.about("Trigger index compaction")
.arg(
Arg::new("namespace")
.short('n')
.long("namespace")
.help("Target namespace (optional, compacts all if not specified)"),
)
.arg(
Arg::new("force")
.short('f')
.long("force")
.action(ArgAction::SetTrue)
.help("Force compaction even if not needed"),
),
)
.subcommand(
Command::new("shutdown")
.about("Gracefully shutdown the server")
.after_help("Examples:\n dk ops shutdown --dry-run\n dk ops shutdown --yes")
.arg(
Arg::new("yes")
.short('y')
.long("yes")
.action(ArgAction::SetTrue)
.help("Skip confirmation prompt"),
)
.arg(
Arg::new("dry-run")
.long("dry-run")
.action(ArgAction::SetTrue)
.help("Show what would happen without initiating the shutdown"),
),
)
.subcommand(Command::new("metrics").about("Show server metrics"))
.subcommand(
Command::new("stats")
.about("Show server statistics (version, state, vector count, uptime)"),
)
}
fn build_memory_command() -> Command {
Command::new("memory")
.about("Manage agent memories")
.subcommand(
Command::new("store")
.about("Store a memory for an agent")
.arg(Arg::new("agent_id").required(true).help("Agent ID"))
.arg(
Arg::new("content")
.required(true)
.help("Memory content text"),
)
.arg(
Arg::new("type")
.short('t')
.long("type")
.default_value("episodic")
.value_parser(["episodic", "semantic", "procedural", "working"])
.help("Memory type"),
)
.arg(
Arg::new("importance")
.short('i')
.long("importance")
.default_value("0.5")
.value_parser(value_parser!(f32))
.help("Importance score (0.0 to 1.0)"),
)
.arg(
Arg::new("session-id")
.short('s')
.long("session-id")
.help("Session ID to associate with"),
),
)
.subcommand(
Command::new("recall")
.about("Recall memories by semantic query")
.arg(Arg::new("agent_id").required(true).help("Agent ID"))
.arg(Arg::new("query").required(true).help("Search query"))
.arg(
Arg::new("top-k")
.short('k')
.long("top-k")
.default_value("5")
.value_parser(value_parser!(usize))
.help("Number of results to return"),
)
.arg(
Arg::new("type")
.short('t')
.long("type")
.value_parser(["episodic", "semantic", "procedural", "working"])
.help("Filter by memory type"),
),
)
.subcommand(
Command::new("get")
.about("Get a specific memory by ID")
.arg(Arg::new("agent_id").required(true).help("Agent ID"))
.arg(Arg::new("memory_id").required(true).help("Memory ID")),
)
.subcommand(
Command::new("update")
.about("Update an existing memory")
.arg(Arg::new("agent_id").required(true).help("Agent ID"))
.arg(Arg::new("memory_id").required(true).help("Memory ID"))
.arg(
Arg::new("content")
.short('c')
.long("content")
.help("New content text"),
)
.arg(
Arg::new("type")
.short('t')
.long("type")
.value_parser(["episodic", "semantic", "procedural", "working"])
.help("New memory type"),
),
)
.subcommand(
Command::new("forget")
.about("Delete a memory")
.arg(Arg::new("agent_id").required(true).help("Agent ID"))
.arg(
Arg::new("memory_id")
.required(true)
.help("Memory ID to delete"),
),
)
.subcommand(
Command::new("search")
.about("Search memories with advanced filters")
.arg(Arg::new("agent_id").required(true).help("Agent ID"))
.arg(Arg::new("query").required(true).help("Search query"))
.arg(
Arg::new("top-k")
.short('k')
.long("top-k")
.default_value("10")
.value_parser(value_parser!(usize))
.help("Number of results to return"),
)
.arg(
Arg::new("type")
.short('t')
.long("type")
.value_parser(["episodic", "semantic", "procedural", "working"])
.help("Filter by memory type"),
),
)
.subcommand(
Command::new("importance")
.about("Update importance score for memories")
.arg(Arg::new("agent_id").required(true).help("Agent ID"))
.arg(
Arg::new("ids")
.long("ids")
.required(true)
.help("Comma-separated memory IDs"),
)
.arg(
Arg::new("value")
.long("value")
.required(true)
.value_parser(value_parser!(f32))
.help("New importance value (0.0 to 1.0)"),
),
)
.subcommand(
Command::new("consolidate")
.about("Consolidate similar memories")
.arg(Arg::new("agent_id").required(true).help("Agent ID"))
.arg(
Arg::new("type")
.short('t')
.long("type")
.value_parser(["episodic", "semantic", "procedural", "working"])
.help("Filter by memory type"),
)
.arg(
Arg::new("threshold")
.long("threshold")
.default_value("0.8")
.value_parser(value_parser!(f32))
.help("Similarity threshold for consolidation"),
)
.arg(
Arg::new("dry-run")
.long("dry-run")
.action(ArgAction::SetTrue)
.help("Preview consolidation without applying changes"),
),
)
.subcommand(
Command::new("feedback")
.about("Submit feedback on a memory recall")
.arg(Arg::new("agent_id").required(true).help("Agent ID"))
.arg(Arg::new("memory_id").required(true).help("Memory ID"))
.arg(Arg::new("feedback").required(true).help("Feedback text"))
.arg(
Arg::new("score")
.short('s')
.long("score")
.value_parser(value_parser!(f32))
.help("Relevance score (0.0 to 1.0)"),
),
)
}
fn build_session_command() -> Command {
Command::new("session")
.about("Manage agent sessions")
.subcommand(
Command::new("start")
.about("Start a new session for an agent")
.arg(Arg::new("agent_id").required(true).help("Agent ID"))
.arg(
Arg::new("metadata")
.short('m')
.long("metadata")
.help("Session metadata as JSON string"),
),
)
.subcommand(
Command::new("end")
.about("End an active session")
.arg(Arg::new("session_id").required(true).help("Session ID"))
.arg(
Arg::new("summary")
.short('s')
.long("summary")
.help("Session summary text"),
),
)
.subcommand(
Command::new("get")
.about("Get session details")
.arg(Arg::new("session_id").required(true).help("Session ID")),
)
.subcommand(
Command::new("list")
.about("List sessions")
.arg(
Arg::new("agent-id")
.short('a')
.long("agent-id")
.help("Filter by agent ID"),
)
.arg(
Arg::new("active-only")
.long("active-only")
.action(ArgAction::SetTrue)
.help("Show only active sessions"),
)
.arg(
Arg::new("limit")
.short('l')
.long("limit")
.default_value("50")
.value_parser(value_parser!(u32))
.help("Maximum number of sessions to return"),
),
)
.subcommand(
Command::new("memories")
.about("Get memories for a session")
.arg(Arg::new("session_id").required(true).help("Session ID")),
)
}
fn build_agent_command() -> Command {
Command::new("agent")
.about("Manage agents")
.subcommand(Command::new("list").about("List all agents"))
.subcommand(
Command::new("memories")
.about("Get memories for an agent")
.arg(Arg::new("agent_id").required(true).help("Agent ID"))
.arg(
Arg::new("type")
.short('t')
.long("type")
.value_parser(["episodic", "semantic", "procedural", "working"])
.help("Filter by memory type"),
)
.arg(
Arg::new("limit")
.short('l')
.long("limit")
.default_value("50")
.value_parser(value_parser!(u32))
.help("Maximum number of memories to return"),
),
)
.subcommand(
Command::new("stats")
.about("Get agent statistics")
.arg(Arg::new("agent_id").required(true).help("Agent ID")),
)
.subcommand(
Command::new("sessions")
.about("Get sessions for an agent")
.arg(Arg::new("agent_id").required(true).help("Agent ID"))
.arg(
Arg::new("active-only")
.long("active-only")
.action(ArgAction::SetTrue)
.help("Show only active sessions"),
)
.arg(
Arg::new("limit")
.short('l')
.long("limit")
.default_value("50")
.value_parser(value_parser!(u32))
.help("Maximum number of sessions to return"),
),
)
}
fn build_knowledge_command() -> Command {
Command::new("knowledge")
.about("Knowledge graph operations")
.subcommand(
Command::new("graph")
.about("Build knowledge graph from a seed memory")
.arg(Arg::new("agent_id").required(true).help("Agent ID"))
.arg(
Arg::new("memory-id")
.short('m')
.long("memory-id")
.help("Seed memory ID"),
)
.arg(
Arg::new("depth")
.short('d')
.long("depth")
.value_parser(value_parser!(u32))
.help("Graph traversal depth"),
)
.arg(
Arg::new("min-similarity")
.short('s')
.long("min-similarity")
.value_parser(value_parser!(f32))
.help("Minimum similarity threshold (0.0 to 1.0)"),
),
)
.subcommand(
Command::new("full-graph")
.about("Build full knowledge graph for an agent")
.arg(Arg::new("agent_id").required(true).help("Agent ID"))
.arg(
Arg::new("max-nodes")
.long("max-nodes")
.value_parser(value_parser!(u32))
.help("Maximum number of nodes"),
)
.arg(
Arg::new("min-similarity")
.short('s')
.long("min-similarity")
.value_parser(value_parser!(f32))
.help("Minimum similarity threshold (0.0 to 1.0)"),
)
.arg(
Arg::new("cluster-threshold")
.long("cluster-threshold")
.value_parser(value_parser!(f32))
.help("Cluster similarity threshold (0.0 to 1.0)"),
)
.arg(
Arg::new("max-edges")
.long("max-edges")
.value_parser(value_parser!(u32))
.help("Maximum edges per node"),
),
)
.subcommand(
Command::new("summarize")
.about("Summarize agent memories")
.arg(Arg::new("agent_id").required(true).help("Agent ID"))
.arg(
Arg::new("memory-ids")
.long("memory-ids")
.help("Comma-separated memory IDs to summarize"),
)
.arg(
Arg::new("target-type")
.short('t')
.long("target-type")
.value_parser(["episodic", "semantic", "procedural", "working"])
.help("Target memory type for the summary"),
)
.arg(
Arg::new("dry-run")
.long("dry-run")
.action(ArgAction::SetTrue)
.help("Preview summarization without applying changes"),
),
)
.subcommand(
Command::new("deduplicate")
.about("Find and remove duplicate memories")
.arg(Arg::new("agent_id").required(true).help("Agent ID"))
.arg(
Arg::new("threshold")
.long("threshold")
.value_parser(value_parser!(f32))
.help("Similarity threshold for deduplication (0.0 to 1.0)"),
)
.arg(
Arg::new("type")
.short('t')
.long("type")
.value_parser(["episodic", "semantic", "procedural", "working"])
.help("Filter by memory type"),
)
.arg(
Arg::new("dry-run")
.long("dry-run")
.action(ArgAction::SetTrue)
.help("Preview deduplication without applying changes"),
),
)
}
fn build_admin_command() -> Command {
Command::new("admin")
.about("Cluster administration, caching, backups, and configuration")
.subcommand(Command::new("cluster-status").about("Get cluster status overview"))
.subcommand(Command::new("cluster-nodes").about("List cluster nodes"))
.subcommand(
Command::new("optimize")
.about("Optimize a namespace (compact indexes, reclaim space)")
.arg(
Arg::new("namespace")
.required(true)
.help("Namespace to optimize"),
),
)
.subcommand(
Command::new("index-stats")
.about("Get index statistics for a namespace")
.arg(Arg::new("namespace").required(true).help("Namespace name")),
)
.subcommand(
Command::new("rebuild-indexes")
.about("Rebuild indexes for a namespace")
.arg(Arg::new("namespace").required(true).help("Namespace name")),
)
.subcommand(Command::new("cache-stats").about("Get cache statistics"))
.subcommand(
Command::new("cache-clear")
.about("Clear cache (optionally for a specific namespace)")
.arg(
Arg::new("namespace")
.short('n')
.long("namespace")
.help("Namespace to clear cache for (all if omitted)"),
),
)
.subcommand(Command::new("config-get").about("Get current server configuration"))
.subcommand(
Command::new("config-set")
.about("Update a configuration value")
.arg(
Arg::new("key")
.short('k')
.long("key")
.required(true)
.help("Configuration key"),
)
.arg(
Arg::new("value")
.short('V')
.long("value")
.required(true)
.help("Configuration value (string or JSON)"),
),
)
.subcommand(Command::new("quotas-get").about("List all namespace quotas"))
.subcommand(
Command::new("quotas-set")
.about("Set namespace quotas")
.arg(
Arg::new("data")
.short('d')
.long("data")
.required(true)
.help("Quota configuration as JSON string"),
),
)
.subcommand(
Command::new("slow-queries")
.about("List slow queries")
.arg(
Arg::new("limit")
.short('l')
.long("limit")
.default_value("20")
.value_parser(value_parser!(u32))
.help("Maximum number of queries to return"),
)
.arg(
Arg::new("min-duration")
.long("min-duration")
.value_parser(value_parser!(f64))
.help("Minimum duration in milliseconds"),
),
)
.subcommand(
Command::new("backup-create")
.about("Create a new backup")
.arg(
Arg::new("no-data")
.long("no-data")
.action(ArgAction::SetTrue)
.help("Create schema-only backup without vector data"),
),
)
.subcommand(Command::new("backup-list").about("List all backups"))
.subcommand(
Command::new("backup-restore")
.about("Restore from a backup")
.arg(
Arg::new("backup_id")
.required(true)
.help("Backup ID to restore"),
),
)
.subcommand(
Command::new("backup-delete").about("Delete a backup").arg(
Arg::new("backup_id")
.required(true)
.help("Backup ID to delete"),
),
)
.subcommand(
Command::new("configure-ttl")
.about("Configure TTL (time-to-live) for a namespace")
.arg(Arg::new("namespace").required(true).help("Namespace name"))
.arg(
Arg::new("ttl-seconds")
.long("ttl-seconds")
.required(true)
.value_parser(value_parser!(u64))
.help("TTL in seconds for vectors in this namespace"),
)
.arg(
Arg::new("strategy")
.long("strategy")
.help("TTL strategy (e.g. delete, archive)"),
),
)
}
fn build_keys_command() -> Command {
Command::new("keys")
.about("Manage API keys")
.subcommand(
Command::new("create")
.about("Create a new API key")
.arg(
Arg::new("name")
.required(true)
.help("Human-readable name for the key"),
)
.arg(
Arg::new("permissions")
.short('p')
.long("permissions")
.help("Permission scope (e.g. read, write, admin)"),
)
.arg(
Arg::new("expires")
.short('e')
.long("expires")
.value_parser(value_parser!(u64))
.help("Expiration in days"),
),
)
.subcommand(Command::new("list").about("List all API keys"))
.subcommand(
Command::new("get")
.about("Get API key details")
.arg(Arg::new("key_id").required(true).help("API key ID")),
)
.subcommand(
Command::new("delete")
.about("Delete (revoke) an API key")
.arg(Arg::new("key_id").required(true).help("API key ID")),
)
.subcommand(
Command::new("deactivate")
.about("Deactivate an API key without deleting it")
.arg(Arg::new("key_id").required(true).help("API key ID")),
)
.subcommand(
Command::new("rotate")
.about("Rotate an API key (generate new secret)")
.arg(Arg::new("key_id").required(true).help("API key ID")),
)
.subcommand(
Command::new("usage")
.about("Get usage statistics for an API key")
.arg(Arg::new("key_id").required(true).help("API key ID")),
)
}
fn build_analytics_command() -> Command {
Command::new("analytics")
.about("View platform analytics and metrics")
.subcommand(
Command::new("overview")
.about("Analytics overview")
.arg(
Arg::new("period")
.short('p')
.long("period")
.default_value("24h")
.help("Time period (e.g. 1h, 24h, 7d)"),
)
.arg(
Arg::new("namespace")
.short('n')
.long("namespace")
.help("Filter by namespace"),
),
)
.subcommand(
Command::new("latency")
.about("Latency statistics")
.arg(
Arg::new("period")
.short('p')
.long("period")
.default_value("24h")
.help("Time period (e.g. 1h, 24h, 7d)"),
)
.arg(
Arg::new("namespace")
.short('n')
.long("namespace")
.help("Filter by namespace"),
),
)
.subcommand(
Command::new("throughput")
.about("Throughput statistics")
.arg(
Arg::new("period")
.short('p')
.long("period")
.default_value("24h")
.help("Time period (e.g. 1h, 24h, 7d)"),
)
.arg(
Arg::new("namespace")
.short('n')
.long("namespace")
.help("Filter by namespace"),
),
)
.subcommand(
Command::new("storage").about("Storage statistics").arg(
Arg::new("namespace")
.short('n')
.long("namespace")
.help("Filter by namespace"),
),
)
}
#[tokio::main]
async fn main() {
let matches = build_cli().get_matches();
if matches.get_flag("verbose") {
tracing_subscriber::registry()
.with(tracing_subscriber::EnvFilter::new("info"))
.with(tracing_subscriber::fmt::layer())
.init();
}
let format_str = matches.get_one::<String>("format").unwrap();
let format = OutputFormat::from(format_str.as_str());
if let Err(err) = run(matches, format).await {
let cli_err = error::classify(&err);
let exit_code = cli_err.exit_code();
match format {
OutputFormat::Json | OutputFormat::Compact => {
let json_err = error::JsonError {
error: true,
code: cli_err.error_code(),
exit_code,
message: cli_err.to_string(),
};
let s = if matches!(format, OutputFormat::Json) {
serde_json::to_string_pretty(&json_err)
} else {
serde_json::to_string(&json_err)
};
eprintln!("{}", s.unwrap_or_else(|_| cli_err.to_string()));
}
OutputFormat::Table => {
output::error(&cli_err.to_string());
}
}
std::process::exit(exit_code);
}
}
async fn run(matches: clap::ArgMatches, format: OutputFormat) -> anyhow::Result<()> {
let config = match matches.get_one::<String>("profile") {
Some(p) => Config::load_with_profile(p),
None => Config::load(),
};
let cli_url = matches.get_one::<String>("url").unwrap();
let url = if cli_url != "http://localhost:3000" {
cli_url.clone()
} else {
config.server_url.clone()
};
match matches.subcommand() {
Some(("init", _)) => {
init::execute().await?;
}
Some(("health", sub_matches)) => {
let detailed = sub_matches.get_flag("detailed");
health::execute(&url, detailed, format).await?;
}
Some(("namespace", sub_matches)) => {
namespace::execute(&url, sub_matches, format).await?;
}
Some(("vector", sub_matches)) => {
vector::execute(&url, sub_matches, format).await?;
}
Some(("index", sub_matches)) => {
index::execute(&url, sub_matches, format).await?;
}
Some(("ops", sub_matches)) => {
ops::execute(&url, sub_matches, format).await?;
}
Some(("memory", sub_matches)) => {
memory::execute(&url, sub_matches, format).await?;
}
Some(("session", sub_matches)) => {
session::execute(&url, sub_matches, format).await?;
}
Some(("agent", sub_matches)) => {
agent::execute(&url, sub_matches, format).await?;
}
Some(("knowledge", sub_matches)) => {
knowledge::execute(&url, sub_matches, format).await?;
}
Some(("analytics", sub_matches)) => {
analytics::execute(&url, sub_matches, format).await?;
}
Some(("admin", sub_matches)) => {
admin::execute(&url, sub_matches, format).await?;
}
Some(("keys", sub_matches)) => {
keys::execute(&url, sub_matches, format).await?;
}
Some(("completion", sub_matches)) => {
let shell = sub_matches.get_one::<String>("shell").unwrap();
let install = sub_matches.get_flag("install");
completion::execute(shell, install)?;
}
Some(("config", sub_matches)) => {
config_cmd::execute(sub_matches).await?;
}
_ => {
build_cli().print_help()?;
}
}
Ok(())
}