use std::io::Write;
use std::path::PathBuf;
use clap::Args;
use pointbreak::crypto::SignerId;
use pointbreak::error::{Result as ShoreResult, ShoreError};
use pointbreak::keys::load_signer_id;
use pointbreak::model::ActorId;
use pointbreak::session::{
ALLOWED_SIGNERS_REL_PATH, EnrollmentDiff, is_valid_actor_id, resolve_writer_actor_id,
stage_enrollment,
};
use serde::Serialize;
use crate::cli::json::DiagnosticDocument;
use crate::cli::output;
#[derive(Debug, Args)]
pub(super) struct EnrollArgs {
#[arg(long, default_value = ".")]
repo: PathBuf,
#[arg(default_value = "default")]
name: String,
#[arg(long)]
actor: Option<String>,
#[arg(long)]
pretty: bool,
#[command(flatten)]
format_args: output::FormatArgs,
}
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
struct EnrollBody {
actor_id: String,
signer_id: String,
path: String,
added: bool,
}
pub(super) fn run(
args: EnrollArgs,
stdout: &mut dyn Write,
) -> Result<(), Box<dyn std::error::Error>> {
let signer_id: SignerId = load_signer_id(&args.name)?;
let actor = resolve_actor(&args)?;
let worktree_root =
pointbreak::git::git_worktree_root(&args.repo).unwrap_or_else(|_| args.repo.clone());
let path = worktree_root.join(ALLOWED_SIGNERS_REL_PATH);
let EnrollmentDiff { added } = stage_enrollment(&path, &actor, &signer_id)?;
let body = EnrollBody {
actor_id: actor.as_str().to_owned(),
signer_id: signer_id.as_str().to_owned(),
path: path.display().to_string(),
added,
};
let document = DiagnosticDocument::new("shore.keys-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)
}
fn resolve_actor(args: &EnrollArgs) -> ShoreResult<ActorId> {
if let Some(raw_actor) = args.actor.as_deref() {
let actor = raw_actor.trim();
if !is_valid_actor_id(actor) {
return Err(ShoreError::WorkflowInputInvalid {
reason: format!(
"--actor {raw_actor:?} is not a valid actor id; expected \
actor:<scheme>:<value> (for example, actor:agent:codex) \
or a did:key signer id"
),
});
}
return Ok(ActorId::new(actor.to_owned()));
}
Ok(resolve_writer_actor_id(&args.repo, None))
}