use crate::args::Args;
use crate::event::VivacKind;
use crate::failure::{Failure, R};
fn envelope(evento: &str, text: &str) -> String {
serde_json::json!({
"hookSpecificOutput": {
"hookEventName": evento,
"additionalContext": text,
}
})
.to_string()
}
pub fn start(ctx: &crate::ops::Ctx, a: &Args, project: &str) -> R {
if !a.has("hook") {
return crate::brief::brief(&ctx.tree, ctx.anchor.as_ref(), a, project);
}
let text = crate::brief::to_text(&ctx.tree, ctx.anchor.as_ref(), a, project)?;
println!("{}", envelope("SessionStart", &text));
Ok(())
}
pub fn end(ctx: &mut crate::ops::Ctx, a: &Args) -> R {
if ctx.tree.stack.is_empty() {
if !a.has("hook") {
println!(" Empty stack: no stop worth saving.");
}
return Ok(());
}
if ctx.tree.seq_change <= ctx.tree.seq_vivac {
if !a.has("hook") {
println!(" Nothing changed since the last stop.");
}
return Ok(());
}
let next = a.opt_or("next");
let num = ctx.tree.next_vivac_num.max(1);
crate::ops::auto_vivac(ctx, VivacKind::Auto, &next)?;
if !a.has("hook") {
println!(" v{num} automatic stop at session close");
}
Ok(())
}
pub fn dispatch(ctx: &mut crate::ops::Ctx, a: &Args, project: &str) -> R {
match a.positional(0) {
Some("start") | Some("inicio") => start(ctx, a, project),
Some("end") | Some("fin") => end(ctx, a),
_ => Err(Failure::usage("usage: vivac session start|end [--hook]")),
}
}
pub fn hooks() -> R {
println!(
r#"
Paste this into the project's .claude/settings.json:
{{
"hooks": {{
"SessionStart": [
{{ "hooks": [{{ "type": "command", "command": "vivac session start --hook" }}] }}
],
"Stop": [
{{ "hooks": [{{ "type": "command", "command": "vivac session end --hook" }}] }}
]
}}
}}
SessionStart injects the brief into the agent's context.
Stop leaves an automatic stop with the stack as it stood.
Both stay quiet and exit 0 where there is no .vivac/, so they can be left
in the global configuration without getting in the way of other projects.
"#
);
Ok(())
}