use std::io::Write;
use std::path::PathBuf;
use clap::Args;
use pointbreak::model::ActorId;
use pointbreak::session::{
DELEGATES_LOCAL_REL_PATH, DELEGATES_REL_PATH, DelegationMap, DelegationStageOutcome,
DelegationWriteRecord, ensure_shore_gitignore, now_rfc3339_utc, stage_delegation,
};
use serde::Serialize;
use crate::cli::json::DiagnosticDocument;
use crate::cli::output;
#[derive(Debug, Args)]
pub(super) struct DelegateArgs {
agent: String,
#[arg(long)]
principal: String,
#[arg(long)]
from: Option<String>,
#[arg(long)]
until: Option<String>,
#[arg(long)]
comment: Option<String>,
#[arg(long)]
local: bool,
#[arg(long, default_value = ".")]
repo: PathBuf,
#[arg(long)]
pretty: bool,
#[command(flatten)]
format_args: output::FormatArgs,
}
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
struct DelegateBody {
agent: String,
principal: String,
valid_from: String,
valid_until: Option<String>,
comment: Option<String>,
path: String,
local: bool,
added: bool,
#[serde(skip_serializing_if = "Option::is_none")]
local_shadows_committed: Option<usize>,
}
pub(super) fn run(
args: DelegateArgs,
stdout: &mut dyn Write,
stderr: &mut dyn Write,
) -> Result<(), Box<dyn std::error::Error>> {
let agent = ActorId::new(&args.agent);
let principal = ActorId::new(&args.principal);
let valid_from = args.from.clone().unwrap_or_else(now_rfc3339_utc); let record = DelegationWriteRecord::new(principal.clone(), valid_from.clone())
.with_valid_until(args.until.clone())
.with_comment(args.comment.clone());
let worktree_root =
pointbreak::git::git_worktree_root(&args.repo).unwrap_or_else(|_| args.repo.clone());
let rel = if args.local {
DELEGATES_LOCAL_REL_PATH
} else {
DELEGATES_REL_PATH
};
let path = worktree_root.join(rel);
let mut local_shadows_committed = None;
if args.local {
ensure_shore_gitignore(&worktree_root)?;
let committed_path = worktree_root.join(DELEGATES_REL_PATH);
if committed_path.exists()
&& let Ok(map) = DelegationMap::from_delegates_file(&committed_path)
{
let n = map.record_count_for(&agent);
if n > 0 {
local_shadows_committed = Some(n);
let _ = writeln!(
stderr,
"note: this local entry replaces {n} committed record(s) for {} locally",
agent.as_str()
);
}
}
}
let DelegationStageOutcome { added } = stage_delegation(&path, &agent, &record)?;
let _ = writeln!(
stderr,
"staged {}; review and `git commit` it to authorize.",
path.display()
);
let body = DelegateBody {
agent: agent.as_str().to_owned(),
principal: principal.as_str().to_owned(),
valid_from,
valid_until: args.until,
comment: args.comment,
path: path.display().to_string(),
local: args.local,
added,
local_shadows_committed,
};
let document = DiagnosticDocument::new("shore.identity-enroll", body, Vec::new());
let format = output::resolve_format(
args.format_args.explicit(args.pretty),
output::OutputFormat::Json,
)?;
output::write_document_json_fallback(stdout, format, &document)
}