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