orra 0.0.2

Context-aware agent session management for any application
Documentation
use std::io::{self, Write};
use std::sync::Arc;

use orra::context::CharEstimator;
use orra::message::Message;
use orra::namespace::Namespace;
use orra::policy::PolicyRegistry;
use orra::providers::claude::ClaudeProvider;
use orra::runtime::{Runtime, RuntimeConfig};
use orra::store::InMemoryStore;
use orra::tool::ToolRegistry;
use orra::tools::github::GitHubConfig;

fn system_prompt(owner: &str, repo: &str) -> String {
    format!(
        "You are a GitHub issues assistant for the {owner}/{repo} repository. \
         You help users browse, search, create, comment on, and close issues. \
         Always use the provided tools to interact with GitHub — never guess about issue contents. \
         Be concise. When listing issues, summarize them clearly. \
         When creating issues, write clear titles and well-formatted markdown descriptions."
    )
}

#[tokio::main]
async fn main() {
    let api_key = std::env::var("ANTHROPIC_API_KEY").unwrap_or_else(|_| {
        eprintln!("Error: ANTHROPIC_API_KEY not set.");
        std::process::exit(1);
    });

    let gh_token = std::env::var("GITHUB_TOKEN").unwrap_or_else(|_| {
        eprintln!("Error: GITHUB_TOKEN not set.");
        eprintln!("Create one at https://github.com/settings/tokens with 'repo' scope.");
        std::process::exit(1);
    });

    // Parse owner/repo from args or env
    let args: Vec<String> = std::env::args().collect();
    let repo_arg = args.get(1).cloned().or_else(|| std::env::var("GITHUB_REPO").ok());

    let (owner, repo) = match repo_arg {
        Some(ref r) if r.contains('/') => {
            let parts: Vec<&str> = r.splitn(2, '/').collect();
            (parts[0].to_string(), parts[1].to_string())
        }
        _ => {
            eprintln!("Usage: github_issues <owner/repo>");
            eprintln!("  e.g. github_issues rust-lang/rust");
            eprintln!("  or set GITHUB_REPO=owner/repo");
            std::process::exit(1);
        }
    };

    let model = std::env::var("AGENTIC_MODEL").unwrap_or_else(|_| "claude-sonnet-4-5-20250929".into());

    let gh = GitHubConfig::new(&gh_token, &owner, &repo);

    let provider = Arc::new(ClaudeProvider::new(&api_key, &model));
    let store = Arc::new(InMemoryStore::new());

    let mut tools = ToolRegistry::new();
    orra::tools::github::register_tools(&mut tools, &gh);

    let config = RuntimeConfig {
        system_prompt: Some(system_prompt(&owner, &repo)),
        max_turns: 5,
        max_tokens: Some(2048),
        temperature: Some(0.3),
        ..RuntimeConfig::default()
    };

    let runtime = Runtime::new(
        provider,
        store,
        tools,
        PolicyRegistry::default(),
        CharEstimator::default(),
        config,
    );

    let ns = Namespace::new("github").child(&format!("{}/{}", owner, repo));

    println!("=== GitHub Issues Assistant ===");
    println!("Repository: {}/{}", owner, repo);
    println!("Type your request, or /quit to exit.");
    println!();

    let stdin = io::stdin();
    loop {
        print!("> ");
        io::stdout().flush().unwrap();

        let mut input = String::new();
        if stdin.read_line(&mut input).unwrap() == 0 {
            break;
        }
        let input = input.trim();
        if input.is_empty() {
            continue;
        }
        if input == "/quit" || input == "/exit" {
            println!("Goodbye!");
            break;
        }

        match runtime.run(&ns, Message::user(input)).await {
            Ok(result) => {
                for turn in &result.turns {
                    for tc in &turn.response.message.tool_calls {
                        println!(
                            "  [{}({})]",
                            tc.name,
                            serde_json::to_string(&tc.arguments).unwrap_or_default()
                        );
                    }
                    for tr in &turn.tool_results {
                        if tr.is_error {
                            println!("  [tool error: {}]", tr.content);
                        }
                    }
                }
                println!();
                println!("{}", result.final_message.content);
                println!(
                    "  ({} input, {} output tokens)",
                    result.total_usage.input_tokens, result.total_usage.output_tokens
                );
                println!();
            }
            Err(e) => {
                eprintln!("Error: {}", e);
                println!();
            }
        }
    }
}