use uuid::Uuid;
use crate::cron_jobs::normalize_schedule;
use crate::routine_storage::write_routine;
use crate::utils::time::now_secs;
use super::command::slugify;
use super::model::{Routine, RoutineStore};
struct DefaultRoutine {
title: &'static str,
schedule: &'static str,
agent: &'static str,
prompt: &'static str,
}
const UPDATE_MOADIM_PROMPT: &str = "\
Ensure the locally installed `moadim` cargo package is up to date, and update it if it is not.
Steps:
1. Find the installed version: `cargo install --list | grep '^moadim '` (no output means it is not installed).
2. Find the latest published version on crates.io: `cargo search moadim --limit 1`.
3. If `moadim` is not installed, or the installed version is older than the latest published version, run `cargo install moadim --force` to update it.
4. If it is already on the latest version, make no changes.
Report which versions you found and whether an update was performed.
";
const DEFAULT_ROUTINES: &[DefaultRoutine] = &[DefaultRoutine {
title: "Update moadim cargo package",
schedule: "0 9 * * *",
agent: "claude",
prompt: UPDATE_MOADIM_PROMPT,
}];
fn materialize(spec: &DefaultRoutine, now: u64) -> Routine {
Routine {
id: Uuid::new_v4().to_string(),
schedule: normalize_schedule(spec.schedule),
title: spec.title.to_string(),
agent: spec.agent.to_string(),
prompt: spec.prompt.to_string(),
repositories: Vec::new(),
enabled: true,
source: "managed".to_string(),
created_at: now,
updated_at: now,
last_triggered_at: None,
ttl_secs: None,
}
}
fn reconcile(spec: &DefaultRoutine, cur: &Routine, now: u64) -> Option<Routine> {
let schedule = normalize_schedule(spec.schedule);
let up_to_date = cur.schedule == schedule
&& cur.agent == spec.agent
&& cur.prompt == spec.prompt
&& cur.repositories.is_empty();
if up_to_date {
return None;
}
Some(Routine {
id: cur.id.clone(),
schedule,
title: spec.title.to_string(),
agent: spec.agent.to_string(),
prompt: spec.prompt.to_string(),
repositories: Vec::new(),
enabled: cur.enabled,
source: "managed".to_string(),
created_at: cur.created_at,
updated_at: now,
last_triggered_at: cur.last_triggered_at,
ttl_secs: cur.ttl_secs,
})
}
pub fn ensure_default_routines(store: &RoutineStore) {
for spec in DEFAULT_ROUTINES {
let slug = slugify(spec.title);
let existing = store
.lock()
.unwrap()
.values()
.find(|routine| slugify(&routine.title) == slug)
.cloned();
let routine = match existing {
Some(cur) => match reconcile(spec, &cur, now_secs()) {
Some(updated) => updated,
None => continue,
},
None => materialize(spec, now_secs()),
};
if let Err(err) = write_routine(&routine) {
log::warn!(
"ensure_default_routines: failed to write {:?}: {err}; skipping",
spec.title
);
continue;
}
store.lock().unwrap().insert(routine.id.clone(), routine);
}
}
#[cfg(test)]
#[path = "defaults_tests.rs"]
mod defaults_tests;