feat: phase 2 — playbooks (am history --save + am playbook, rejeu pas à pas) (closes #51)

This commit is contained in:
2026-08-18 13:30:29 -04:00
parent e82979e6a3
commit aa0d8e43eb
17 changed files with 630 additions and 16 deletions
+45
View File
@@ -42,6 +42,37 @@ fn rerun_entry(app: &App, n: usize) -> Result<i32> {
}
}
/// Filter history entries; returns them most recent first.
/// Parse "12..25" (inclusive bounds, 1-based) or a single "12".
fn parse_range(r: &str) -> Result<(usize, usize)> {
let r = r.trim();
if r.is_empty() {
anyhow::bail!("empty range — usage: am history <from>..<to> --save <file>");
}
if let Some((a, b)) = r.split_once("..") {
let from = a
.trim()
.parse::<usize>()
.map_err(|_| anyhow!("invalid range '{r}' — expected <from>..<to>"))?;
let to = b
.trim()
.parse::<usize>()
.map_err(|_| anyhow!("invalid range '{r}' — expected <from>..<to>"))?;
if from == 0 || to == 0 {
anyhow::bail!("history entries start at 1");
}
Ok((from, to))
} else {
let n = r
.parse::<usize>()
.map_err(|_| anyhow!("invalid range '{r}' — expected <from>..<to>"))?;
if n == 0 {
anyhow::bail!("history entries start at 1");
}
Ok((n, n))
}
}
/// Filter history entries; returns them most recent first.
pub fn filter(
app: &App,
@@ -96,11 +127,25 @@ pub fn run(
session: Option<&str>,
limit: usize,
rerun: Option<usize>,
range: Option<&str>,
save: Option<&Path>,
) -> Result<i32> {
history::migrate(app)?;
if let Some(n) = rerun {
return rerun_entry(app, n);
}
// Issue #51: 'am history 12..25 --save <file>' exports a playbook.
if let Some(r) = range {
let file = save.ok_or_else(|| anyhow!("--save <file> is required with a range"))?;
let (from, to) = parse_range(r)?;
let (n, path) = crate::playbook::export_range(app, from, to, file)?;
app.log
.success(&format!("playbook saved: {} ({n} step(s))", path.display()));
return Ok(0);
}
if save.is_some() {
anyhow::bail!("usage: am history <from>..<to> --save <file>");
}
let rows = filter(app, kind, cwd, search, failed, session, limit);
if app.json() {
let payload: Vec<serde_json::Value> = rows