config file, install command, scrub personal references
Add ~/.config/poc-memory/config.toml for user_name, assistant_name, data_dir, projects_dir, and core_nodes. All agent prompts and transcript parsing now use configured names instead of hardcoded personal references. `poc-memory daemon install` writes the systemd user service and installs the memory-search hook into Claude's settings.json. Scrubbed hardcoded names from code and docs. Authors: ProofOfConcept <poc@bcachefs.org> and Kent Overstreet
This commit is contained in:
parent
ed641ec95f
commit
a8aaadb0ad
11 changed files with 256 additions and 41 deletions
123
src/daemon.rs
123
src/daemon.rs
|
|
@ -24,23 +24,19 @@ use std::time::{Duration, SystemTime};
|
|||
const SESSION_STALE_SECS: u64 = 600; // 10 minutes
|
||||
const SCHEDULER_INTERVAL: Duration = Duration::from_secs(60);
|
||||
const HEALTH_INTERVAL: Duration = Duration::from_secs(3600);
|
||||
const STATUS_FILE: &str = ".claude/memory/daemon-status.json";
|
||||
const LOG_FILE: &str = ".claude/memory/daemon.log";
|
||||
|
||||
fn home_dir() -> PathBuf {
|
||||
PathBuf::from(std::env::var("HOME").expect("HOME not set"))
|
||||
}
|
||||
fn status_file() -> &'static str { "daemon-status.json" }
|
||||
fn log_file() -> &'static str { "daemon.log" }
|
||||
|
||||
fn status_path() -> PathBuf {
|
||||
home_dir().join(STATUS_FILE)
|
||||
crate::config::get().data_dir.join(status_file())
|
||||
}
|
||||
|
||||
fn log_path() -> PathBuf {
|
||||
home_dir().join(LOG_FILE)
|
||||
crate::config::get().data_dir.join(log_file())
|
||||
}
|
||||
|
||||
fn projects_dir() -> PathBuf {
|
||||
home_dir().join(".claude/projects")
|
||||
crate::config::get().projects_dir.clone()
|
||||
}
|
||||
|
||||
// --- Logging ---
|
||||
|
|
@ -600,6 +596,115 @@ pub fn show_status() -> Result<(), String> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn install_service() -> Result<(), String> {
|
||||
let exe = std::env::current_exe()
|
||||
.map_err(|e| format!("current_exe: {}", e))?;
|
||||
let home = std::env::var("HOME").map_err(|e| format!("HOME: {}", e))?;
|
||||
|
||||
let unit_dir = PathBuf::from(&home).join(".config/systemd/user");
|
||||
fs::create_dir_all(&unit_dir)
|
||||
.map_err(|e| format!("create {}: {}", unit_dir.display(), e))?;
|
||||
|
||||
let unit = format!(
|
||||
r#"[Unit]
|
||||
Description=poc-memory daemon — background memory maintenance
|
||||
After=default.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStart={exe} daemon
|
||||
Restart=on-failure
|
||||
RestartSec=30
|
||||
Environment=HOME={home}
|
||||
Environment=PATH={home}/.cargo/bin:{home}/.local/bin:{home}/bin:/usr/local/bin:/usr/bin:/bin
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
"#, exe = exe.display(), home = home);
|
||||
|
||||
let unit_path = unit_dir.join("poc-memory.service");
|
||||
fs::write(&unit_path, &unit)
|
||||
.map_err(|e| format!("write {}: {}", unit_path.display(), e))?;
|
||||
eprintln!("Wrote {}", unit_path.display());
|
||||
|
||||
let status = std::process::Command::new("systemctl")
|
||||
.args(["--user", "daemon-reload"])
|
||||
.status()
|
||||
.map_err(|e| format!("systemctl daemon-reload: {}", e))?;
|
||||
if !status.success() {
|
||||
return Err("systemctl daemon-reload failed".into());
|
||||
}
|
||||
|
||||
let status = std::process::Command::new("systemctl")
|
||||
.args(["--user", "enable", "--now", "poc-memory"])
|
||||
.status()
|
||||
.map_err(|e| format!("systemctl enable: {}", e))?;
|
||||
if !status.success() {
|
||||
return Err("systemctl enable --now failed".into());
|
||||
}
|
||||
|
||||
eprintln!("Service enabled and started");
|
||||
|
||||
// Install memory-search hook into Claude settings
|
||||
install_hook(&home, &exe)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn install_hook(home: &str, exe: &Path) -> Result<(), String> {
|
||||
let settings_path = PathBuf::from(home).join(".claude/settings.json");
|
||||
let hook_binary = exe.with_file_name("memory-search");
|
||||
|
||||
if !hook_binary.exists() {
|
||||
eprintln!("Warning: {} not found — hook not installed", hook_binary.display());
|
||||
eprintln!(" Build with: cargo install --path .");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut settings: serde_json::Value = if settings_path.exists() {
|
||||
let content = fs::read_to_string(&settings_path)
|
||||
.map_err(|e| format!("read settings: {}", e))?;
|
||||
serde_json::from_str(&content)
|
||||
.map_err(|e| format!("parse settings: {}", e))?
|
||||
} else {
|
||||
serde_json::json!({})
|
||||
};
|
||||
|
||||
let hook_command = hook_binary.to_string_lossy().to_string();
|
||||
|
||||
// Check if hook already exists
|
||||
let hooks = settings
|
||||
.as_object_mut().ok_or("settings not an object")?
|
||||
.entry("hooks")
|
||||
.or_insert_with(|| serde_json::json!({}))
|
||||
.as_object_mut().ok_or("hooks not an object")?
|
||||
.entry("UserPromptSubmit")
|
||||
.or_insert_with(|| serde_json::json!([]))
|
||||
.as_array_mut().ok_or("UserPromptSubmit not an array")?;
|
||||
|
||||
let already_installed = hooks.iter().any(|h| {
|
||||
h.get("command").and_then(|c| c.as_str())
|
||||
.is_some_and(|c| c.contains("memory-search"))
|
||||
});
|
||||
|
||||
if already_installed {
|
||||
eprintln!("Hook already installed in {}", settings_path.display());
|
||||
} else {
|
||||
hooks.push(serde_json::json!({
|
||||
"type": "command",
|
||||
"command": hook_command,
|
||||
"timeout": 10
|
||||
}));
|
||||
let json = serde_json::to_string_pretty(&settings)
|
||||
.map_err(|e| format!("serialize settings: {}", e))?;
|
||||
fs::write(&settings_path, json)
|
||||
.map_err(|e| format!("write settings: {}", e))?;
|
||||
eprintln!("Hook installed: {}", hook_command);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn show_log(job_filter: Option<&str>, lines: usize) -> Result<(), String> {
|
||||
let path = log_path();
|
||||
if !path.exists() {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue