systemprompt-cli 0.2.1

Unified CLI for systemprompt.io AI governance: agent orchestration, MCP governance, analytics, profiles, cloud deploy, and self-hosted operations.
Documentation
pub mod remote;

use anyhow::{Context, Result};
use systemprompt_cloud::{SessionKey, SessionStore, StoredTenant, TenantStore};
use systemprompt_identifiers::{ContextId, TenantId};
use systemprompt_models::ProfileBootstrap;

use crate::paths::ResolvedPaths;

pub enum ExecutionTarget {
    Local,
    Remote {
        hostname: String,
        token: String,
        context: ContextId,
    },
}

pub fn determine_execution_target() -> Result<ExecutionTarget> {
    let Ok(profile) = ProfileBootstrap::get() else {
        tracing::debug!("No profile loaded, routing to local execution");
        return Ok(ExecutionTarget::Local);
    };

    if profile.target.is_local() {
        tracing::debug!(
            profile_name = %profile.name,
            "Profile target is local, routing to local execution"
        );
        return Ok(ExecutionTarget::Local);
    }

    let Some(tenant_id) = profile.cloud.as_ref().and_then(|c| c.tenant_id.as_ref()) else {
        tracing::debug!(
            profile_name = %profile.name,
            "Profile has no tenant_id, routing to local execution"
        );
        return Ok(ExecutionTarget::Local);
    };

    tracing::debug!(
        profile_name = %profile.name,
        tenant_id = %tenant_id,
        "Profile has tenant_id, resolving remote execution target"
    );

    let tenant = resolve_tenant(tenant_id)?;
    let hostname = tenant
        .hostname
        .as_ref()
        .context("Tenant has no hostname configured")?
        .clone();

    let session_key = SessionKey::Tenant(TenantId::new(tenant_id.clone()));
    let session = load_session_for_key(&session_key)?;

    tracing::info!(
        hostname = %hostname,
        tenant_id = %tenant_id,
        "Routing to remote execution"
    );

    Ok(ExecutionTarget::Remote {
        hostname,
        token: session.session_token.as_str().to_string(),
        context: session.context_id.clone(),
    })
}

fn resolve_tenant(tenant: &str) -> Result<StoredTenant> {
    let tenants_path = ResolvedPaths::discover().tenants_path();

    let store = TenantStore::load_from_path(&tenants_path)
        .context("Failed to load tenants. Run 'systemprompt cloud tenant list' to sync.")?;

    store
        .find_tenant(tenant)
        .cloned()
        .with_context(|| format!("Tenant '{}' not found in local tenant store", tenant))
}

fn load_session_for_key(session_key: &SessionKey) -> Result<systemprompt_cloud::CliSession> {
    let sessions_dir = ResolvedPaths::discover().sessions_dir();

    let store = SessionStore::load_or_create(&sessions_dir)?;

    store
        .get_valid_session(session_key)
        .cloned()
        .context("No active session. Run 'systemprompt admin session login'.")
}