cli: extract node commands from main.rs into cli/node.rs
Move 15 node subcommand handlers (310 lines) out of main.rs:
render, write, used, wrong, not-relevant, not-useful, gap,
node-delete, node-rename, history, list-keys, list-edges,
dump-json, lookup-bump, lookups.
main.rs: 2518 → 2193 lines.
Co-Authored-By: Kent Overstreet <kent.overstreet@linux.dev>
2026-03-14 18:02:12 -04:00
|
|
|
// cli/node.rs — node subcommand handlers
|
|
|
|
|
//
|
remove legacy feedback commands (used, wrong, gap, etc.)
These were early experiments with manual feedback signals that
never worked well. The scoring system will handle this properly.
Removed:
- CLI: used, wrong, not-relevant, not-useful, gap
- MCP: memory_used
- Store: mark_used, mark_wrong, record_gap, modify_node
Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-12 22:12:02 -04:00
|
|
|
// render, write, node-delete, node-rename, history, list-keys,
|
|
|
|
|
// list-edges, dump-json, lookup-bump, lookups.
|
cli: extract node commands from main.rs into cli/node.rs
Move 15 node subcommand handlers (310 lines) out of main.rs:
render, write, used, wrong, not-relevant, not-useful, gap,
node-delete, node-rename, history, list-keys, list-edges,
dump-json, lookup-bump, lookups.
main.rs: 2518 → 2193 lines.
Co-Authored-By: Kent Overstreet <kent.overstreet@linux.dev>
2026-03-14 18:02:12 -04:00
|
|
|
|
|
|
|
|
use crate::store;
|
|
|
|
|
|
2026-03-20 12:16:55 -04:00
|
|
|
pub fn cmd_weight_set(key: &str, weight: f32) -> Result<(), String> {
|
|
|
|
|
super::check_dry_run();
|
2026-04-12 21:54:34 -04:00
|
|
|
let result = crate::mcp_server::memory_rpc(
|
|
|
|
|
"memory_weight_set",
|
|
|
|
|
serde_json::json!({"key": key, "weight": weight}),
|
|
|
|
|
).map_err(|e| e.to_string())?;
|
|
|
|
|
println!("{}", result);
|
2026-03-20 12:16:55 -04:00
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
cli: extract node commands from main.rs into cli/node.rs
Move 15 node subcommand handlers (310 lines) out of main.rs:
render, write, used, wrong, not-relevant, not-useful, gap,
node-delete, node-rename, history, list-keys, list-edges,
dump-json, lookup-bump, lookups.
main.rs: 2518 → 2193 lines.
Co-Authored-By: Kent Overstreet <kent.overstreet@linux.dev>
2026-03-14 18:02:12 -04:00
|
|
|
pub fn cmd_list_keys(pattern: Option<&str>) -> Result<(), String> {
|
|
|
|
|
let store = store::Store::load()?;
|
|
|
|
|
let g = store.build_graph();
|
|
|
|
|
|
|
|
|
|
if let Some(pat) = pattern {
|
|
|
|
|
let pat_lower = pat.to_lowercase();
|
|
|
|
|
let (prefix, suffix, middle) = if pat_lower.starts_with('*') && pat_lower.ends_with('*') {
|
|
|
|
|
(None, None, Some(pat_lower.trim_matches('*').to_string()))
|
|
|
|
|
} else if pat_lower.starts_with('*') {
|
|
|
|
|
(None, Some(pat_lower.trim_start_matches('*').to_string()), None)
|
|
|
|
|
} else if pat_lower.ends_with('*') {
|
|
|
|
|
(Some(pat_lower.trim_end_matches('*').to_string()), None, None)
|
|
|
|
|
} else {
|
|
|
|
|
(None, None, Some(pat_lower.clone()))
|
|
|
|
|
};
|
|
|
|
|
let mut keys: Vec<_> = store.nodes.keys()
|
|
|
|
|
.filter(|k| {
|
|
|
|
|
let kl = k.to_lowercase();
|
|
|
|
|
if let Some(ref m) = middle { kl.contains(m.as_str()) }
|
|
|
|
|
else if let Some(ref p) = prefix { kl.starts_with(p.as_str()) }
|
|
|
|
|
else if let Some(ref s) = suffix { kl.ends_with(s.as_str()) }
|
|
|
|
|
else { true }
|
|
|
|
|
})
|
|
|
|
|
.cloned()
|
|
|
|
|
.collect();
|
|
|
|
|
keys.sort();
|
|
|
|
|
for k in keys { println!("{}", k); }
|
|
|
|
|
Ok(())
|
|
|
|
|
} else {
|
|
|
|
|
crate::query_parser::run_query(&store, &g, "* | sort key asc")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn cmd_list_edges() -> Result<(), String> {
|
|
|
|
|
let store = store::Store::load()?;
|
|
|
|
|
for rel in &store.relations {
|
|
|
|
|
println!("{}\t{}\t{:.2}\t{:?}",
|
|
|
|
|
rel.source_key, rel.target_key, rel.strength, rel.rel_type);
|
|
|
|
|
}
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn cmd_dump_json() -> Result<(), String> {
|
|
|
|
|
let store = store::Store::load()?;
|
|
|
|
|
let json = serde_json::to_string_pretty(&store)
|
|
|
|
|
.map_err(|e| format!("serialize: {}", e))?;
|
|
|
|
|
println!("{}", json);
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn cmd_node_delete(key: &[String]) -> Result<(), String> {
|
|
|
|
|
if key.is_empty() {
|
|
|
|
|
return Err("node-delete requires a key".into());
|
|
|
|
|
}
|
poc-memory: POC_MEMORY_DRY_RUN=1 for agent testing
All mutating commands (write, delete, rename, link-add, journal write,
used, wrong, not-useful, gap) check POC_MEMORY_DRY_RUN after argument
validation but before mutation. If set, process exits silently — agent
tool calls are visible in the LLM output so we can see what it tried
to do without applying changes.
Read commands (render, search, graph link, journal tail) work normally
in dry-run mode so agents can still explore the graph.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-16 18:09:56 -04:00
|
|
|
super::check_dry_run();
|
cli: extract node commands from main.rs into cli/node.rs
Move 15 node subcommand handlers (310 lines) out of main.rs:
render, write, used, wrong, not-relevant, not-useful, gap,
node-delete, node-rename, history, list-keys, list-edges,
dump-json, lookup-bump, lookups.
main.rs: 2518 → 2193 lines.
Co-Authored-By: Kent Overstreet <kent.overstreet@linux.dev>
2026-03-14 18:02:12 -04:00
|
|
|
let key = key.join(" ");
|
2026-04-12 22:15:53 -04:00
|
|
|
let result = crate::mcp_server::memory_rpc(
|
|
|
|
|
"memory_delete",
|
|
|
|
|
serde_json::json!({"key": key}),
|
|
|
|
|
).map_err(|e| e.to_string())?;
|
|
|
|
|
println!("{}", result);
|
cli: extract node commands from main.rs into cli/node.rs
Move 15 node subcommand handlers (310 lines) out of main.rs:
render, write, used, wrong, not-relevant, not-useful, gap,
node-delete, node-rename, history, list-keys, list-edges,
dump-json, lookup-bump, lookups.
main.rs: 2518 → 2193 lines.
Co-Authored-By: Kent Overstreet <kent.overstreet@linux.dev>
2026-03-14 18:02:12 -04:00
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn cmd_node_rename(old_key: &str, new_key: &str) -> Result<(), String> {
|
poc-memory: POC_MEMORY_DRY_RUN=1 for agent testing
All mutating commands (write, delete, rename, link-add, journal write,
used, wrong, not-useful, gap) check POC_MEMORY_DRY_RUN after argument
validation but before mutation. If set, process exits silently — agent
tool calls are visible in the LLM output so we can see what it tried
to do without applying changes.
Read commands (render, search, graph link, journal tail) work normally
in dry-run mode so agents can still explore the graph.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-16 18:09:56 -04:00
|
|
|
super::check_dry_run();
|
2026-04-12 21:54:34 -04:00
|
|
|
let result = crate::mcp_server::memory_rpc(
|
|
|
|
|
"memory_rename",
|
|
|
|
|
serde_json::json!({"old_key": old_key, "new_key": new_key}),
|
|
|
|
|
).map_err(|e| e.to_string())?;
|
|
|
|
|
println!("{}", result);
|
cli: extract node commands from main.rs into cli/node.rs
Move 15 node subcommand handlers (310 lines) out of main.rs:
render, write, used, wrong, not-relevant, not-useful, gap,
node-delete, node-rename, history, list-keys, list-edges,
dump-json, lookup-bump, lookups.
main.rs: 2518 → 2193 lines.
Co-Authored-By: Kent Overstreet <kent.overstreet@linux.dev>
2026-03-14 18:02:12 -04:00
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-20 13:47:14 -04:00
|
|
|
/// Render a node to a string: content + deduped footer links.
|
|
|
|
|
/// Used by both the CLI command and agent placeholders.
|
|
|
|
|
pub fn render_node(store: &store::Store, key: &str) -> Option<String> {
|
2026-03-25 01:55:21 -04:00
|
|
|
crate::hippocampus::memory::MemoryNode::from_store(store, key)
|
|
|
|
|
.map(|node| node.render())
|
2026-03-20 13:47:14 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn cmd_render(key: &[String]) -> Result<(), String> {
|
|
|
|
|
if key.is_empty() {
|
|
|
|
|
return Err("render requires a key".into());
|
|
|
|
|
}
|
|
|
|
|
let key = key.join(" ");
|
|
|
|
|
let bare = store::strip_md_suffix(&key);
|
|
|
|
|
|
remove legacy feedback commands (used, wrong, gap, etc.)
These were early experiments with manual feedback signals that
never worked well. The scoring system will handle this properly.
Removed:
- CLI: used, wrong, not-relevant, not-useful, gap
- MCP: memory_used
- Store: mark_used, mark_wrong, record_gap, modify_node
Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-12 22:12:02 -04:00
|
|
|
let rendered = crate::mcp_server::memory_rpc(
|
|
|
|
|
"memory_render",
|
|
|
|
|
serde_json::json!({"key": bare}),
|
|
|
|
|
).map_err(|e| e.to_string())?;
|
2026-03-20 13:47:14 -04:00
|
|
|
print!("{}", rendered);
|
2026-03-22 02:43:46 -04:00
|
|
|
|
2026-03-24 12:32:46 -04:00
|
|
|
// Mark as seen if we're inside a Claude session (not an agent subprocess —
|
|
|
|
|
// agents read the seen set but shouldn't write to it as a side effect of
|
|
|
|
|
// tool calls; only surface_agent_cycle should mark keys seen)
|
|
|
|
|
if std::env::var("POC_AGENT").is_err()
|
|
|
|
|
&& let Ok(session_id) = std::env::var("POC_SESSION_ID")
|
|
|
|
|
&& !session_id.is_empty()
|
|
|
|
|
{
|
move data home from ~/.claude/memory to ~/.consciousness
The consciousness project should stand independently of Claude Code.
All data, logs, sessions, and agent state now live under
~/.consciousness/ instead of being scattered across ~/.claude/memory/,
/tmp/claude-memory-search/, ~/.config/poc-memory/, and ~/.cache/.
Layout:
~/.consciousness/
*.capnp, *.bin, *.rkyv — store files
sessions/ — per-session state (seen sets, cookies)
logs/ — all logs (hook, agent, debug, dream)
agents/ — agent runtime state (pid files, output)
notifications/ — notification state
cache/ — transient data
Things that stay in ~/.claude/:
- projects/ (Claude Code transcripts)
- hooks/ (Claude Code hook system)
- telegram/ (shared integration)
- irc/ (shared integration)
- settings.json (Claude Code settings)
Debug log moves from /tmp/ to ~/.consciousness/logs/debug.log.
Session state moves from /tmp/claude-memory-search/ to sessions/.
Notifications move from ~/.claude/notifications/ to notifications/.
2026-03-27 21:05:15 -04:00
|
|
|
let state_dir = crate::store::memory_dir().join("sessions");
|
2026-03-24 12:32:46 -04:00
|
|
|
let seen_path = state_dir.join(format!("seen-{}", session_id));
|
|
|
|
|
if let Ok(mut f) = std::fs::OpenOptions::new()
|
|
|
|
|
.create(true).append(true).open(seen_path)
|
|
|
|
|
{
|
|
|
|
|
use std::io::Write;
|
|
|
|
|
let ts = chrono::Local::now().format("%Y-%m-%dT%H:%M:%S");
|
|
|
|
|
let _ = writeln!(f, "{}\t{}", ts, bare);
|
2026-03-22 02:43:46 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
cli: extract node commands from main.rs into cli/node.rs
Move 15 node subcommand handlers (310 lines) out of main.rs:
render, write, used, wrong, not-relevant, not-useful, gap,
node-delete, node-rename, history, list-keys, list-edges,
dump-json, lookup-bump, lookups.
main.rs: 2518 → 2193 lines.
Co-Authored-By: Kent Overstreet <kent.overstreet@linux.dev>
2026-03-14 18:02:12 -04:00
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn cmd_history(key: &[String], full: bool) -> Result<(), String> {
|
|
|
|
|
if key.is_empty() {
|
|
|
|
|
return Err("history requires a key".into());
|
|
|
|
|
}
|
|
|
|
|
let raw_key = key.join(" ");
|
|
|
|
|
|
|
|
|
|
let store = store::Store::load()?;
|
|
|
|
|
let key = store.resolve_key(&raw_key).unwrap_or(raw_key);
|
|
|
|
|
drop(store);
|
|
|
|
|
|
|
|
|
|
let path = store::nodes_path();
|
|
|
|
|
if !path.exists() {
|
|
|
|
|
return Err("No node log found".into());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
use std::io::BufReader;
|
|
|
|
|
let file = std::fs::File::open(&path)
|
|
|
|
|
.map_err(|e| format!("open {}: {}", path.display(), e))?;
|
|
|
|
|
let mut reader = BufReader::new(file);
|
|
|
|
|
|
|
|
|
|
let mut versions: Vec<store::Node> = Vec::new();
|
|
|
|
|
while let Ok(msg) = capnp::serialize::read_message(&mut reader, capnp::message::ReaderOptions::new()) {
|
|
|
|
|
let log = msg.get_root::<crate::memory_capnp::node_log::Reader>()
|
|
|
|
|
.map_err(|e| format!("read log: {}", e))?;
|
|
|
|
|
for node_reader in log.get_nodes()
|
|
|
|
|
.map_err(|e| format!("get nodes: {}", e))? {
|
|
|
|
|
let node = store::Node::from_capnp_migrate(node_reader)?;
|
|
|
|
|
if node.key == key {
|
|
|
|
|
versions.push(node);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if versions.is_empty() {
|
|
|
|
|
return Err(format!("No history found for '{}'", key));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
eprintln!("{} versions of '{}':\n", versions.len(), key);
|
|
|
|
|
for node in &versions {
|
|
|
|
|
let ts = if node.timestamp > 0 && node.timestamp < 4_000_000_000 {
|
|
|
|
|
store::format_datetime(node.timestamp)
|
|
|
|
|
} else {
|
|
|
|
|
format!("(raw:{})", node.timestamp)
|
|
|
|
|
};
|
2026-03-17 18:00:58 -04:00
|
|
|
let deleted_marker = if node.deleted { " DELETED" } else { "" };
|
cli: extract node commands from main.rs into cli/node.rs
Move 15 node subcommand handlers (310 lines) out of main.rs:
render, write, used, wrong, not-relevant, not-useful, gap,
node-delete, node-rename, history, list-keys, list-edges,
dump-json, lookup-bump, lookups.
main.rs: 2518 → 2193 lines.
Co-Authored-By: Kent Overstreet <kent.overstreet@linux.dev>
2026-03-14 18:02:12 -04:00
|
|
|
let content_len = node.content.len();
|
|
|
|
|
if full {
|
2026-03-17 18:00:58 -04:00
|
|
|
eprintln!("=== v{} {} {}{} w={:.3} {}b ===",
|
|
|
|
|
node.version, ts, node.provenance, deleted_marker, node.weight, content_len);
|
cli: extract node commands from main.rs into cli/node.rs
Move 15 node subcommand handlers (310 lines) out of main.rs:
render, write, used, wrong, not-relevant, not-useful, gap,
node-delete, node-rename, history, list-keys, list-edges,
dump-json, lookup-bump, lookups.
main.rs: 2518 → 2193 lines.
Co-Authored-By: Kent Overstreet <kent.overstreet@linux.dev>
2026-03-14 18:02:12 -04:00
|
|
|
eprintln!("{}", node.content);
|
|
|
|
|
} else {
|
|
|
|
|
let preview = crate::util::first_n_chars(&node.content, 120);
|
|
|
|
|
let preview = preview.replace('\n', "\\n");
|
2026-03-17 18:00:58 -04:00
|
|
|
eprintln!(" v{:<3} {} {:24} w={:.3} {}b{}",
|
|
|
|
|
node.version, ts, node.provenance, node.weight, content_len, deleted_marker);
|
cli: extract node commands from main.rs into cli/node.rs
Move 15 node subcommand handlers (310 lines) out of main.rs:
render, write, used, wrong, not-relevant, not-useful, gap,
node-delete, node-rename, history, list-keys, list-edges,
dump-json, lookup-bump, lookups.
main.rs: 2518 → 2193 lines.
Co-Authored-By: Kent Overstreet <kent.overstreet@linux.dev>
2026-03-14 18:02:12 -04:00
|
|
|
eprintln!(" {}", preview);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-21 19:42:38 -04:00
|
|
|
if !full
|
|
|
|
|
&& let Some(latest) = versions.last() {
|
cli: extract node commands from main.rs into cli/node.rs
Move 15 node subcommand handlers (310 lines) out of main.rs:
render, write, used, wrong, not-relevant, not-useful, gap,
node-delete, node-rename, history, list-keys, list-edges,
dump-json, lookup-bump, lookups.
main.rs: 2518 → 2193 lines.
Co-Authored-By: Kent Overstreet <kent.overstreet@linux.dev>
2026-03-14 18:02:12 -04:00
|
|
|
eprintln!("\n--- Latest content (v{}, {}) ---",
|
|
|
|
|
latest.version, latest.provenance);
|
|
|
|
|
print!("{}", latest.content);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn cmd_write(key: &[String]) -> Result<(), String> {
|
|
|
|
|
if key.is_empty() {
|
|
|
|
|
return Err("write requires a key (reads content from stdin)".into());
|
|
|
|
|
}
|
2026-04-12 22:15:53 -04:00
|
|
|
let key = key.join(" ");
|
cli: extract node commands from main.rs into cli/node.rs
Move 15 node subcommand handlers (310 lines) out of main.rs:
render, write, used, wrong, not-relevant, not-useful, gap,
node-delete, node-rename, history, list-keys, list-edges,
dump-json, lookup-bump, lookups.
main.rs: 2518 → 2193 lines.
Co-Authored-By: Kent Overstreet <kent.overstreet@linux.dev>
2026-03-14 18:02:12 -04:00
|
|
|
let mut content = String::new();
|
|
|
|
|
std::io::Read::read_to_string(&mut std::io::stdin(), &mut content)
|
|
|
|
|
.map_err(|e| format!("read stdin: {}", e))?;
|
|
|
|
|
|
|
|
|
|
if content.trim().is_empty() {
|
|
|
|
|
return Err("No content on stdin".into());
|
|
|
|
|
}
|
poc-memory: POC_MEMORY_DRY_RUN=1 for agent testing
All mutating commands (write, delete, rename, link-add, journal write,
used, wrong, not-useful, gap) check POC_MEMORY_DRY_RUN after argument
validation but before mutation. If set, process exits silently — agent
tool calls are visible in the LLM output so we can see what it tried
to do without applying changes.
Read commands (render, search, graph link, journal tail) work normally
in dry-run mode so agents can still explore the graph.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-16 18:09:56 -04:00
|
|
|
super::check_dry_run();
|
cli: extract node commands from main.rs into cli/node.rs
Move 15 node subcommand handlers (310 lines) out of main.rs:
render, write, used, wrong, not-relevant, not-useful, gap,
node-delete, node-rename, history, list-keys, list-edges,
dump-json, lookup-bump, lookups.
main.rs: 2518 → 2193 lines.
Co-Authored-By: Kent Overstreet <kent.overstreet@linux.dev>
2026-03-14 18:02:12 -04:00
|
|
|
|
2026-04-12 22:15:53 -04:00
|
|
|
let result = crate::mcp_server::memory_rpc(
|
|
|
|
|
"memory_write",
|
|
|
|
|
serde_json::json!({"key": key, "content": content}),
|
|
|
|
|
).map_err(|e| e.to_string())?;
|
|
|
|
|
println!("{}", result);
|
cli: extract node commands from main.rs into cli/node.rs
Move 15 node subcommand handlers (310 lines) out of main.rs:
render, write, used, wrong, not-relevant, not-useful, gap,
node-delete, node-rename, history, list-keys, list-edges,
dump-json, lookup-bump, lookups.
main.rs: 2518 → 2193 lines.
Co-Authored-By: Kent Overstreet <kent.overstreet@linux.dev>
2026-03-14 18:02:12 -04:00
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-20 23:51:06 -04:00
|
|
|
pub fn cmd_edit(key: &[String]) -> Result<(), String> {
|
|
|
|
|
if key.is_empty() {
|
|
|
|
|
return Err("edit requires a key".into());
|
|
|
|
|
}
|
|
|
|
|
let raw_key = key.join(" ");
|
|
|
|
|
let store = store::Store::load()?;
|
|
|
|
|
let key = store.resolve_key(&raw_key).unwrap_or(raw_key.clone());
|
|
|
|
|
|
|
|
|
|
let content = store.nodes.get(&key)
|
|
|
|
|
.map(|n| n.content.clone())
|
|
|
|
|
.unwrap_or_default();
|
|
|
|
|
|
|
|
|
|
let tmp = std::env::temp_dir().join(format!("poc-memory-edit-{}.md", key.replace('/', "_")));
|
|
|
|
|
std::fs::write(&tmp, &content)
|
|
|
|
|
.map_err(|e| format!("write temp file: {}", e))?;
|
|
|
|
|
|
|
|
|
|
let editor = std::env::var("EDITOR").unwrap_or_else(|_| "vi".into());
|
|
|
|
|
let status = std::process::Command::new(&editor)
|
|
|
|
|
.arg(&tmp)
|
|
|
|
|
.status()
|
|
|
|
|
.map_err(|e| format!("spawn {}: {}", editor, e))?;
|
|
|
|
|
|
|
|
|
|
if !status.success() {
|
|
|
|
|
let _ = std::fs::remove_file(&tmp);
|
|
|
|
|
return Err(format!("{} exited with {}", editor, status));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let new_content = std::fs::read_to_string(&tmp)
|
|
|
|
|
.map_err(|e| format!("read temp file: {}", e))?;
|
|
|
|
|
let _ = std::fs::remove_file(&tmp);
|
|
|
|
|
|
|
|
|
|
if new_content == content {
|
|
|
|
|
println!("No change: '{}'", key);
|
|
|
|
|
return Ok(());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if new_content.trim().is_empty() {
|
|
|
|
|
return Err("Content is empty, aborting".into());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
drop(store);
|
|
|
|
|
let mut store = store::Store::load()?;
|
|
|
|
|
let result = store.upsert(&key, &new_content)?;
|
|
|
|
|
match result {
|
|
|
|
|
"unchanged" => println!("No change: '{}'", key),
|
|
|
|
|
"updated" => println!("Updated '{}' (v{})", key, store.nodes[&key].version),
|
|
|
|
|
_ => println!("Created '{}'", key),
|
|
|
|
|
}
|
|
|
|
|
if result != "unchanged" {
|
|
|
|
|
store.save()?;
|
|
|
|
|
}
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
cli: extract node commands from main.rs into cli/node.rs
Move 15 node subcommand handlers (310 lines) out of main.rs:
render, write, used, wrong, not-relevant, not-useful, gap,
node-delete, node-rename, history, list-keys, list-edges,
dump-json, lookup-bump, lookups.
main.rs: 2518 → 2193 lines.
Co-Authored-By: Kent Overstreet <kent.overstreet@linux.dev>
2026-03-14 18:02:12 -04:00
|
|
|
pub fn cmd_lookup_bump(keys: &[String]) -> Result<(), String> {
|
|
|
|
|
if keys.is_empty() {
|
|
|
|
|
return Err("lookup-bump requires at least one key".into());
|
|
|
|
|
}
|
|
|
|
|
let keys: Vec<&str> = keys.iter().map(|s| s.as_str()).collect();
|
|
|
|
|
crate::lookups::bump_many(&keys)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn cmd_lookups(date: Option<&str>) -> Result<(), String> {
|
|
|
|
|
let date = date.map(|d| d.to_string())
|
|
|
|
|
.unwrap_or_else(|| chrono::Local::now().format("%Y-%m-%d").to_string());
|
|
|
|
|
|
|
|
|
|
let store = store::Store::load()?;
|
|
|
|
|
let keys: Vec<String> = store.nodes.values().map(|n| n.key.clone()).collect();
|
|
|
|
|
let resolved = crate::lookups::dump_resolved(&date, &keys)?;
|
|
|
|
|
|
|
|
|
|
if resolved.is_empty() {
|
|
|
|
|
println!("No lookups for {}", date);
|
|
|
|
|
return Ok(());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
println!("Lookups for {}:", date);
|
|
|
|
|
for (key, count) in &resolved {
|
|
|
|
|
println!(" {:4} {}", count, key);
|
|
|
|
|
}
|
|
|
|
|
println!("\n{} distinct keys, {} total lookups",
|
|
|
|
|
resolved.len(),
|
|
|
|
|
resolved.iter().map(|(_, c)| *c as u64).sum::<u64>());
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|