use std::path::{Path, PathBuf};
use recall_hooks::{exit, PromoteError, PromoteTarget};
use crate::project;
use crate::ui::{self, Tone};
#[derive(Debug, Clone, Copy, PartialEq, Eq, clap::ValueEnum)]
pub enum Target {
Global,
Machine,
}
impl From<Target> for PromoteTarget {
fn from(t: Target) -> Self {
match t {
Target::Global => PromoteTarget::Global,
Target::Machine => PromoteTarget::Machine,
}
}
}
pub async fn run(file: &Path, to: Target) -> anyhow::Result<i32> {
let ctx = project::resolve().hook_context()?;
let path = resolve(&ctx.memory_dir, file);
match recall_hooks::promote(&ctx, &path, to.into()).await {
Ok(res) => {
let (scope, next) = match to {
Target::Global => (
ctx.global(),
"Every other project picks it up at its next session start.",
),
Target::Machine => (
ctx.machine(),
"It follows this machine into every project here, and reaches no other \
machine.",
),
};
let key = scope.map(|s| s.key.as_str()).unwrap_or_default();
ui::title("recall promote", &format!("{} → {key}", ctx.project_key()));
anstream::println!();
if res.resumed {
ui::step(
Tone::Good,
&format!(
"Finished an earlier promotion: {} was already in {}",
res.from, res.to
),
None,
);
} else {
ui::step(
Tone::Good,
&format!("Moved {} to {}", res.from, res.to),
None,
);
}
ui::step(
Tone::Good,
&format!("Stored under {key}, and linked from MEMORY.md"),
None,
);
ui::verdict(Tone::Good, next);
Ok(exit::OK)
}
Err(err) => {
eprintln!("recall: {err}");
Ok(match err {
PromoteError::Sync(_) => exit::SERVER,
_ => exit::CONFIG,
})
}
}
}
fn resolve(memory_dir: &Path, file: &Path) -> PathBuf {
if file.is_absolute() {
return file.to_path_buf();
}
memory_dir.join(file)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_relative_note_is_looked_for_in_the_memory_directory() {
let dir = Path::new("/home/me/.claude/projects/-home-me-app/memory");
assert_eq!(
resolve(dir, Path::new("topics/user.md")),
dir.join("topics/user.md")
);
}
#[test]
fn an_absolute_note_is_taken_as_given() {
let given = Path::new("/home/me/.claude/projects/-home-me-app/memory/user.md");
assert_eq!(resolve(Path::new("/somewhere/else"), given), given);
}
#[test]
fn a_traversing_path_still_reads_as_traversing_after_the_join() {
let dir = Path::new("/home/me/memory");
assert_eq!(
resolve(dir, Path::new("../../.ssh/id_rsa")),
Path::new("/home/me/memory/../../.ssh/id_rsa")
);
}
}