chore: rename quicproquo → quicprochat in Rust workspace

Rename all crate directories, package names, binary names, proto
package/module paths, ALPN strings, env var prefixes, config filenames,
mDNS service names, and plugin ABI symbols from quicproquo/qpq to
quicprochat/qpc.
This commit is contained in:
2026-03-07 18:24:52 +01:00
parent d8c1392587
commit a710037dde
212 changed files with 609 additions and 609 deletions

View File

@@ -0,0 +1,228 @@
//! v2 CLI command implementations — thin wrappers over the SDK.
use quicprochat_sdk::client::QpqClient;
use quicprochat_sdk::error::SdkError;
/// Register a new user account via OPAQUE.
pub async fn cmd_register_user(
client: &mut QpqClient,
username: &str,
password: &str,
) -> Result<(), SdkError> {
client.register(username, password).await?;
let key = client.identity_key().unwrap_or_default();
println!("registered user: {username}");
println!("identity key : {}", hex::encode(key));
Ok(())
}
/// Log in via OPAQUE and print session info.
pub async fn cmd_login(
client: &mut QpqClient,
username: &str,
password: &str,
) -> Result<(), SdkError> {
client.login(username, password).await?;
println!("logged in as: {username}");
if let Some(key) = client.identity_key() {
println!("identity key: {}", hex::encode(key));
}
Ok(())
}
/// Print local identity information.
pub fn cmd_whoami(client: &QpqClient) {
match client.username() {
Some(u) => println!("username : {u}"),
None => println!("username : (not logged in)"),
}
match client.identity_key() {
Some(k) => println!("identity key: {}", hex::encode(k)),
None => println!("identity key: (none)"),
}
println!("connected : {}", client.is_connected());
println!("authenticated: {}", client.is_authenticated());
}
/// Health check — connect to the server and report status.
pub async fn cmd_health(client: &mut QpqClient) -> Result<(), SdkError> {
let start = std::time::Instant::now();
// The SDK connect() already establishes a QUIC connection.
// If we're already connected, just report success.
if !client.is_connected() {
client.connect().await?;
}
let rtt_ms = start.elapsed().as_millis();
println!("status : ok");
println!("rtt : {rtt_ms}ms");
Ok(())
}
/// Resolve a username to its identity key.
pub async fn cmd_resolve(client: &mut QpqClient, username: &str) -> Result<(), SdkError> {
let rpc = client.rpc()?;
match quicprochat_sdk::users::resolve_user(rpc, username).await? {
Some(key) => {
println!("{username} -> {}", hex::encode(&key));
}
None => {
println!("{username}: not found");
}
}
Ok(())
}
/// List registered devices.
pub async fn cmd_devices_list(client: &mut QpqClient) -> Result<(), SdkError> {
let rpc = client.rpc()?;
let devices = quicprochat_sdk::devices::list_devices(rpc).await?;
if devices.is_empty() {
println!("no devices registered");
} else {
println!("{:<36} {:<20} {}", "DEVICE ID", "NAME", "REGISTERED AT");
for d in &devices {
println!(
"{:<36} {:<20} {}",
hex::encode(&d.device_id),
d.device_name,
d.registered_at,
);
}
}
Ok(())
}
/// Register a new device.
pub async fn cmd_devices_register(
client: &mut QpqClient,
device_id: &str,
device_name: &str,
) -> Result<(), SdkError> {
let rpc = client.rpc()?;
let id_bytes = hex::decode(device_id)
.map_err(|e| SdkError::Other(anyhow::anyhow!("invalid device_id hex: {e}")))?;
let was_new = quicprochat_sdk::devices::register_device(rpc, &id_bytes, device_name).await?;
if was_new {
println!("device registered: {device_name}");
} else {
println!("device already registered: {device_name}");
}
Ok(())
}
/// Revoke a device.
pub async fn cmd_devices_revoke(
client: &mut QpqClient,
device_id: &str,
) -> Result<(), SdkError> {
let rpc = client.rpc()?;
let id_bytes = hex::decode(device_id)
.map_err(|e| SdkError::Other(anyhow::anyhow!("invalid device_id hex: {e}")))?;
let revoked = quicprochat_sdk::devices::revoke_device(rpc, &id_bytes).await?;
if revoked {
println!("device revoked: {device_id}");
} else {
println!("device not found: {device_id}");
}
Ok(())
}
/// Set up account recovery — generate codes and upload encrypted bundles.
pub async fn cmd_recovery_setup(client: &mut QpqClient) -> Result<(), SdkError> {
// Load identity seed from state file.
let state_path = client.config_state_path();
let stored = quicprochat_sdk::state::load_state(&state_path, None)
.map_err(|e| SdkError::Crypto(format!("load identity for recovery: {e}")))?;
let rpc = client.rpc()?;
let codes =
quicprochat_sdk::recovery::setup_recovery(rpc, &stored.identity_seed, &[]).await?;
println!("=== RECOVERY CODES ===");
println!("Save these codes securely. They will NOT be shown again.");
println!("Each code can independently recover your account.");
println!();
for (i, code) in codes.iter().enumerate() {
println!(" {}. {}", i + 1, code);
}
println!();
println!("{} codes generated and uploaded.", codes.len());
Ok(())
}
// ── Outbox commands ──────────────────────────────────────────────────────────
/// List pending outbox entries.
pub fn cmd_outbox_list(client: &QpqClient) -> Result<(), SdkError> {
let store = client.conversations()?;
let entries = quicprochat_sdk::outbox::list_pending(store)?;
if entries.is_empty() {
println!("outbox is empty — no pending messages");
} else {
println!("{:<6} {:<34} {:<8} PAYLOAD SIZE", "ID", "CONVERSATION", "RETRIES");
for e in &entries {
println!(
"{:<6} {:<34} {:<8} {} bytes",
e.id,
e.conversation_id.hex(),
e.retry_count,
e.payload.len(),
);
}
println!("\n{} pending entries", entries.len());
}
Ok(())
}
/// Retry sending all pending outbox entries.
pub async fn cmd_outbox_retry(client: &mut QpqClient) -> Result<(), SdkError> {
let rpc = client.rpc()?;
let store = client.conversations()?;
let (sent, failed) = quicprochat_sdk::outbox::flush_outbox(rpc, store).await?;
println!("outbox flush: {sent} sent, {failed} permanently failed");
Ok(())
}
/// Clear permanently failed outbox entries.
pub fn cmd_outbox_clear(client: &QpqClient) -> Result<(), SdkError> {
let store = client.conversations()?;
let cleared = quicprochat_sdk::outbox::clear_failed(store)?;
println!("cleared {cleared} failed outbox entries");
Ok(())
}
/// Recover an account from a recovery code.
pub async fn cmd_recovery_restore(
client: &mut QpqClient,
code: &str,
) -> Result<(), SdkError> {
let rpc = client.rpc()?;
let (identity_seed, conversation_ids) =
quicprochat_sdk::recovery::recover_account(rpc, code).await?;
// Restore identity.
let keypair = quicprochat_core::IdentityKeypair::from_seed(identity_seed);
client.set_identity_key(keypair.public_key_bytes().to_vec());
println!("account recovered successfully");
println!("identity key: {}", hex::encode(keypair.public_key_bytes()));
if !conversation_ids.is_empty() {
println!(
"{} conversations need rejoin (peers must re-invite this device)",
conversation_ids.len()
);
}
// Save recovered state.
let state = quicprochat_sdk::state::StoredState {
identity_seed,
group: None,
hybrid_key: None,
member_keys: Vec::new(),
};
let state_path = client.config_state_path();
quicprochat_sdk::state::save_state(&state_path, &state, None)?;
println!("state saved to {}", state_path.display());
Ok(())
}