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