sparrow/cmd_handlers/
handle_memory_graph_cmd.rs1use super::prelude::*;
3pub fn handle_memory_graph(
4 action: sparrow::cli::GraphAction,
5 memory: &Arc<dyn Memory>,
6) -> anyhow::Result<()> {
7 use sparrow::memory::{GraphDirection, GraphEdge, GraphNode};
8 let now = chrono::Utc::now().to_rfc3339();
9 match action {
10 sparrow::cli::GraphAction::UpsertNode {
11 id,
12 label,
13 kind,
14 properties,
15 } => {
16 let properties = parse_json_properties(&properties)?;
17 memory.upsert_graph_node(GraphNode {
18 id: id.clone(),
19 label,
20 kind,
21 properties,
22 created_at: now.clone(),
23 updated_at: now,
24 })?;
25 println!("Graph node stored: {}", id);
26 }
27 sparrow::cli::GraphAction::UpsertEdge {
28 from_id,
29 relation,
30 to_id,
31 id,
32 weight,
33 properties,
34 } => {
35 let edge_id = id.unwrap_or_else(|| format!("{}:{}:{}", from_id, relation, to_id));
36 let properties = parse_json_properties(&properties)?;
37 memory.upsert_graph_edge(GraphEdge {
38 id: edge_id.clone(),
39 from_id,
40 to_id,
41 relation,
42 weight,
43 properties,
44 created_at: now.clone(),
45 updated_at: now,
46 })?;
47 println!("Graph edge stored: {}", edge_id);
48 }
49 sparrow::cli::GraphAction::Get { id } => {
50 if let Some(node) = memory.graph_node(&id) {
51 println!("{}", serde_json::to_string_pretty(&node)?);
52 } else {
53 println!("Graph node '{}' not found.", id);
54 }
55 }
56 sparrow::cli::GraphAction::Neighbors {
57 id,
58 direction,
59 limit,
60 } => {
61 let rows = memory.graph_neighbors(&id, GraphDirection::parse(&direction), limit);
62 println!("{}", serde_json::to_string_pretty(&rows)?);
63 }
64 sparrow::cli::GraphAction::Search { query, limit } => {
65 let nodes = memory.search_graph(&query, limit);
66 println!("{}", serde_json::to_string_pretty(&nodes)?);
67 }
68 sparrow::cli::GraphAction::Export => {
69 println!("{}", serde_json::to_string_pretty(&memory.graph_export())?);
70 }
71 sparrow::cli::GraphAction::DeleteNode { id } => {
72 memory.delete_graph_node(&id)?;
73 println!("Graph node deleted: {}", id);
74 }
75 sparrow::cli::GraphAction::DeleteEdge { id } => {
76 memory.delete_graph_edge(&id)?;
77 println!("Graph edge deleted: {}", id);
78 }
79 sparrow::cli::GraphAction::SyncNeo4j => {
80 let graph = memory.graph_export();
81 let runtime = tokio::runtime::Builder::new_current_thread()
82 .enable_all()
83 .build()?;
84 let statements =
85 runtime.block_on(sparrow::tools::knowledge_graph::sync_graph_to_neo4j(&graph))?;
86 println!(
87 "Synced graph to Neo4j: {} nodes, {} edges, {} statements",
88 graph.nodes.len(),
89 graph.edges.len(),
90 statements
91 );
92 }
93 }
94 Ok(())
95}
96
97pub fn parse_json_properties(raw: &str) -> anyhow::Result<serde_json::Value> {
98 let value: serde_json::Value = serde_json::from_str(raw)
99 .map_err(|e| anyhow::anyhow!("properties must be valid JSON object: {}", e))?;
100 if !value.is_object() {
101 anyhow::bail!("properties must be a JSON object");
102 }
103 Ok(value)
104}
105
106pub fn current_repo_head() -> Option<String> {
109 let output = std::process::Command::new("git")
110 .args(["rev-parse", "HEAD"])
111 .output()
112 .ok()?;
113 if !output.status.success() {
114 return None;
115 }
116 let head = String::from_utf8_lossy(&output.stdout).trim().to_string();
117 if head.is_empty() { None } else { Some(head) }
118}