pub mod agent;
pub mod audit;
pub mod backup;
pub mod briefing;
pub mod config_cmd;
pub mod doctor;
pub mod edge;
pub mod export;
pub mod import;
pub mod init;
pub mod migrate;
pub mod node;
pub mod prompt;
pub mod search;
pub mod security;
pub mod shell;
pub mod stats;
pub mod traverse;
pub mod trust;
use clap::{Args, Parser, Subcommand};
use std::path::PathBuf;
#[derive(Parser, Debug)]
#[command(name = "cortex")]
#[command(version, about = "Self-organizing graph memory for AI agents")]
pub struct Cli {
#[arg(
long,
global = true,
env = "CORTEX_CONFIG",
default_value = "cortex.toml"
)]
pub config: PathBuf,
#[arg(long, global = true, env = "CORTEX_DATA_DIR")]
pub data_dir: Option<PathBuf>,
#[arg(
long,
global = true,
env = "CORTEX_ADDR",
default_value = "http://localhost:9090"
)]
pub server: String,
#[command(subcommand)]
pub command: Commands,
}
#[derive(Subcommand, Debug)]
pub enum Commands {
Serve,
Init(InitArgs),
Shell,
#[command(subcommand)]
Node(NodeCommands),
#[command(subcommand)]
Edge(EdgeCommands),
Search(SearchArgs),
Traverse(TraverseArgs),
Path(PathArgs),
Briefing(BriefingArgs),
Import(ImportArgs),
Export(ExportArgs),
Backup(BackupArgs),
Restore(RestoreArgs),
Migrate,
Stats,
Doctor,
#[command(subcommand)]
Config(ConfigCommands),
Audit(AuditArgs),
#[command(subcommand)]
Security(SecurityCommands),
Mcp(McpArgs),
#[command(subcommand)]
Agent(AgentCommands),
#[command(subcommand)]
Prompt(PromptCommands),
Trust(TrustArgs),
}
#[derive(Args, Debug)]
pub struct TrustArgs {
pub id: Option<String>,
#[arg(long)]
pub agent: Option<String>,
#[arg(long, default_value = "table")]
pub format: String,
}
#[derive(Args, Debug)]
pub struct InitArgs {
#[arg(long)]
pub template: Option<String>,
}
#[derive(Args, Debug)]
pub struct McpArgs {
#[arg(long)]
pub data_dir: Option<PathBuf>,
#[arg(long)]
pub server: Option<String>,
}
#[derive(Subcommand, Debug)]
pub enum NodeCommands {
Create(NodeCreateArgs),
Get(NodeGetArgs),
List(NodeListArgs),
Delete(NodeDeleteArgs),
Stats(NodeStatsArgs),
}
#[derive(Subcommand, Debug)]
pub enum EdgeCommands {
Create(EdgeCreateArgs),
List(EdgeListArgs),
}
#[derive(Subcommand, Debug)]
pub enum ConfigCommands {
Validate,
Show,
}
#[derive(Subcommand, Debug)]
pub enum SecurityCommands {
GenerateKey,
}
#[derive(Subcommand, Debug)]
pub enum AgentCommands {
List(AgentListArgs),
Show(AgentShowArgs),
Bind(AgentBindArgs),
Unbind(AgentUnbindArgs),
Resolve(AgentResolveArgs),
Select(AgentSelectArgs),
History(AgentHistoryArgs),
Observe(AgentObserveArgs),
}
#[derive(Args, Debug)]
pub struct AgentListArgs {
#[arg(long, default_value = "table")]
pub format: String,
}
#[derive(Args, Debug)]
pub struct AgentShowArgs {
pub name: String,
#[arg(long, default_value = "table")]
pub format: String,
}
#[derive(Args, Debug)]
pub struct AgentBindArgs {
pub name: String,
pub slug: String,
#[arg(long, default_value = "1.0")]
pub weight: f32,
#[arg(long, default_value = "table")]
pub format: String,
}
#[derive(Args, Debug)]
pub struct AgentUnbindArgs {
pub name: String,
pub slug: String,
}
#[derive(Args, Debug)]
pub struct AgentResolveArgs {
pub name: String,
#[arg(long, default_value = "text")]
pub format: String,
}
#[derive(Args, Debug)]
pub struct AgentSelectArgs {
pub name: String,
#[arg(long, default_value = "0.5")]
pub sentiment: f32,
#[arg(long, default_value = "casual")]
pub task_type: String,
#[arg(long, default_value = "0.0")]
pub correction_rate: f32,
#[arg(long, default_value = "0.0")]
pub topic_shift: f32,
#[arg(long, default_value = "0.5")]
pub energy: f32,
#[arg(long, default_value = "0.2")]
pub epsilon: f32,
#[arg(long, default_value = "table")]
pub format: String,
}
#[derive(Args, Debug)]
pub struct AgentHistoryArgs {
pub name: String,
#[arg(long, default_value = "20")]
pub limit: usize,
#[arg(long, default_value = "table")]
pub format: String,
}
#[derive(Args, Debug)]
pub struct AgentObserveArgs {
pub name: String,
#[arg(long)]
pub variant_id: String,
#[arg(long)]
pub variant_slug: String,
#[arg(long, default_value = "0.5")]
pub sentiment_score: f32,
#[arg(long, default_value = "0")]
pub correction_count: u32,
#[arg(long, default_value = "unknown")]
pub task_outcome: String,
#[arg(long)]
pub token_cost: Option<u32>,
}
#[derive(Subcommand, Debug)]
pub enum PromptCommands {
List(PromptListArgs),
Get(PromptGetArgs),
Migrate(PromptMigrateArgs),
Performance(PromptPerformanceArgs),
Deploy(PromptDeployArgs),
RollbackStatus(PromptRollbackStatusArgs),
Unquarantine(PromptUnquarantineArgs),
}
#[derive(Args, Debug)]
pub struct PromptListArgs {
#[arg(long)]
pub branch: Option<String>,
#[arg(long, default_value = "table")]
pub format: String,
}
#[derive(Args, Debug)]
pub struct PromptGetArgs {
pub slug: String,
#[arg(long)]
pub branch: Option<String>,
#[arg(long)]
pub version: Option<u32>,
#[arg(long, default_value = "table")]
pub format: String,
}
#[derive(Args, Debug)]
pub struct PromptMigrateArgs {
pub file: std::path::PathBuf,
#[arg(long)]
pub dry_run: bool,
}
#[derive(Args, Debug)]
pub struct PromptPerformanceArgs {
pub slug: String,
#[arg(long, default_value = "50")]
pub limit: usize,
#[arg(long, default_value = "table")]
pub format: String,
}
#[derive(Args, Debug)]
pub struct PromptDeployArgs {
pub slug: String,
#[arg(long, default_value = "main")]
pub branch: String,
#[arg(long)]
pub agent_name: String,
#[arg(long, default_value = "20")]
pub baseline_sample_size: usize,
#[arg(long, default_value = "table")]
pub format: String,
}
#[derive(Args, Debug)]
pub struct PromptRollbackStatusArgs {
pub slug: String,
#[arg(long, default_value = "main")]
pub branch: String,
#[arg(long, default_value = "table")]
pub format: String,
}
#[derive(Args, Debug)]
pub struct PromptUnquarantineArgs {
pub slug: String,
#[arg(long, default_value = "main")]
pub branch: String,
}
#[derive(Args, Debug)]
pub struct AuditArgs {
#[arg(long)]
pub since: Option<String>,
#[arg(long)]
pub node: Option<String>,
#[arg(long)]
pub actor: Option<String>,
#[arg(long, default_value = "table")]
pub format: String,
#[arg(long, default_value = "100")]
pub limit: usize,
}
#[derive(Args, Debug)]
pub struct NodeCreateArgs {
#[arg(long)]
pub kind: String,
#[arg(long)]
pub title: String,
#[arg(long)]
pub body: Option<String>,
#[arg(long, default_value = "0.5")]
pub importance: f32,
#[arg(long, value_delimiter = ',')]
pub tags: Vec<String>,
#[arg(long)]
pub stdin: bool,
#[arg(long)]
pub metadata: Option<String>,
#[arg(long)]
pub valid_from: Option<String>,
#[arg(long)]
pub valid_until: Option<String>,
#[arg(long)]
pub expires_at: Option<String>,
#[arg(long)]
pub check_conventions: bool,
#[arg(long, default_value = "table")]
pub format: String,
}
#[derive(Args, Debug)]
pub struct NodeGetArgs {
pub id: String,
#[arg(long, default_value = "table")]
pub format: String,
}
#[derive(Args, Debug)]
pub struct NodeListArgs {
#[arg(long)]
pub kind: Option<String>,
#[arg(long, default_value = "20")]
pub limit: u32,
#[arg(long)]
pub source: Option<String>,
#[arg(long, default_value = "table")]
pub format: String,
}
#[derive(Args, Debug)]
pub struct NodeDeleteArgs {
pub id: String,
#[arg(long, short = 'y')]
pub yes: bool,
}
#[derive(Args, Debug)]
pub struct NodeStatsArgs {
pub id: String,
#[arg(long, default_value = "table")]
pub format: String,
}
#[derive(Args, Debug)]
pub struct EdgeCreateArgs {
#[arg(long)]
pub from: String,
#[arg(long)]
pub to: String,
#[arg(long)]
pub relation: String,
#[arg(long, default_value = "1.0")]
pub weight: f32,
#[arg(long, default_value = "table")]
pub format: String,
}
#[derive(Args, Debug)]
pub struct EdgeListArgs {
#[arg(long)]
pub node: String,
#[arg(long, default_value = "both")]
pub direction: String,
#[arg(long, default_value = "table")]
pub format: String,
}
#[derive(Args, Debug)]
pub struct SearchArgs {
pub query: String,
#[arg(long, default_value = "10")]
pub limit: u32,
#[arg(long)]
pub hybrid: bool,
#[arg(long, default_value = "table")]
pub format: String,
}
#[derive(Args, Debug)]
pub struct TraverseArgs {
pub id: String,
#[arg(long, default_value = "2")]
pub depth: u32,
#[arg(long, default_value = "both")]
pub direction: String,
#[arg(long)]
pub relation: Option<String>,
#[arg(long, default_value = "table")]
pub format: String,
}
#[derive(Args, Debug)]
pub struct PathArgs {
pub from: String,
pub to: String,
#[arg(long, default_value = "5")]
pub max_hops: u32,
#[arg(long, default_value = "table")]
pub format: String,
}
#[derive(Args, Debug)]
pub struct BriefingArgs {
#[arg(default_value = "")]
pub agent_id: String,
#[arg(long)]
pub compact: bool,
#[arg(long, default_value = "text")]
pub format: String,
#[arg(long)]
pub no_cache: bool,
#[arg(long, default_value = "agent")]
pub scope: String,
#[arg(long)]
pub agents: Option<String>,
}
#[derive(Args, Debug)]
pub struct ImportArgs {
pub file: PathBuf,
#[arg(long)]
pub format: Option<String>,
#[arg(long, default_value = "import")]
pub source: String,
#[arg(long)]
pub dry_run: bool,
}
#[derive(Args, Debug)]
pub struct ExportArgs {
#[arg(long)]
pub output: Option<PathBuf>,
#[arg(long, default_value = "json")]
pub format: String,
#[arg(long)]
pub kind: Option<String>,
}
#[derive(Args, Debug)]
pub struct BackupArgs {
pub path: PathBuf,
#[arg(long)]
pub encrypt: bool,
}
#[derive(Args, Debug)]
pub struct RestoreArgs {
pub path: PathBuf,
#[arg(long, short = 'y')]
pub yes: bool,
}
use cortex_proto::cortex_service_client::CortexServiceClient;
use tonic::transport::Channel;
pub async fn grpc_connect(server: &str) -> anyhow::Result<CortexServiceClient<Channel>> {
CortexServiceClient::connect(server.to_string())
.await
.map_err(|e| {
anyhow::anyhow!(
"Failed to connect to Cortex server at {}: {}\nIs `cortex serve` running?",
server,
e
)
})
}
pub fn print_node_table(nodes: &[cortex_proto::NodeResponse]) {
if nodes.is_empty() {
println!("(no results)");
return;
}
println!("{:<36} {:<12} {:<6} TITLE", "ID", "KIND", "IMP");
println!("{}", "─".repeat(80));
for n in nodes {
let title = truncate(&n.title, 40);
println!(
"{:<36} {:<12} {:<6.2} {}",
n.id, n.kind, n.importance, title
);
}
}
pub fn print_edge_table(edges: &[cortex_proto::EdgeResponse]) {
if edges.is_empty() {
println!("(no edges)");
return;
}
println!(
"{:<36} {:<36} {:<20} {:<5}",
"FROM", "TO", "RELATION", "WEIGHT"
);
println!("{}", "─".repeat(100));
for e in edges {
println!(
"{:<36} {:<36} {:<20} {:<5.2}",
e.from_id, e.to_id, e.relation, e.weight
);
}
}
pub fn truncate(s: &str, max: usize) -> String {
if s.chars().count() <= max {
s.to_string()
} else {
format!("{}…", s.chars().take(max - 1).collect::<String>())
}
}