Skip to main content

cyphr_cli/
lib.rs

1//! Cyphr CLI library components.
2//!
3//! This module exposes the CLI types for potential reuse in tests or other tools.
4
5#![warn(missing_docs)]
6#![warn(rust_2018_idioms)]
7#![forbid(unsafe_code)]
8
9use std::path::PathBuf;
10
11use clap::{Parser, Subcommand, ValueEnum};
12
13pub mod commands;
14pub mod keystore;
15
16/// CLI error type. Replaces `Box<dyn Error>` throughout the CLI crate.
17#[derive(Debug, thiserror::Error)]
18pub enum Error {
19    /// I/O error.
20    #[error("{0}")]
21    Io(#[from] std::io::Error),
22
23    /// Keystore operation failed.
24    #[error("{0}")]
25    Keystore(#[from] keystore::Error),
26
27    /// Core cyphr protocol error.
28    #[error("{0}")]
29    Cyphr(#[from] cyphr::Error),
30
31    /// JSON serialization/deserialization error.
32    #[error("{0}")]
33    Json(#[from] serde_json::Error),
34
35    /// Base64 decoding error.
36    #[error("invalid base64url: {0}")]
37    Base64(#[from] base64ct::Error),
38
39    /// A required field was missing from a JSON object.
40    #[error("missing field: {0}")]
41    MissingField(&'static str),
42
43    /// Invalid argument or option value.
44    #[error("{0}")]
45    InvalidArgument(String),
46
47    /// Cryptographic signing or verification failed.
48    #[error("{0}")]
49    Signing(String),
50
51    /// Storage layer error (file store operations).
52    #[error("{0}")]
53    Storage(String),
54
55    /// File store error.
56    #[error("{0}")]
57    FileStore(#[from] cyphr_storage::FileStoreError),
58
59    /// Load error (importing/replaying commits).
60    #[error("{0}")]
61    Load(#[from] cyphr_storage::LoadError),
62
63    /// Export error (serializing principal to commits).
64    #[error("{0}")]
65    Export(#[from] cyphr_storage::ExportError),
66}
67
68/// CLI result type alias.
69pub type Result<T> = std::result::Result<T, Error>;
70
71/// Cyphr identity protocol CLI.
72#[derive(Parser)]
73#[command(name = "cyphr", version, about, long_about = None)]
74pub struct Cli {
75    /// Storage backend URI (e.g., file:./data)
76    #[arg(long, default_value = "file:./cyphr-data")]
77    pub store: String,
78
79    /// Path to private key storage
80    #[arg(long, default_value = "./cyphr-keys.json")]
81    pub keystore: PathBuf,
82
83    /// Authority domain for transaction typ URIs (e.g., example.com)
84    #[arg(long, default_value = "cyphr.me")]
85    pub authority: String,
86
87    /// Output format
88    #[arg(long, value_enum, default_value_t = OutputFormat::Table)]
89    pub output: OutputFormat,
90
91    /// Subcommand to execute
92    #[command(subcommand)]
93    pub command: Commands,
94}
95
96/// Output format for command results.
97#[derive(Debug, Clone, Copy, ValueEnum)]
98pub enum OutputFormat {
99    /// Human-readable table format
100    Table,
101    /// Machine-parseable JSON
102    Json,
103}
104
105/// Top-level subcommands.
106#[derive(Subcommand)]
107pub enum Commands {
108    /// Create a new identity
109    Init {
110        /// Algorithm for genesis key (ES256, ES384, ES512, Ed25519)
111        #[arg(long, default_value = "ES256")]
112        algo: String,
113
114        /// Use existing key from keystore (by thumbprint)
115        #[arg(long)]
116        key: Option<String>,
117
118        /// Create explicit genesis with multiple keys (comma-separated thumbprints)
119        #[arg(long, value_delimiter = ',')]
120        keys: Option<Vec<String>>,
121    },
122
123    /// Key management operations
124    Key {
125        /// Key subcommand to execute
126        #[command(subcommand)]
127        command: KeyCommands,
128    },
129
130    /// ParsedCoz operations
131    Tx {
132        /// ParsedCoz subcommand to execute
133        #[command(subcommand)]
134        command: TxCommands,
135    },
136
137    /// Display identity state
138    Inspect {
139        /// Principal Root (base64url)
140        #[arg(long)]
141        identity: String,
142    },
143
144    /// Export identity to JSONL file
145    Export {
146        /// Principal Root (base64url)
147        #[arg(long)]
148        identity: String,
149
150        /// Output file path
151        #[arg(long)]
152        output: PathBuf,
153    },
154
155    /// Import identity from JSONL file
156    Import {
157        /// Input file path
158        #[arg(long)]
159        input: PathBuf,
160    },
161}
162
163/// Key management subcommands.
164#[derive(Subcommand)]
165pub enum KeyCommands {
166    /// Generate a new keypair
167    Generate {
168        /// Algorithm (ES256, ES384, ES512, Ed25519)
169        #[arg(long, default_value = "ES256")]
170        algo: String,
171
172        /// Optional tag for the key
173        #[arg(long)]
174        tag: Option<String>,
175    },
176
177    /// Add a key to an identity
178    Add {
179        /// Principal Root (base64url)
180        #[arg(long)]
181        identity: String,
182
183        /// Thumbprint of key to add (if omitted, generates new key)
184        #[arg(long)]
185        key: Option<String>,
186
187        /// Thumbprint of signing key
188        #[arg(long)]
189        signer: String,
190    },
191
192    /// Revoke a key from an identity
193    Revoke {
194        /// Principal Root (base64url)
195        #[arg(long)]
196        identity: String,
197
198        /// Thumbprint of key to revoke
199        #[arg(long)]
200        key: String,
201
202        /// Thumbprint of signing key
203        #[arg(long)]
204        signer: String,
205    },
206
207    /// List keys (from keystore if no identity, from identity if provided)
208    List {
209        /// Principal Root (base64url) - if omitted, lists keystore keys
210        #[arg(long)]
211        identity: Option<String>,
212    },
213}
214
215/// ParsedCoz subcommands.
216#[derive(Subcommand)]
217pub enum TxCommands {
218    /// List cozies for an identity
219    List {
220        /// Principal Root (base64url)
221        #[arg(long)]
222        identity: String,
223    },
224
225    /// Verify coz chain integrity
226    Verify {
227        /// Principal Root (base64url)
228        #[arg(long)]
229        identity: String,
230    },
231}