opencode-cloud 25.1.3

CLI for managing opencode as a persistent cloud service
Documentation
use opencode_cloud_core::docker::{CONTAINER_NAME, DockerClient, exec_command_with_status};
use serde_json::Value;

const BOOTSTRAP_HELPER_PATH: &str = "/usr/local/bin/opencode-cloud-bootstrap";
pub(crate) const IOTP_FALLBACK_COMMAND: &str = "occ logs | grep -F \"INITIAL ONE-TIME PASSWORD (IOTP): \" | tail -n1 | sed 's/.*INITIAL ONE-TIME PASSWORD (IOTP): //'";

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum IotpState {
    ActiveUnused,
    InactiveUsersConfigured,
    InactiveCompleted,
    InactiveNotInitialized,
    ErrorInvalidState,
    ErrorHelper,
    Unavailable,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct IotpSnapshot {
    pub(crate) state: IotpState,
    pub(crate) state_label: String,
    pub(crate) otp: Option<String>,
    pub(crate) detail: Option<String>,
}

impl IotpSnapshot {
    pub(crate) fn unavailable(detail: impl Into<String>) -> Self {
        Self {
            state: IotpState::Unavailable,
            state_label: "unavailable".to_string(),
            otp: None,
            detail: Some(detail.into()),
        }
    }
}

pub(crate) async fn fetch_iotp_snapshot(client: &DockerClient) -> IotpSnapshot {
    query_iotp_snapshot(
        client,
        vec![BOOTSTRAP_HELPER_PATH, "status", "--include-secret"],
    )
    .await
}

pub(crate) async fn reset_iotp_snapshot(client: &DockerClient) -> IotpSnapshot {
    query_iotp_snapshot(client, vec![BOOTSTRAP_HELPER_PATH, "reset"]).await
}

async fn query_iotp_snapshot(client: &DockerClient, command: Vec<&str>) -> IotpSnapshot {
    let (output, status) = match exec_command_with_status(client, CONTAINER_NAME, command).await {
        Ok(result) => result,
        Err(err) => {
            return IotpSnapshot::unavailable(format!("failed to query bootstrap helper: {err}"));
        }
    };

    if status != 0 {
        let detail = if output.trim().is_empty() {
            format!("bootstrap helper exited with status {status}")
        } else {
            format!(
                "bootstrap helper exited with status {status}: {}",
                output.trim()
            )
        };
        return IotpSnapshot::unavailable(detail);
    }

    match parse_snapshot_output(&output) {
        Ok(snapshot) => snapshot,
        Err(err) => IotpSnapshot::unavailable(err),
    }
}

fn snapshot(
    state: IotpState,
    state_label: &str,
    otp: Option<String>,
    detail: Option<String>,
) -> IotpSnapshot {
    IotpSnapshot {
        state,
        state_label: state_label.to_string(),
        otp,
        detail,
    }
}

fn parse_snapshot_output(output: &str) -> Result<IotpSnapshot, String> {
    let trimmed = output.trim();
    if trimmed.is_empty() {
        return Err("bootstrap helper returned empty output".to_string());
    }

    let payload = parse_json_payload(trimmed)?;
    let ok = payload.get("ok").and_then(Value::as_bool).unwrap_or(false);
    if !ok {
        let code = payload
            .get("code")
            .and_then(Value::as_str)
            .unwrap_or("unknown");
        let message = payload
            .get("message")
            .and_then(Value::as_str)
            .unwrap_or("unknown helper error");
        return Ok(snapshot(
            IotpState::ErrorHelper,
            "error",
            None,
            Some(format!("{code}: {message}")),
        ));
    }

    let active = payload
        .get("active")
        .and_then(Value::as_bool)
        .unwrap_or(false);
    if active {
        let otp = payload
            .get("otp")
            .and_then(Value::as_str)
            .map(str::trim)
            .filter(|value| !value.is_empty())
            .map(ToOwned::to_owned);
        if let Some(token) = otp {
            return Ok(snapshot(
                IotpState::ActiveUnused,
                "unused (active)",
                Some(token),
                None,
            ));
        }
        return Ok(snapshot(
            IotpState::ErrorInvalidState,
            "error (invalid bootstrap state)",
            None,
            Some("bootstrap helper reported active state without an OTP value".to_string()),
        ));
    }

    let reason = payload
        .get("reason")
        .and_then(Value::as_str)
        .unwrap_or("unknown");
    let mapped = match reason {
        "user_exists" => snapshot(
            IotpState::InactiveUsersConfigured,
            "inactive (users configured)",
            None,
            None,
        ),
        "completed" => snapshot(IotpState::InactiveCompleted, "used (completed)", None, None),
        "not_initialized" => snapshot(
            IotpState::InactiveNotInitialized,
            "inactive (not initialized)",
            None,
            None,
        ),
        "invalid_state" | "invalid_secret" => snapshot(
            IotpState::ErrorInvalidState,
            "error (invalid bootstrap state)",
            None,
            Some(format!("bootstrap helper reported reason: {reason}")),
        ),
        other => snapshot(
            IotpState::Unavailable,
            "unavailable",
            None,
            Some(format!("bootstrap helper reported reason: {other}")),
        ),
    };
    Ok(mapped)
}

fn parse_json_payload(raw: &str) -> Result<Value, String> {
    if let Ok(value) = serde_json::from_str::<Value>(raw) {
        return Ok(value);
    }

    for line in raw.lines().rev() {
        let line = line.trim();
        if line.is_empty() {
            continue;
        }
        if let Ok(value) = serde_json::from_str::<Value>(line) {
            return Ok(value);
        }
    }

    Err("failed to parse bootstrap helper JSON output".to_string())
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn parse_active_snapshot_with_otp() {
        let raw = r#"{"ok":true,"active":true,"created_at":"2026-02-08T00:00:00Z","otp":"abc123"}"#;
        let snapshot = parse_snapshot_output(raw).expect("parse should succeed");
        assert_eq!(snapshot.state, IotpState::ActiveUnused);
        assert_eq!(snapshot.state_label, "unused (active)");
        assert_eq!(snapshot.otp, Some("abc123".to_string()));
        assert!(snapshot.detail.is_none());
    }

    #[test]
    fn parse_user_exists_reason() {
        let raw = r#"{"ok":true,"active":false,"reason":"user_exists"}"#;
        let snapshot = parse_snapshot_output(raw).expect("parse should succeed");
        assert_eq!(snapshot.state, IotpState::InactiveUsersConfigured);
        assert_eq!(snapshot.state_label, "inactive (users configured)");
        assert!(snapshot.otp.is_none());
    }

    #[test]
    fn parse_completed_reason() {
        let raw = r#"{"ok":true,"active":false,"reason":"completed"}"#;
        let snapshot = parse_snapshot_output(raw).expect("parse should succeed");
        assert_eq!(snapshot.state, IotpState::InactiveCompleted);
        assert_eq!(snapshot.state_label, "used (completed)");
    }

    #[test]
    fn parse_not_initialized_reason() {
        let raw = r#"{"ok":true,"active":false,"reason":"not_initialized"}"#;
        let snapshot = parse_snapshot_output(raw).expect("parse should succeed");
        assert_eq!(snapshot.state, IotpState::InactiveNotInitialized);
        assert_eq!(snapshot.state_label, "inactive (not initialized)");
    }

    #[test]
    fn parse_invalid_state_reason() {
        let raw = r#"{"ok":true,"active":false,"reason":"invalid_secret"}"#;
        let snapshot = parse_snapshot_output(raw).expect("parse should succeed");
        assert_eq!(snapshot.state, IotpState::ErrorInvalidState);
        assert_eq!(snapshot.state_label, "error (invalid bootstrap state)");
        assert!(
            snapshot
                .detail
                .expect("detail should be present")
                .contains("invalid_secret")
        );
    }

    #[test]
    fn parse_helper_error_payload() {
        let raw = r#"{"ok":false,"code":"otp_invalid","message":"bad otp","status":401}"#;
        let snapshot = parse_snapshot_output(raw).expect("parse should succeed");
        assert_eq!(snapshot.state, IotpState::ErrorHelper);
        assert_eq!(snapshot.state_label, "error");
        assert_eq!(snapshot.detail, Some("otp_invalid: bad otp".to_string()));
    }

    #[test]
    fn malformed_json_is_error() {
        let err = parse_snapshot_output("not-json").expect_err("parse should fail");
        assert!(err.contains("failed to parse"));
    }
}