everruns_integrations_github/
lib.rs1mod 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}