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, CapabilityLocalization, CapabilityStatus,
17    IntegrationPlugin,
18};
19use everruns_core::tools::Tool;
20use serde_json::json;
21
22use tools::{ReadGitHubFileTool, SearchGitHubCodeTool, SearchGitHubIssuesTool};
23
24inventory::submit! {
25    IntegrationPlugin {
26        experimental_only: false,
27        feature_flag: None,
28        factory: || Box::new(GitHubScoutCapability),
29    }
30}
31
32pub const GITHUB_API_BASE: &str = "https://api.github.com";
33pub const GITHUB_CONNECTION_PROVIDER: &str = "github";
34pub const GITHUB_TOKEN_SECRET: &str = "GITHUB_TOKEN";
35
36pub struct GitHubScoutCapability;
37
38impl Capability for GitHubScoutCapability {
39    fn id(&self) -> &str {
40        "github_scout"
41    }
42
43    fn name(&self) -> &str {
44        "GitHub Scout"
45    }
46
47    fn description(&self) -> &str {
48        "Blueprint-only GitHub repository scout that can spawn read-only GitHub exploration subagents."
49    }
50
51    fn status(&self) -> CapabilityStatus {
52        CapabilityStatus::Available
53    }
54
55    fn icon(&self) -> Option<&str> {
56        Some("github")
57    }
58
59    fn category(&self) -> Option<&str> {
60        Some("Integrations")
61    }
62
63    fn tools(&self) -> Vec<Box<dyn Tool>> {
64        vec![]
65    }
66
67    fn dependencies(&self) -> Vec<&'static str> {
68        vec!["subagents"]
69    }
70
71    fn localizations(&self) -> Vec<CapabilityLocalization> {
72        vec![CapabilityLocalization::text(
73            "uk",
74            "GitHub Scout",
75            "Розвідник репозиторіїв GitHub, що працює лише через blueprint і може породжувати \
76             субагентів для дослідження GitHub у режимі лише читання.",
77        )]
78    }
79
80    fn agent_blueprints(&self) -> Vec<AgentBlueprint> {
81        vec![AgentBlueprint {
82            id: "github_scout",
83            name: "GitHub Scout",
84            description: "Search GitHub repositories for code, files, issues, and pull requests. Fast read-only agent for codebase exploration and pattern discovery.",
85            model: BlueprintModel::Fixed("claude-haiku-4-5-20251001".to_string()),
86            system_prompt: GITHUB_SCOUT_PROMPT,
87            tools: vec![
88                Box::new(SearchGitHubCodeTool),
89                Box::new(ReadGitHubFileTool),
90                Box::new(SearchGitHubIssuesTool),
91            ],
92            max_turns: Some(15),
93            config_schema: Some(json!({
94                "type": "object",
95                "properties": {
96                    "repos": {
97                        "type": "array",
98                        "items": {
99                            "type": "string",
100                            "pattern": "^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$"
101                        },
102                        "description": "Repository list to scope searches, in owner/repo format."
103                    }
104                },
105                "additionalProperties": false
106            })),
107        }]
108    }
109}
110
111const GITHUB_SCOUT_PROMPT: &str = r#"You are GitHub Scout, a read-only repository exploration agent.
112
113Use 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.
114
115Return a concise summary with:
116- the answer or finding,
117- the most relevant file paths, symbols, issues, or pull requests,
118- direct URLs when useful,
119- any uncertainty or gaps."#;
120
121#[cfg(test)]
122mod tests {
123    use super::*;
124
125    #[test]
126    fn capability_is_blueprint_only() {
127        let cap = GitHubScoutCapability;
128        assert_eq!(cap.id(), "github_scout");
129        assert_eq!(cap.name(), "GitHub Scout");
130        assert!(cap.tools().is_empty());
131        assert_eq!(cap.dependencies(), vec!["subagents"]);
132    }
133
134    #[test]
135    fn uk_localization_resolves() {
136        let cap = GitHubScoutCapability;
137        assert_ne!(cap.localized_description(Some("uk")), cap.description());
138        assert_eq!(cap.localized_name(Some("uk")), "GitHub Scout");
139    }
140
141    #[test]
142    fn contributes_github_scout_blueprint() {
143        let cap = GitHubScoutCapability;
144        let blueprints = cap.agent_blueprints();
145        assert_eq!(blueprints.len(), 1);
146
147        let scout = &blueprints[0];
148        assert_eq!(scout.id, "github_scout");
149        assert_eq!(scout.name, "GitHub Scout");
150        assert_eq!(scout.max_turns, Some(15));
151        assert!(matches!(
152            scout.model,
153            BlueprintModel::Fixed(ref model) if model == "claude-haiku-4-5-20251001"
154        ));
155
156        let tool_names: Vec<&str> = scout.tools.iter().map(|tool| tool.name()).collect();
157        assert_eq!(
158            tool_names,
159            vec![
160                "search_github_code",
161                "read_github_file",
162                "search_github_issues"
163            ]
164        );
165    }
166}