cel-context 0.1.1

Portable context snapshots for AI agents. Normalize UI, browser, app, log, and external signals into reusable elements and snapshots.
Documentation
//! Generic context snapshot — builds a ScreenContext from caller-provided streams.
//!
//! Usage:
//!   cargo run -p cel-context --example context_snapshot
//!   cargo run -p cel-context --example context_snapshot -- --json

use std::collections::HashMap;

use cel_context::{
    Bounds, ContentRole, ContextContribution, ContextElement, ContextMerger, ContextSource,
    ElementState, StreamStatus,
};

fn main() {
    let json_output = std::env::args().any(|arg| arg == "--json");

    let mut merger = ContextMerger::new().with_defaults("Example CRM", "Customer Detail");
    merger.push(
        ContextContribution::new(
            "crm_api",
            vec![
                element(
                    "crm:title",
                    "heading",
                    "Customer: Acme Corp",
                    ContextSource::NativeApi,
                    None,
                ),
                element(
                    "crm:button:save",
                    "button",
                    "Save changes",
                    ContextSource::NativeApi,
                    Some(Bounds {
                        x: 720,
                        y: 64,
                        width: 128,
                        height: 36,
                    }),
                ),
            ],
        )
        .with_app("Example CRM")
        .with_window("Customer Detail")
        .with_stream_status(StreamStatus {
            accessibility: false,
            display: false,
            network: false,
            signals: false,
            vision: false,
            audio_capture: false,
        }),
    );
    merger.push(ContextContribution::new(
        "browser_dom",
        vec![element(
            "dom:input:notes",
            "textbox",
            "Internal notes",
            ContextSource::Cdp,
            Some(Bounds {
                x: 160,
                y: 420,
                width: 520,
                height: 120,
            }),
        )],
    ));

    let ctx = merger.build();

    if json_output {
        println!("{}", serde_json::to_string_pretty(&ctx).unwrap());
        return;
    }

    println!("=== CEL Context Snapshot ===");
    println!("App:       {}", ctx.app);
    println!("Window:    {}", ctx.window);
    println!("Timestamp: {} ms", ctx.timestamp_ms);
    println!("Elements:  {}", ctx.elements.len());
    println!();

    for (i, elem) in ctx.elements.iter().enumerate() {
        let label = elem.label.as_deref().unwrap_or("(no label)");
        let bounds = elem
            .bounds
            .as_ref()
            .map(|b| format!("[{},{} {}x{}]", b.x, b.y, b.width, b.height))
            .unwrap_or_else(|| "(no bounds)".into());
        println!(
            "  {:>2}. [{:.2}] {:12} {:22} {} {:?}",
            i + 1,
            elem.confidence,
            elem.element_type,
            label,
            bounds,
            elem.source
        );
    }
}

fn element(
    id: &str,
    element_type: &str,
    label: &str,
    source: ContextSource,
    bounds: Option<Bounds>,
) -> ContextElement {
    ContextElement {
        id: id.into(),
        label: Some(label.into()),
        description: None,
        element_type: element_type.into(),
        value: None,
        bounds,
        state: ElementState::default(),
        parent_id: None,
        actions: Vec::new(),
        confidence: 0.0,
        source,
        content_role: ContentRole::Interactive,
        properties: HashMap::new(),
    }
}