1use anyhow::{Result, anyhow, bail};
11use serde_json::{Value, json};
12
13pub fn init_self_idempotent(
19 handle: &str,
20 name: Option<&str>,
21 relay: Option<&str>,
22) -> Result<Value> {
23 use crate::agent_card::{build_agent_card, sign_agent_card};
24 use crate::signing::{fingerprint, generate_keypair, make_key_id};
25 use crate::trust::{add_self_to_trust, empty_trust};
26
27 if !handle
28 .chars()
29 .all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '_')
30 {
31 bail!("handle must be ASCII alphanumeric / '-' / '_' (got {handle:?})");
32 }
33
34 if crate::config::is_initialized()? {
35 let card = crate::config::read_agent_card()?;
36 let existing_did = card
37 .get("did")
38 .and_then(Value::as_str)
39 .unwrap_or("")
40 .to_string();
41 let existing_handle = card
44 .get("handle")
45 .and_then(Value::as_str)
46 .map(str::to_string)
47 .unwrap_or_else(|| {
48 crate::agent_card::display_handle_from_did(&existing_did).to_string()
49 });
50 let handle: &str = &existing_handle;
57 let pk_b64 = card
58 .get("verify_keys")
59 .and_then(Value::as_object)
60 .and_then(|m| m.values().next())
61 .and_then(|v| v.get("key"))
62 .and_then(Value::as_str)
63 .ok_or_else(|| anyhow!("agent-card missing verify_keys[*].key"))?;
64 let pk_bytes = crate::signing::b64decode(pk_b64)?;
65 let mut out = json!({
66 "did": existing_did,
67 "handle": handle,
68 "fingerprint": fingerprint(&pk_bytes),
69 "key_id": make_key_id(handle, &pk_bytes),
70 "config_dir": crate::config::config_dir()?.to_string_lossy(),
71 "already_initialized": true,
72 });
73 let mut relay_state = crate::config::read_relay_state()?;
74 if let Some(url) = relay {
75 let url = url.trim_end_matches('/');
76 let already = crate::endpoints::self_endpoints(&relay_state)
84 .into_iter()
85 .find(|e| e.relay_url == url);
86 if let Some(ep) = already {
87 out["relay_url"] = json!(url);
88 out["slot_id"] = json!(ep.slot_id);
89 } else {
90 let client = crate::relay_client::RelayClient::new(url);
91 client.check_healthz()?;
92 let alloc = client.allocate_slot(Some(handle))?;
93 crate::endpoints::upsert_self_endpoint(
94 &mut relay_state,
95 crate::endpoints::Endpoint {
96 relay_url: url.to_string(),
97 slot_id: alloc.slot_id.clone(),
98 slot_token: alloc.slot_token,
99 scope: crate::endpoints::infer_scope_from_url(url),
100 },
101 );
102 crate::config::write_relay_state(&relay_state)?;
103 out["relay_url"] = json!(url);
104 out["slot_id"] = json!(alloc.slot_id);
105 }
106 }
107 return Ok(out);
108 }
109
110 crate::config::ensure_dirs()?;
111 let (sk_seed, pk_bytes) = generate_keypair();
112 crate::config::write_private_key(&sk_seed)?;
113
114 let synth_did = crate::agent_card::did_for_with_key(handle, &pk_bytes);
121 let persona = crate::character::Character::from_did(&synth_did).nickname;
122 let handle: &str = &persona;
123
124 let card = build_agent_card(handle, &pk_bytes, name, None, None);
125 let card = crate::enroll::with_op_claims_if_enrolled(card)?;
129 let signed = sign_agent_card(&card, &sk_seed);
130 crate::config::write_agent_card(&signed)?;
131 let mut trust = empty_trust();
132 add_self_to_trust(&mut trust, handle, &pk_bytes);
133 crate::config::write_trust(&trust)?;
134
135 let mut out = json!({
136 "did": crate::agent_card::did_for_with_key(handle, &pk_bytes),
137 "handle": handle,
138 "fingerprint": fingerprint(&pk_bytes),
139 "key_id": make_key_id(handle, &pk_bytes),
140 "config_dir": crate::config::config_dir()?.to_string_lossy(),
141 "already_initialized": false,
142 });
143
144 if let Some(url) = relay {
145 let client = crate::relay_client::RelayClient::new(url);
146 client.check_healthz()?;
147 let alloc = client.allocate_slot(Some(handle))?;
148 let mut rs = crate::config::read_relay_state()?;
149 rs["self"] = json!({
150 "relay_url": url,
151 "slot_id": alloc.slot_id.clone(),
152 "slot_token": alloc.slot_token,
153 });
154 crate::config::write_relay_state(&rs)?;
155 out["relay_url"] = json!(url);
156 out["slot_id"] = json!(alloc.slot_id);
157 }
158
159 Ok(out)
160}