Skip to main content

everruns_integrations_github/
lib.rs

1//! GitHub-backed blueprints for Everruns.
2//!
3//! This crate registers the `github_scout` capability through Everruns' inventory
4//! plugin system. The capability is blueprint-only: it does not add tools to a
5//! host agent, and instead contributes the `github_scout` agent blueprint.
6//!
7//! `github_scout` runs as a read-only child agent with private GitHub REST API
8//! tools for code search, file reads, and issue or pull request search. Tool
9//! credentials are resolved from the existing `github` user connection, with a
10//! `GITHUB_TOKEN` session secret fallback for local and compatibility flows.
11
12mod client;
13mod tools;
14
15use everruns_core::capabilities::{
16    AgentBlueprint, BlueprintModel, Capability, CapabilityStatus, IntegrationPlugin,
17};
18use everruns_core::tools::Tool;
19use serde_json::json;
20
21use tools::{ReadGitHubFileTool, SearchGitHubCodeTool, SearchGitHubIssuesTool};
22
23inventory::submit! {
24    IntegrationPlugin {
25        experimental_only: false,
26        feature_flag: None,
27        factory: || Box::new(GitHubScoutCapability),
28    }
29}
30
31pub const GITHUB_API_BASE: &str = "https://api.github.com";
32pub const GITHUB_CONNECTION_PROVIDER: &str = "github";
33pub const GITHUB_TOKEN_SECRET: &str = "GITHUB_TOKEN";
34
35pub struct GitHubScoutCapability;
36
37impl Capability for GitHubScoutCapability {
38    fn id(&self) -> &str {
39        "github_scout"
40    }
41
42    fn name(&self) -> &str {
43        "GitHub Scout"
44    }
45
46    fn description(&self) -> &str {
47        "Blueprint-only GitHub repository scout that can spawn read-only GitHub exploration subagents."
48    }
49
50    fn status(&self) -> CapabilityStatus {
51        CapabilityStatus::Available
52    }
53
54    fn icon(&self) -> Option<&str> {
55        Some("github")
56    }
57
58    fn category(&self) -> Option<&str> {
59        Some("Integrations")
60    }
61
62    fn tools(&self) -> Vec<Box<dyn Tool>> {
63        vec![]
64    }
65
66    fn dependencies(&self) -> Vec<&'static str> {
67        vec!["subagents"]
68    }
69
70    fn agent_blueprints(&self) -> Vec<AgentBlueprint> {
71        vec![AgentBlueprint {
72            id: "github_scout",
73            name: "GitHub Scout",
74            description: "Search GitHub repositories for code, files, issues, and pull requests. Fast read-only agent for codebase exploration and pattern discovery.",
75            model: BlueprintModel::Fixed("claude-haiku-4-5-20251001".to_string()),
76            system_prompt: GITHUB_SCOUT_PROMPT,
77            tools: vec![
78                Box::new(SearchGitHubCodeTool),
79                Box::new(ReadGitHubFileTool),
80                Box::new(SearchGitHubIssuesTool),
81            ],
82            max_turns: Some(15),
83            config_schema: Some(json!({
84                "type": "object",
85                "properties": {
86                    "repos": {
87                        "type": "array",
88                        "items": {
89                            "type": "string",
90                            "pattern": "^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$"
91                        },
92                        "description": "Repository list to scope searches, in owner/repo format."
93                    }
94                },
95                "additionalProperties": false
96            })),
97        }]
98    }
99}
100
101const GITHUB_SCOUT_PROMPT: &str = r#"You are GitHub Scout, a read-only repository exploration agent.
102
103Use your GitHub tools to find concrete code, files, issues, and pull requests relevant to the task. Prefer targeted searches over broad scans. When the host provides config.repos, scope searches to those repositories unless the task clearly asks otherwise.
104
105Return a concise summary with:
106- the answer or finding,
107- the most relevant file paths, symbols, issues, or pull requests,
108- direct URLs when useful,
109- any uncertainty or gaps."#;
110
111#[cfg(test)]
112mod tests {
113    use super::*;
114
115    #[test]
116    fn capability_is_blueprint_only() {
117        let cap = GitHubScoutCapability;
118        assert_eq!(cap.id(), "github_scout");
119        assert_eq!(cap.name(), "GitHub Scout");
120        assert!(cap.tools().is_empty());
121        assert_eq!(cap.dependencies(), vec!["subagents"]);
122    }
123
124    #[test]
125    fn contributes_github_scout_blueprint() {
126        let cap = GitHubScoutCapability;
127        let blueprints = cap.agent_blueprints();
128        assert_eq!(blueprints.len(), 1);
129
130        let scout = &blueprints[0];
131        assert_eq!(scout.id, "github_scout");
132        assert_eq!(scout.name, "GitHub Scout");
133        assert_eq!(scout.max_turns, Some(15));
134        assert!(matches!(
135            scout.model,
136            BlueprintModel::Fixed(ref model) if model == "claude-haiku-4-5-20251001"
137        ));
138
139        let tool_names: Vec<&str> = scout.tools.iter().map(|tool| tool.name()).collect();
140        assert_eq!(
141            tool_names,
142            vec![
143                "search_github_code",
144                "read_github_file",
145                "search_github_issues"
146            ]
147        );
148    }
149}