Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T04:16:40.487Z Public web read
NIP-34 coordinate30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omega
MaintainersHidden in public view
References2 branches · 1 tag
Read-only clonegit clone https://openagents.com/git/tenant.openagents/omega.git
Browse files

omega_identity.rs

223 lines · 7.8 KB · rust
1//! Operator CLI for Omega Nostr identity custody.
2//!
3//! Ready-state UI only offers Protect, not Reset. Use this tool to inspect
4//! and clear channel custody through the typed IdentityService path.
5
6use std::{path::PathBuf, process::ExitCode, str::FromStr};
7
8use anyhow::{Context as _, Result, bail};
9use app_identity::AppChannel;
10use clap::{Parser, Subcommand};
11use omega_identity::{
12    CustodyState, IdentityRef, IdentityService, NostrPublicKeyHex, OwnerAttestationRequest,
13    ReceiptRef,
14};
15use serde_json::json;
16
17#[derive(Parser, Debug)]
18#[command(
19    name = "omega-identity",
20    about = "Inspect and reset Omega Nostr identity custody"
21)]
22struct Args {
23    /// Release channel that owns the identity (`dev`, `nightly`, `rc`, `stable`).
24    #[arg(long, default_value = "rc")]
25    channel: String,
26
27    /// Optional override for the channel data root (parent of `identity/`).
28    #[arg(long)]
29    data_root: Option<PathBuf>,
30
31    #[command(subcommand)]
32    command: Command,
33}
34
35#[derive(Subcommand, Debug)]
36enum Command {
37    /// Print custody state and public identity refs as JSON.
38    Status,
39    /// Sign a public NIP-OA auth tag for an admitted agent without exporting the owner key.
40    AttestAgent {
41        #[arg(long)]
42        agent_public_key_hex: String,
43        #[arg(long, default_value = "")]
44        conditions: String,
45        #[arg(long)]
46        request: Option<String>,
47    },
48    /// Record a marker-first reset (relaunch-required). Requires --yes.
49    Reset {
50        /// Confirm destructive reset of the current Ready identity.
51        #[arg(long)]
52        yes: bool,
53        /// Optional authorization receipt; generated when omitted.
54        #[arg(long)]
55        receipt: Option<String>,
56        /// Optional expected identity_ref; defaults to the inspected Ready identity.
57        #[arg(long)]
58        identity_ref: Option<String>,
59    },
60    /// Resume a pending or failed reset (deletes Keychain + public documents).
61    Resume,
62    /// Acknowledge a completed reset after relaunch proof (Absent).
63    Acknowledge,
64    /// Reset, resume, and acknowledge in one operator flow. Requires --yes.
65    Wipe {
66        #[arg(long)]
67        yes: bool,
68        #[arg(long)]
69        receipt: Option<String>,
70    },
71}
72
73fn main() -> ExitCode {
74    match run() {
75        Ok(()) => ExitCode::SUCCESS,
76        Err(error) => {
77            eprintln!("error: {error:#}");
78            ExitCode::FAILURE
79        }
80    }
81}
82
83fn run() -> Result<()> {
84    let args = Args::parse();
85    let channel = AppChannel::from_str(&args.channel).map_err(|_| {
86        anyhow::anyhow!(
87            "invalid --channel {}; expected dev|nightly|rc|stable",
88            args.channel
89        )
90    })?;
91    let service = match args.data_root {
92        Some(root) => IdentityService::for_channel_data_root(channel, root),
93        None => IdentityService::for_channel(channel),
94    };
95
96    match args.command {
97        Command::Status => {
98            let inspection = service
99                .inspect_details()
100                .context("inspect identity custody")?;
101            println!("{}", serde_json::to_string_pretty(&inspection)?);
102        }
103        Command::AttestAgent {
104            agent_public_key_hex,
105            conditions,
106            request,
107        } => {
108            let inspection = service.inspect().context("inspect identity custody")?;
109            let identity = inspection
110                .identity
111                .context("no Ready identity to attest an agent")?;
112            let stamp = std::time::SystemTime::now()
113                .duration_since(std::time::UNIX_EPOCH)
114                .context("system clock")?
115                .as_millis();
116            let result = service
117                .sign_owner_attestation(&OwnerAttestationRequest {
118                    request_ref: ReceiptRef::new(request.unwrap_or_else(|| {
119                        format!("omega-identity-cli-agent-attestation-{stamp}")
120                    }))?,
121                    identity_ref: identity.identity_ref().clone(),
122                    agent_public_key_hex: NostrPublicKeyHex::new(agent_public_key_hex)?,
123                    conditions,
124                })
125                .context("sign owner agent attestation")?;
126            println!("{}", serde_json::to_string_pretty(&result)?);
127        }
128        Command::Reset {
129            yes,
130            receipt,
131            identity_ref,
132        } => {
133            if !yes {
134                bail!("refusing reset without --yes");
135            }
136            let result = reset_identity(&service, receipt, identity_ref)?;
137            println!("{}", serde_json::to_string_pretty(&result)?);
138        }
139        Command::Resume => {
140            let result = service
141                .resume_pending_reset()
142                .context("resume pending identity reset")?;
143            println!("{}", serde_json::to_string_pretty(&result)?);
144        }
145        Command::Acknowledge => {
146            let result = service
147                .acknowledge_relaunch()
148                .context("acknowledge completed identity reset")?;
149            println!("{}", serde_json::to_string_pretty(&result)?);
150        }
151        Command::Wipe { yes, receipt } => {
152            if !yes {
153                bail!("refusing wipe without --yes");
154            }
155            let reset = reset_identity(&service, receipt, None)?;
156            let resumed = match service.resume_pending_reset() {
157                Ok(result) => result,
158                Err(_) => {
159                    // inspect() also resumes a pending marker
160                    service.inspect().context("inspect after reset marker")?
161                }
162            };
163            let acknowledged = if resumed.state == CustodyState::RelaunchRequired {
164                service
165                    .acknowledge_relaunch()
166                    .context("acknowledge completed identity reset")?
167            } else if resumed.state == CustodyState::Absent {
168                resumed.clone()
169            } else {
170                bail!(
171                    "reset did not reach relaunch-required or absent; state={:?}",
172                    resumed.state
173                );
174            };
175            println!(
176                "{}",
177                serde_json::to_string_pretty(&json!({
178                    "channel": channel.storage_slug(),
179                    "reset": reset,
180                    "resumed": resumed,
181                    "acknowledged": acknowledged,
182                }))?
183            );
184        }
185    }
186    Ok(())
187}
188
189fn reset_identity(
190    service: &IdentityService,
191    receipt: Option<String>,
192    identity_ref: Option<String>,
193) -> Result<omega_identity::CustodyResult> {
194    let inspection = service
195        .inspect_details()
196        .context("inspect identity before reset")?;
197    let expected = match identity_ref {
198        Some(value) => IdentityRef::new(value).context("parse --identity-ref")?,
199        None => {
200            let identity = inspection
201                .custody
202                .identity
203                .as_ref()
204                .context("no Ready identity to reset; run status first")?;
205            IdentityRef::new(identity.identity_ref().as_str()).context("copy identity_ref")?
206        }
207    };
208    let authorization = match receipt {
209        Some(value) => ReceiptRef::new(value).context("parse --receipt")?,
210        None => {
211            let stamp = std::time::SystemTime::now()
212                .duration_since(std::time::UNIX_EPOCH)
213                .context("system clock")?
214                .as_millis();
215            ReceiptRef::new(format!("omega-identity-cli-reset-{stamp}"))
216                .context("build reset receipt")?
217        }
218    };
219    service
220        .reset(&expected, authorization)
221        .context("record identity reset marker")
222}
223
Served at tenant.openagents/omega Member data and write actions are omitted.