neoagent 2.3.1-beta.2 → 2.3.1-beta.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (264) hide show
  1. package/.env.example +39 -0
  2. package/README.md +2 -0
  3. package/docs/capabilities.md +2 -2
  4. package/docs/configuration.md +13 -5
  5. package/docs/integrations.md +4 -1
  6. package/flutter_app/.metadata +42 -0
  7. package/flutter_app/README.md +21 -0
  8. package/flutter_app/analysis_options.yaml +32 -0
  9. package/flutter_app/android/app/build.gradle.kts +109 -0
  10. package/flutter_app/android/app/src/debug/AndroidManifest.xml +7 -0
  11. package/flutter_app/android/app/src/main/AndroidManifest.xml +147 -0
  12. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/MainActivity.kt +747 -0
  13. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/health/HealthConnectGateway.kt +280 -0
  14. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/health/HealthSyncNotifications.kt +113 -0
  15. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/health/HealthSyncPayload.kt +57 -0
  16. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/health/HealthSyncScheduler.kt +78 -0
  17. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/health/HealthSyncWorker.kt +253 -0
  18. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/health/PermissionsRationaleActivity.kt +46 -0
  19. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/recording/RecordingBootReceiver.kt +21 -0
  20. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/recording/RecordingForegroundService.kt +586 -0
  21. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/recording/RecordingStateStore.kt +78 -0
  22. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/recording/RecordingUploadClient.kt +104 -0
  23. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/widgets/AiHomeWidgetProvider.kt +457 -0
  24. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/widgets/AiWidgetStore.kt +194 -0
  25. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/widgets/VoiceLaunchWidgetProvider.kt +67 -0
  26. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/widgets/WidgetConfigActivity.kt +228 -0
  27. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/widgets/WidgetSyncScheduler.kt +72 -0
  28. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/widgets/WidgetSyncWorker.kt +186 -0
  29. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/widgets/WidgetTaskRunWorker.kt +210 -0
  30. package/flutter_app/android/app/src/main/res/drawable/launch_background.xml +12 -0
  31. package/flutter_app/android/app/src/main/res/drawable/neoagent_ai_widget_bg.xml +11 -0
  32. package/flutter_app/android/app/src/main/res/drawable/neoagent_ai_widget_task_bg.xml +8 -0
  33. package/flutter_app/android/app/src/main/res/drawable-v21/launch_background.xml +12 -0
  34. package/flutter_app/android/app/src/main/res/layout/neoagent_ai_widget.xml +138 -0
  35. package/flutter_app/android/app/src/main/res/layout/neoagent_ai_widget_task_row.xml +52 -0
  36. package/flutter_app/android/app/src/main/res/layout/neoagent_voice_widget.xml +49 -0
  37. package/flutter_app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png +0 -0
  38. package/flutter_app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png +0 -0
  39. package/flutter_app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png +0 -0
  40. package/flutter_app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png +0 -0
  41. package/flutter_app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
  42. package/flutter_app/android/app/src/main/res/values/strings.xml +12 -0
  43. package/flutter_app/android/app/src/main/res/values/styles.xml +18 -0
  44. package/flutter_app/android/app/src/main/res/values-night/styles.xml +18 -0
  45. package/flutter_app/android/app/src/main/res/xml/file_paths.xml +6 -0
  46. package/flutter_app/android/app/src/main/res/xml/neoagent_ai_widget_info.xml +12 -0
  47. package/flutter_app/android/app/src/main/res/xml/neoagent_voice_widget_info.xml +12 -0
  48. package/flutter_app/android/app/src/profile/AndroidManifest.xml +7 -0
  49. package/flutter_app/android/build.gradle.kts +24 -0
  50. package/flutter_app/android/ci-release.keystore +0 -0
  51. package/flutter_app/android/gradle/wrapper/gradle-wrapper.properties +5 -0
  52. package/flutter_app/android/gradle.properties +3 -0
  53. package/flutter_app/android/key.properties +4 -0
  54. package/flutter_app/android/settings.gradle.kts +26 -0
  55. package/flutter_app/assets/branding/app_icon_1024.png +0 -0
  56. package/flutter_app/assets/branding/app_icon_128.png +0 -0
  57. package/flutter_app/assets/branding/app_icon_192.png +0 -0
  58. package/flutter_app/assets/branding/app_icon_256.png +0 -0
  59. package/flutter_app/assets/branding/app_icon_32.png +0 -0
  60. package/flutter_app/assets/branding/app_icon_512.png +0 -0
  61. package/flutter_app/assets/branding/app_icon_64.png +0 -0
  62. package/flutter_app/assets/branding/tray_icon_template.png +0 -0
  63. package/flutter_app/lib/features/location/location_service.dart +119 -0
  64. package/flutter_app/lib/features/notifications/notification_interceptor.dart +97 -0
  65. package/flutter_app/lib/main.dart +23057 -0
  66. package/flutter_app/lib/main_app_shell.dart +1682 -0
  67. package/flutter_app/lib/main_integrations.dart +931 -0
  68. package/flutter_app/lib/main_launcher.dart +959 -0
  69. package/flutter_app/lib/main_launcher_entry.dart +5 -0
  70. package/flutter_app/lib/main_models.dart +3473 -0
  71. package/flutter_app/lib/main_shared.dart +2861 -0
  72. package/flutter_app/lib/main_theme.dart +204 -0
  73. package/flutter_app/lib/main_voice_assistant.dart +831 -0
  74. package/flutter_app/lib/src/android_apk_drop_zone.dart +32 -0
  75. package/flutter_app/lib/src/android_apk_drop_zone_stub.dart +16 -0
  76. package/flutter_app/lib/src/android_apk_drop_zone_web.dart +348 -0
  77. package/flutter_app/lib/src/android_app_installer.dart +22 -0
  78. package/flutter_app/lib/src/android_app_installer_io.dart +122 -0
  79. package/flutter_app/lib/src/android_app_installer_stub.dart +21 -0
  80. package/flutter_app/lib/src/android_launcher_bridge.dart +239 -0
  81. package/flutter_app/lib/src/app_launch_bridge.dart +29 -0
  82. package/flutter_app/lib/src/app_release_updater.dart +511 -0
  83. package/flutter_app/lib/src/backend_client.dart +1833 -0
  84. package/flutter_app/lib/src/desktop_companion.dart +2 -0
  85. package/flutter_app/lib/src/desktop_companion_actions.dart +586 -0
  86. package/flutter_app/lib/src/desktop_companion_io.dart +538 -0
  87. package/flutter_app/lib/src/desktop_companion_stub.dart +59 -0
  88. package/flutter_app/lib/src/desktop_native_bridge.dart +91 -0
  89. package/flutter_app/lib/src/desktop_screen_capture.dart +21 -0
  90. package/flutter_app/lib/src/desktop_screen_capture_io.dart +142 -0
  91. package/flutter_app/lib/src/desktop_screen_capture_stub.dart +12 -0
  92. package/flutter_app/lib/src/diagnostics_logger.dart +119 -0
  93. package/flutter_app/lib/src/health_bridge.dart +136 -0
  94. package/flutter_app/lib/src/live_voice_capture.dart +85 -0
  95. package/flutter_app/lib/src/messaging_access_summary.dart +46 -0
  96. package/flutter_app/lib/src/network/app_http_client.dart +53 -0
  97. package/flutter_app/lib/src/network/app_http_client_factory.dart +6 -0
  98. package/flutter_app/lib/src/network/app_http_client_io.dart +138 -0
  99. package/flutter_app/lib/src/network/app_http_client_stub.dart +3 -0
  100. package/flutter_app/lib/src/network/app_http_client_web.dart +94 -0
  101. package/flutter_app/lib/src/oauth_launcher.dart +33 -0
  102. package/flutter_app/lib/src/oauth_launcher_io.dart +77 -0
  103. package/flutter_app/lib/src/oauth_launcher_stub.dart +33 -0
  104. package/flutter_app/lib/src/oauth_launcher_web.dart +107 -0
  105. package/flutter_app/lib/src/recording_bridge.dart +232 -0
  106. package/flutter_app/lib/src/recording_bridge_io.dart +1019 -0
  107. package/flutter_app/lib/src/recording_bridge_stub.dart +120 -0
  108. package/flutter_app/lib/src/recording_bridge_web.dart +689 -0
  109. package/flutter_app/lib/src/recording_payloads.dart +86 -0
  110. package/flutter_app/lib/src/theme/palette.dart +81 -0
  111. package/flutter_app/lib/src/widget_bridge.dart +49 -0
  112. package/flutter_app/linux/CMakeLists.txt +128 -0
  113. package/flutter_app/linux/flutter/CMakeLists.txt +88 -0
  114. package/flutter_app/linux/flutter/generated_plugin_registrant.cc +43 -0
  115. package/flutter_app/linux/flutter/generated_plugin_registrant.h +15 -0
  116. package/flutter_app/linux/flutter/generated_plugins.cmake +31 -0
  117. package/flutter_app/linux/runner/CMakeLists.txt +26 -0
  118. package/flutter_app/linux/runner/main.cc +6 -0
  119. package/flutter_app/linux/runner/my_application.cc +144 -0
  120. package/flutter_app/linux/runner/my_application.h +18 -0
  121. package/flutter_app/linux/runner/resources/app_icon.png +0 -0
  122. package/flutter_app/macos/Flutter/Flutter-Debug.xcconfig +2 -0
  123. package/flutter_app/macos/Flutter/Flutter-Release.xcconfig +2 -0
  124. package/flutter_app/macos/Flutter/GeneratedPluginRegistrant.swift +40 -0
  125. package/flutter_app/macos/Podfile +42 -0
  126. package/flutter_app/macos/Podfile.lock +87 -0
  127. package/flutter_app/macos/Runner/AppDelegate.swift +576 -0
  128. package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +68 -0
  129. package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png +0 -0
  130. package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png +0 -0
  131. package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png +0 -0
  132. package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png +0 -0
  133. package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png +0 -0
  134. package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png +0 -0
  135. package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png +0 -0
  136. package/flutter_app/macos/Runner/Base.lproj/MainMenu.xib +342 -0
  137. package/flutter_app/macos/Runner/Configs/AppInfo.xcconfig +14 -0
  138. package/flutter_app/macos/Runner/Configs/Debug.xcconfig +2 -0
  139. package/flutter_app/macos/Runner/Configs/Release.xcconfig +2 -0
  140. package/flutter_app/macos/Runner/Configs/Warnings.xcconfig +13 -0
  141. package/flutter_app/macos/Runner/DebugProfile.entitlements +16 -0
  142. package/flutter_app/macos/Runner/Info.plist +36 -0
  143. package/flutter_app/macos/Runner/MainFlutterWindow.swift +19 -0
  144. package/flutter_app/macos/Runner/Release.entitlements +12 -0
  145. package/flutter_app/macos/Runner.xcodeproj/project.pbxproj +801 -0
  146. package/flutter_app/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
  147. package/flutter_app/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +99 -0
  148. package/flutter_app/macos/Runner.xcworkspace/contents.xcworkspacedata +10 -0
  149. package/flutter_app/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
  150. package/flutter_app/macos/RunnerTests/RunnerTests.swift +12 -0
  151. package/flutter_app/patch_strings.py +12 -0
  152. package/flutter_app/pubspec.lock +1088 -0
  153. package/flutter_app/pubspec.yaml +53 -0
  154. package/flutter_app/test/messaging_access_summary_test.dart +22 -0
  155. package/flutter_app/test/recording_payloads_test.dart +53 -0
  156. package/flutter_app/third_party/desktop_audio_capture/LICENSE +21 -0
  157. package/flutter_app/third_party/desktop_audio_capture/README.md +262 -0
  158. package/flutter_app/third_party/desktop_audio_capture/lib/audio_capture.dart +65 -0
  159. package/flutter_app/third_party/desktop_audio_capture/lib/config/mic_audio_config.dart +153 -0
  160. package/flutter_app/third_party/desktop_audio_capture/lib/config/system_adudio_config.dart +110 -0
  161. package/flutter_app/third_party/desktop_audio_capture/lib/mic/mic_audio_capture.dart +461 -0
  162. package/flutter_app/third_party/desktop_audio_capture/lib/model/audio_status.dart +91 -0
  163. package/flutter_app/third_party/desktop_audio_capture/lib/model/decibel_data.dart +106 -0
  164. package/flutter_app/third_party/desktop_audio_capture/lib/model/input_device_type.dart +219 -0
  165. package/flutter_app/third_party/desktop_audio_capture/lib/system/system_audio_capture.dart +336 -0
  166. package/flutter_app/third_party/desktop_audio_capture/linux/CMakeLists.txt +101 -0
  167. package/flutter_app/third_party/desktop_audio_capture/linux/audio_capture_plugin.cc +692 -0
  168. package/flutter_app/third_party/desktop_audio_capture/linux/include/audio_capture/audio_capture_plugin.h +35 -0
  169. package/flutter_app/third_party/desktop_audio_capture/linux/include/audio_capture/mic_capture_plugin.h +36 -0
  170. package/flutter_app/third_party/desktop_audio_capture/linux/include/desktop_audio_capture/audio_capture_plugin.h +32 -0
  171. package/flutter_app/third_party/desktop_audio_capture/linux/include/desktop_audio_capture/mic_capture_plugin.h +32 -0
  172. package/flutter_app/third_party/desktop_audio_capture/linux/mic_capture_plugin.cc +878 -0
  173. package/flutter_app/third_party/desktop_audio_capture/macos/Classes/AudioCapturePlugin.swift +27 -0
  174. package/flutter_app/third_party/desktop_audio_capture/macos/Classes/MicCapturePlugin.swift +1172 -0
  175. package/flutter_app/third_party/desktop_audio_capture/macos/Classes/SystemCapturePlugin.swift +655 -0
  176. package/flutter_app/third_party/desktop_audio_capture/macos/Resources/PrivacyInfo.xcprivacy +12 -0
  177. package/flutter_app/third_party/desktop_audio_capture/macos/desktop_audio_capture.podspec +30 -0
  178. package/flutter_app/third_party/desktop_audio_capture/pubspec.yaml +87 -0
  179. package/flutter_app/third_party/desktop_audio_capture/windows/CMakeLists.txt +105 -0
  180. package/flutter_app/third_party/desktop_audio_capture/windows/audio_capture_plugin.cpp +80 -0
  181. package/flutter_app/third_party/desktop_audio_capture/windows/audio_capture_plugin.h +31 -0
  182. package/flutter_app/third_party/desktop_audio_capture/windows/audio_capture_plugin_c_api.cpp +12 -0
  183. package/flutter_app/third_party/desktop_audio_capture/windows/include/audio_capture/audio_capture_plugin_c_api.h +23 -0
  184. package/flutter_app/third_party/desktop_audio_capture/windows/include/desktop_audio_capture/audio_capture_plugin.h +25 -0
  185. package/flutter_app/third_party/desktop_audio_capture/windows/mic_capture_plugin.cpp +1117 -0
  186. package/flutter_app/third_party/desktop_audio_capture/windows/mic_capture_plugin.h +115 -0
  187. package/flutter_app/third_party/desktop_audio_capture/windows/system_audio_capture_plugin.cpp +777 -0
  188. package/flutter_app/third_party/desktop_audio_capture/windows/system_audio_capture_plugin.h +87 -0
  189. package/flutter_app/third_party/flutter_secure_storage_linux/linux/CMakeLists.txt +30 -0
  190. package/flutter_app/third_party/flutter_secure_storage_linux/linux/flutter_secure_storage_linux_plugin.cc +215 -0
  191. package/flutter_app/third_party/flutter_secure_storage_linux/linux/include/flutter_secure_storage_linux/flutter_secure_storage_linux_plugin.h +27 -0
  192. package/flutter_app/third_party/flutter_secure_storage_linux/pubspec.yaml +20 -0
  193. package/flutter_app/tool/generate_desktop_branding.py +219 -0
  194. package/flutter_app/web/favicon.png +0 -0
  195. package/flutter_app/web/favicon.svg +12 -0
  196. package/flutter_app/web/icons/Icon-192.png +0 -0
  197. package/flutter_app/web/icons/Icon-512.png +0 -0
  198. package/flutter_app/web/icons/Icon-maskable-192.png +0 -0
  199. package/flutter_app/web/icons/Icon-maskable-512.png +0 -0
  200. package/flutter_app/web/index.html +39 -0
  201. package/flutter_app/web/manifest.json +35 -0
  202. package/flutter_app/windows/CMakeLists.txt +108 -0
  203. package/flutter_app/windows/flutter/CMakeLists.txt +109 -0
  204. package/flutter_app/windows/flutter/generated_plugin_registrant.cc +47 -0
  205. package/flutter_app/windows/flutter/generated_plugin_registrant.h +15 -0
  206. package/flutter_app/windows/flutter/generated_plugins.cmake +35 -0
  207. package/flutter_app/windows/runner/CMakeLists.txt +41 -0
  208. package/flutter_app/windows/runner/Runner.rc +121 -0
  209. package/flutter_app/windows/runner/flutter_window.cpp +533 -0
  210. package/flutter_app/windows/runner/flutter_window.h +37 -0
  211. package/flutter_app/windows/runner/main.cpp +53 -0
  212. package/flutter_app/windows/runner/resource.h +16 -0
  213. package/flutter_app/windows/runner/resources/app_icon.ico +0 -0
  214. package/flutter_app/windows/runner/runner.exe.manifest +14 -0
  215. package/flutter_app/windows/runner/utils.cpp +65 -0
  216. package/flutter_app/windows/runner/utils.h +19 -0
  217. package/flutter_app/windows/runner/win32_window.cpp +299 -0
  218. package/flutter_app/windows/runner/win32_window.h +102 -0
  219. package/lib/manager.js +231 -7
  220. package/package.json +3 -1
  221. package/server/db/database.js +68 -0
  222. package/server/http/middleware.js +50 -0
  223. package/server/http/routes.js +3 -1
  224. package/server/index.js +1 -0
  225. package/server/public/.last_build_id +1 -1
  226. package/server/public/assets/NOTICES +61 -0
  227. package/server/public/assets/fonts/MaterialIcons-Regular.otf +0 -0
  228. package/server/public/flutter_bootstrap.js +1 -1
  229. package/server/public/main.dart.js +65262 -64422
  230. package/server/routes/integrations.js +86 -0
  231. package/server/routes/memory.js +11 -2
  232. package/server/routes/screenHistory.js +46 -0
  233. package/server/routes/triggers.js +81 -0
  234. package/server/services/ai/models.js +30 -0
  235. package/server/services/ai/providers/githubCopilot.js +97 -0
  236. package/server/services/ai/providers/openai.js +2 -1
  237. package/server/services/ai/providers/openaiCodex.js +31 -0
  238. package/server/services/ai/settings.js +20 -0
  239. package/server/services/ai/systemPrompt.js +1 -1
  240. package/server/services/ai/tools.js +35 -6
  241. package/server/services/browser/controller.js +47 -3
  242. package/server/services/desktop/screenRecorder.js +172 -0
  243. package/server/services/integrations/env.js +5 -0
  244. package/server/services/integrations/github/common.js +106 -0
  245. package/server/services/integrations/github/provider.js +499 -0
  246. package/server/services/integrations/github/repos.js +1124 -0
  247. package/server/services/integrations/home_assistant/provider.js +306 -26
  248. package/server/services/integrations/manager.js +63 -7
  249. package/server/services/integrations/oauth_provider.js +13 -6
  250. package/server/services/integrations/provider_config_store.js +76 -0
  251. package/server/services/integrations/registry.js +4 -0
  252. package/server/services/integrations/trello/provider.js +744 -0
  253. package/server/services/integrations/whatsapp/provider.js +6 -2
  254. package/server/services/manager.js +22 -0
  255. package/server/services/memory/manager.js +39 -2
  256. package/server/services/skills/base_catalog.js +33 -0
  257. package/server/services/tasks/adapters/index.js +1 -0
  258. package/server/services/tasks/adapters/manual.js +12 -0
  259. package/server/services/tasks/runtime.js +1 -1
  260. package/server/services/voice/openaiClient.js +4 -1
  261. package/server/services/voice/providers.js +2 -1
  262. package/server/services/widgets/service.js +49 -4
  263. package/server/utils/local_secrets.js +56 -0
  264. package/server/utils/logger.js +37 -9
@@ -0,0 +1,1124 @@
1
+ 'use strict';
2
+
3
+ const {
4
+ githubApiRequest,
5
+ buildPaginationParams,
6
+ parseOwnerRepo,
7
+ } = require('./common');
8
+
9
+ const ISSUE_STATES = ['open', 'closed', 'all'];
10
+ const PR_STATES = ['open', 'closed', 'all'];
11
+ const SORT_OPTIONS = ['created', 'updated', 'comments'];
12
+ const DIRECTION_OPTIONS = ['asc', 'desc'];
13
+ const DEFAULT_GITHUB_API_HOST = 'api.github.com';
14
+
15
+ function parsePositiveInt(value, fallback) {
16
+ const parsed = Number(value);
17
+ if (!Number.isFinite(parsed) || parsed <= 0) return fallback;
18
+ return Math.floor(parsed);
19
+ }
20
+
21
+ function getAllowedGithubApiHosts() {
22
+ const envHosts = String(process.env.GITHUB_ALLOWED_API_HOSTS || '')
23
+ .split(',')
24
+ .map((host) => host.trim().toLowerCase())
25
+ .filter(Boolean);
26
+ return new Set([DEFAULT_GITHUB_API_HOST, ...envHosts]);
27
+ }
28
+
29
+ function isAllowedGithubApiHost(hostname) {
30
+ const host = String(hostname || '').trim().toLowerCase();
31
+ if (!host) return false;
32
+ return getAllowedGithubApiHosts().has(host);
33
+ }
34
+
35
+ const githubToolDefinitions = [
36
+ {
37
+ name: 'github_search_repos',
38
+ access: 'read',
39
+ description: 'Search GitHub repositories by keyword.',
40
+ parameters: {
41
+ type: 'object',
42
+ properties: {
43
+ query: {
44
+ type: 'string',
45
+ description: 'Search query (e.g. "language:javascript stars:>100").',
46
+ },
47
+ max_results: {
48
+ type: 'number',
49
+ description: 'Maximum number of results (default 10, max 100).',
50
+ },
51
+ },
52
+ required: ['query'],
53
+ },
54
+ },
55
+ {
56
+ name: 'github_get_repo',
57
+ access: 'read',
58
+ description: 'Get details about a specific GitHub repository.',
59
+ parameters: {
60
+ type: 'object',
61
+ properties: {
62
+ owner_repo: {
63
+ type: 'string',
64
+ description: 'Repository in format "owner/repo".',
65
+ },
66
+ },
67
+ required: ['owner_repo'],
68
+ },
69
+ },
70
+ {
71
+ name: 'github_list_issues',
72
+ access: 'read',
73
+ description: 'List issues in a GitHub repository.',
74
+ parameters: {
75
+ type: 'object',
76
+ properties: {
77
+ owner_repo: {
78
+ type: 'string',
79
+ description: 'Repository in format "owner/repo".',
80
+ },
81
+ state: {
82
+ type: 'string',
83
+ enum: ISSUE_STATES,
84
+ description: 'Issue state filter (default open).',
85
+ },
86
+ labels: {
87
+ type: 'string',
88
+ description: 'Comma-separated label names to filter by.',
89
+ },
90
+ assignee: {
91
+ type: 'string',
92
+ description: 'Filter by assignee username (use @me for self).',
93
+ },
94
+ sort: {
95
+ type: 'string',
96
+ enum: SORT_OPTIONS,
97
+ description: 'Sort field (default created).',
98
+ },
99
+ direction: {
100
+ type: 'string',
101
+ enum: DIRECTION_OPTIONS,
102
+ description: 'Sort direction (default desc).',
103
+ },
104
+ max_results: {
105
+ type: 'number',
106
+ description: 'Maximum number of results (default 30).',
107
+ },
108
+ },
109
+ required: ['owner_repo'],
110
+ },
111
+ },
112
+ {
113
+ name: 'github_get_issue',
114
+ access: 'read',
115
+ description: 'Get details about a specific GitHub issue.',
116
+ parameters: {
117
+ type: 'object',
118
+ properties: {
119
+ owner_repo: {
120
+ type: 'string',
121
+ description: 'Repository in format "owner/repo".',
122
+ },
123
+ issue_number: {
124
+ type: 'number',
125
+ description: 'Issue number.',
126
+ },
127
+ },
128
+ required: ['owner_repo', 'issue_number'],
129
+ },
130
+ },
131
+ {
132
+ name: 'github_create_issue',
133
+ access: 'write',
134
+ description: 'Create a new GitHub issue.',
135
+ parameters: {
136
+ type: 'object',
137
+ properties: {
138
+ owner_repo: {
139
+ type: 'string',
140
+ description: 'Repository in format "owner/repo".',
141
+ },
142
+ title: {
143
+ type: 'string',
144
+ description: 'Issue title.',
145
+ },
146
+ body: {
147
+ type: 'string',
148
+ description: 'Issue body/description.',
149
+ },
150
+ labels: {
151
+ type: 'string',
152
+ description: 'Comma-separated label names.',
153
+ },
154
+ assignees: {
155
+ type: 'string',
156
+ description: 'Comma-separated usernames to assign.',
157
+ },
158
+ },
159
+ required: ['owner_repo', 'title'],
160
+ },
161
+ },
162
+ {
163
+ name: 'github_update_issue',
164
+ access: 'write',
165
+ description: 'Update an existing GitHub issue.',
166
+ parameters: {
167
+ type: 'object',
168
+ properties: {
169
+ owner_repo: {
170
+ type: 'string',
171
+ description: 'Repository in format "owner/repo".',
172
+ },
173
+ issue_number: {
174
+ type: 'number',
175
+ description: 'Issue number.',
176
+ },
177
+ title: {
178
+ type: 'string',
179
+ description: 'New issue title.',
180
+ },
181
+ body: {
182
+ type: 'string',
183
+ description: 'New issue body.',
184
+ },
185
+ state: {
186
+ type: 'string',
187
+ enum: ['open', 'closed'],
188
+ description: 'New issue state.',
189
+ },
190
+ labels: {
191
+ type: 'string',
192
+ description: 'Comma-separated label names (replaces existing).',
193
+ },
194
+ assignees: {
195
+ type: 'string',
196
+ description: 'Comma-separated usernames (replaces existing).',
197
+ },
198
+ },
199
+ required: ['owner_repo', 'issue_number'],
200
+ },
201
+ },
202
+ {
203
+ name: 'github_add_issue_labels',
204
+ access: 'write',
205
+ description: 'Add labels to a GitHub issue.',
206
+ parameters: {
207
+ type: 'object',
208
+ properties: {
209
+ owner_repo: {
210
+ type: 'string',
211
+ description: 'Repository in format "owner/repo".',
212
+ },
213
+ issue_number: {
214
+ type: 'number',
215
+ description: 'Issue number.',
216
+ },
217
+ labels: {
218
+ type: 'string',
219
+ description: 'Comma-separated label names to add.',
220
+ },
221
+ },
222
+ required: ['owner_repo', 'issue_number', 'labels'],
223
+ },
224
+ },
225
+ {
226
+ name: 'github_add_issue_assignees',
227
+ access: 'write',
228
+ description: 'Add assignees to a GitHub issue.',
229
+ parameters: {
230
+ type: 'object',
231
+ properties: {
232
+ owner_repo: {
233
+ type: 'string',
234
+ description: 'Repository in format "owner/repo".',
235
+ },
236
+ issue_number: {
237
+ type: 'number',
238
+ description: 'Issue number.',
239
+ },
240
+ assignees: {
241
+ type: 'string',
242
+ description: 'Comma-separated usernames to add.',
243
+ },
244
+ },
245
+ required: ['owner_repo', 'issue_number', 'assignees'],
246
+ },
247
+ },
248
+ {
249
+ name: 'github_list_prs',
250
+ access: 'read',
251
+ description: 'List pull requests in a GitHub repository.',
252
+ parameters: {
253
+ type: 'object',
254
+ properties: {
255
+ owner_repo: {
256
+ type: 'string',
257
+ description: 'Repository in format "owner/repo".',
258
+ },
259
+ state: {
260
+ type: 'string',
261
+ enum: PR_STATES,
262
+ description: 'PR state filter (default open).',
263
+ },
264
+ sort: {
265
+ type: 'string',
266
+ enum: SORT_OPTIONS,
267
+ description: 'Sort field (default created).',
268
+ },
269
+ direction: {
270
+ type: 'string',
271
+ enum: DIRECTION_OPTIONS,
272
+ description: 'Sort direction (default desc).',
273
+ },
274
+ max_results: {
275
+ type: 'number',
276
+ description: 'Maximum number of results (default 30).',
277
+ },
278
+ },
279
+ required: ['owner_repo'],
280
+ },
281
+ },
282
+ {
283
+ name: 'github_get_pr',
284
+ access: 'read',
285
+ description: 'Get details about a specific pull request.',
286
+ parameters: {
287
+ type: 'object',
288
+ properties: {
289
+ owner_repo: {
290
+ type: 'string',
291
+ description: 'Repository in format "owner/repo".',
292
+ },
293
+ pr_number: {
294
+ type: 'number',
295
+ description: 'Pull request number.',
296
+ },
297
+ },
298
+ required: ['owner_repo', 'pr_number'],
299
+ },
300
+ },
301
+ {
302
+ name: 'github_create_pr',
303
+ access: 'write',
304
+ description: 'Create a new pull request.',
305
+ parameters: {
306
+ type: 'object',
307
+ properties: {
308
+ owner_repo: {
309
+ type: 'string',
310
+ description: 'Repository in format "owner/repo".',
311
+ },
312
+ title: {
313
+ type: 'string',
314
+ description: 'PR title.',
315
+ },
316
+ body: {
317
+ type: 'string',
318
+ description: 'PR body/description.',
319
+ },
320
+ head: {
321
+ type: 'string',
322
+ description: 'Branch name containing the changes.',
323
+ },
324
+ base: {
325
+ type: 'string',
326
+ description: 'Base branch to merge into (default main).',
327
+ },
328
+ draft: {
329
+ type: 'boolean',
330
+ description: 'Create as draft PR.',
331
+ },
332
+ maintainer_can_modify: {
333
+ type: 'boolean',
334
+ description: 'Allow maintainers to push to your branch.',
335
+ },
336
+ },
337
+ required: ['owner_repo', 'title', 'head'],
338
+ },
339
+ },
340
+ {
341
+ name: 'github_update_pr',
342
+ access: 'write',
343
+ description: 'Update an existing pull request.',
344
+ parameters: {
345
+ type: 'object',
346
+ properties: {
347
+ owner_repo: {
348
+ type: 'string',
349
+ description: 'Repository in format "owner/repo".',
350
+ },
351
+ pr_number: {
352
+ type: 'number',
353
+ description: 'Pull request number.',
354
+ },
355
+ title: {
356
+ type: 'string',
357
+ description: 'New PR title.',
358
+ },
359
+ body: {
360
+ type: 'string',
361
+ description: 'New PR body.',
362
+ },
363
+ state: {
364
+ type: 'string',
365
+ enum: ['open', 'closed'],
366
+ description: 'New PR state.',
367
+ },
368
+ },
369
+ required: ['owner_repo', 'pr_number'],
370
+ },
371
+ },
372
+ {
373
+ name: 'github_merge_pr',
374
+ access: 'write',
375
+ description: 'Merge a pull request.',
376
+ parameters: {
377
+ type: 'object',
378
+ properties: {
379
+ owner_repo: {
380
+ type: 'string',
381
+ description: 'Repository in format "owner/repo".',
382
+ },
383
+ pr_number: {
384
+ type: 'number',
385
+ description: 'Pull request number.',
386
+ },
387
+ merge_method: {
388
+ type: 'string',
389
+ enum: ['squash', 'merge', 'rebase'],
390
+ description: 'Merge method (default squash).',
391
+ },
392
+ commit_title: {
393
+ type: 'string',
394
+ description: 'Custom commit title.',
395
+ },
396
+ commit_message: {
397
+ type: 'string',
398
+ description: 'Custom commit message.',
399
+ },
400
+ },
401
+ required: ['owner_repo', 'pr_number'],
402
+ },
403
+ },
404
+ {
405
+ name: 'github_list_commits',
406
+ access: 'read',
407
+ description: 'List commits in a repository or for a PR.',
408
+ parameters: {
409
+ type: 'object',
410
+ properties: {
411
+ owner_repo: {
412
+ type: 'string',
413
+ description: 'Repository in format "owner/repo".',
414
+ },
415
+ sha: {
416
+ type: 'string',
417
+ description: 'SHA or branch to list commits from (default HEAD).',
418
+ },
419
+ path: {
420
+ type: 'string',
421
+ description: 'Filter commits affecting a specific path.',
422
+ },
423
+ max_results: {
424
+ type: 'number',
425
+ description: 'Maximum number of results (default 30).',
426
+ },
427
+ },
428
+ required: ['owner_repo'],
429
+ },
430
+ },
431
+ {
432
+ name: 'github_list_branches',
433
+ access: 'read',
434
+ description: 'List branches in a GitHub repository.',
435
+ parameters: {
436
+ type: 'object',
437
+ properties: {
438
+ owner_repo: {
439
+ type: 'string',
440
+ description: 'Repository in format "owner/repo".',
441
+ },
442
+ max_results: {
443
+ type: 'number',
444
+ description: 'Maximum number of results (default 30).',
445
+ },
446
+ },
447
+ required: ['owner_repo'],
448
+ },
449
+ },
450
+ {
451
+ name: 'github_get_branch',
452
+ access: 'read',
453
+ description: 'Get details about a specific branch.',
454
+ parameters: {
455
+ type: 'object',
456
+ properties: {
457
+ owner_repo: {
458
+ type: 'string',
459
+ description: 'Repository in format "owner/repo".',
460
+ },
461
+ branch: {
462
+ type: 'string',
463
+ description: 'Branch name.',
464
+ },
465
+ },
466
+ required: ['owner_repo', 'branch'],
467
+ },
468
+ },
469
+ {
470
+ name: 'github_list_collaborators',
471
+ access: 'read',
472
+ description: 'List repository collaborators.',
473
+ parameters: {
474
+ type: 'object',
475
+ properties: {
476
+ owner_repo: {
477
+ type: 'string',
478
+ description: 'Repository in format "owner/repo".',
479
+ },
480
+ affiliation: {
481
+ type: 'string',
482
+ enum: ['all', 'outside', 'direct'],
483
+ description: 'Filter by affiliation (default all).',
484
+ },
485
+ max_results: {
486
+ type: 'number',
487
+ description: 'Maximum number of results (default 30).',
488
+ },
489
+ },
490
+ required: ['owner_repo'],
491
+ },
492
+ },
493
+ {
494
+ name: 'github_get_content',
495
+ access: 'read',
496
+ description: 'Get file or directory contents from a repository.',
497
+ parameters: {
498
+ type: 'object',
499
+ properties: {
500
+ owner_repo: {
501
+ type: 'string',
502
+ description: 'Repository in format "owner/repo".',
503
+ },
504
+ path: {
505
+ type: 'string',
506
+ description: 'File or directory path.',
507
+ },
508
+ ref: {
509
+ type: 'string',
510
+ description: 'Git ref (branch, tag, or SHA).',
511
+ },
512
+ },
513
+ required: ['owner_repo', 'path'],
514
+ },
515
+ },
516
+ {
517
+ name: 'github_create_or_update_file',
518
+ access: 'write',
519
+ description: 'Create or update a single file in a repository.',
520
+ parameters: {
521
+ type: 'object',
522
+ properties: {
523
+ owner_repo: {
524
+ type: 'string',
525
+ description: 'Repository in format "owner/repo".',
526
+ },
527
+ path: {
528
+ type: 'string',
529
+ description: 'File path in the repository.',
530
+ },
531
+ message: {
532
+ type: 'string',
533
+ description: 'Commit message.',
534
+ },
535
+ content: {
536
+ type: 'string',
537
+ description: 'Base64-encoded file content.',
538
+ },
539
+ sha: {
540
+ type: 'string',
541
+ description: 'SHA of file being replaced (required for updates).',
542
+ },
543
+ branch: {
544
+ type: 'string',
545
+ description: 'Branch to commit to (default main).',
546
+ },
547
+ },
548
+ required: ['owner_repo', 'path', 'message', 'content'],
549
+ },
550
+ },
551
+ {
552
+ name: 'github_delete_file',
553
+ access: 'write',
554
+ description: 'Delete a file from a repository.',
555
+ parameters: {
556
+ type: 'object',
557
+ properties: {
558
+ owner_repo: {
559
+ type: 'string',
560
+ description: 'Repository in format "owner/repo".',
561
+ },
562
+ path: {
563
+ type: 'string',
564
+ description: 'File path to delete.',
565
+ },
566
+ message: {
567
+ type: 'string',
568
+ description: 'Commit message.',
569
+ },
570
+ sha: {
571
+ type: 'string',
572
+ description: 'SHA of file to delete.',
573
+ },
574
+ branch: {
575
+ type: 'string',
576
+ description: 'Branch to delete from (default main).',
577
+ },
578
+ },
579
+ required: ['owner_repo', 'path', 'message', 'sha'],
580
+ },
581
+ },
582
+ {
583
+ name: 'github_list_workflow_runs',
584
+ access: 'read',
585
+ description: 'List GitHub Actions workflow runs.',
586
+ parameters: {
587
+ type: 'object',
588
+ properties: {
589
+ owner_repo: {
590
+ type: 'string',
591
+ description: 'Repository in format "owner/repo".',
592
+ },
593
+ workflow_id: {
594
+ type: 'string',
595
+ description: 'Workflow ID or filename (omit for all workflows).',
596
+ },
597
+ branch: {
598
+ type: 'string',
599
+ description: 'Filter by branch.',
600
+ },
601
+ status: {
602
+ type: 'string',
603
+ enum: ['queued', 'in_progress', 'completed', 'success', 'failure', 'cancelled', 'neutral', 'skipped', 'timed_out', 'action_required', 'all'],
604
+ description: 'Filter by status.',
605
+ },
606
+ max_results: {
607
+ type: 'number',
608
+ description: 'Maximum number of results (default 30).',
609
+ },
610
+ },
611
+ required: ['owner_repo'],
612
+ },
613
+ },
614
+ {
615
+ name: 'github_get_workflow_run',
616
+ access: 'read',
617
+ description: 'Get details about a specific workflow run.',
618
+ parameters: {
619
+ type: 'object',
620
+ properties: {
621
+ owner_repo: {
622
+ type: 'string',
623
+ description: 'Repository in format "owner/repo".',
624
+ },
625
+ run_id: {
626
+ type: 'number',
627
+ description: 'Run ID.',
628
+ },
629
+ },
630
+ required: ['owner_repo', 'run_id'],
631
+ },
632
+ },
633
+ {
634
+ name: 'github_trigger_workflow',
635
+ access: 'write',
636
+ description: 'Trigger a GitHub Actions workflow.',
637
+ parameters: {
638
+ type: 'object',
639
+ properties: {
640
+ owner_repo: {
641
+ type: 'string',
642
+ description: 'Repository in format "owner/repo".',
643
+ },
644
+ workflow_id: {
645
+ type: 'string',
646
+ description: 'Workflow ID or filename.',
647
+ },
648
+ ref: {
649
+ type: 'string',
650
+ description: 'Git ref to trigger (branch, tag, or SHA).',
651
+ },
652
+ inputs: {
653
+ type: 'string',
654
+ description: 'JSON string of workflow inputs.',
655
+ },
656
+ },
657
+ required: ['owner_repo', 'workflow_id'],
658
+ },
659
+ },
660
+ {
661
+ name: 'github_list_workflows',
662
+ access: 'read',
663
+ description: 'List GitHub Actions workflows in a repository.',
664
+ parameters: {
665
+ type: 'object',
666
+ properties: {
667
+ owner_repo: {
668
+ type: 'string',
669
+ description: 'Repository in format "owner/repo".',
670
+ },
671
+ },
672
+ required: ['owner_repo'],
673
+ },
674
+ },
675
+ {
676
+ name: 'github_get_auth_user',
677
+ access: 'read',
678
+ description: 'Get the authenticated GitHub user information.',
679
+ parameters: {
680
+ type: 'object',
681
+ properties: {},
682
+ },
683
+ },
684
+ {
685
+ name: 'github_list_user_repos',
686
+ access: 'read',
687
+ description: 'List repositories for the authenticated user or a specific user.',
688
+ parameters: {
689
+ type: 'object',
690
+ properties: {
691
+ username: {
692
+ type: 'string',
693
+ description: 'Username to list repos for (omit for authenticated user).',
694
+ },
695
+ visibility: {
696
+ type: 'string',
697
+ enum: ['all', 'public', 'private'],
698
+ description: 'Filter by visibility.',
699
+ },
700
+ sort: {
701
+ type: 'string',
702
+ enum: ['created', 'updated', 'pushed', 'full_name'],
703
+ description: 'Sort field (default full_name).',
704
+ },
705
+ direction: {
706
+ type: 'string',
707
+ enum: DIRECTION_OPTIONS,
708
+ description: 'Sort direction (default asc).',
709
+ },
710
+ max_results: {
711
+ type: 'number',
712
+ description: 'Maximum number of results (default 30).',
713
+ },
714
+ },
715
+ },
716
+ },
717
+ {
718
+ name: 'github_api_request',
719
+ access: 'dynamic_http_method',
720
+ description: 'Make an authenticated GitHub API request for advanced operations not covered by dedicated tools.',
721
+ parameters: {
722
+ type: 'object',
723
+ properties: {
724
+ method: {
725
+ type: 'string',
726
+ enum: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'],
727
+ description: 'HTTP method.',
728
+ },
729
+ path: {
730
+ type: 'string',
731
+ description: 'API path or full URL.',
732
+ },
733
+ query: {
734
+ type: 'object',
735
+ description: 'Optional query parameters.',
736
+ },
737
+ body: {
738
+ type: 'object',
739
+ description: 'Optional JSON request body.',
740
+ },
741
+ },
742
+ required: ['method', 'path'],
743
+ },
744
+ },
745
+ ];
746
+
747
+ function parseCommaSeparatedList(value) {
748
+ if (!value) return [];
749
+ return String(value).split(',').map((s) => s.trim()).filter(Boolean);
750
+ }
751
+
752
+ async function executeGithubTool(toolName, args, auth) {
753
+ switch (toolName) {
754
+ case 'github_get_auth_user': {
755
+ return await githubApiRequest(auth, {
756
+ path: '/user',
757
+ });
758
+ }
759
+
760
+ case 'github_search_repos': {
761
+ return await githubApiRequest(auth, {
762
+ path: '/search/repositories',
763
+ query: {
764
+ q: String(args.query || ''),
765
+ per_page: Math.min(Number(args.max_results) || 10, 100),
766
+ },
767
+ });
768
+ }
769
+
770
+ case 'github_get_repo': {
771
+ const { owner, repo } = parseOwnerRepo(args.owner_repo);
772
+ return await githubApiRequest(auth, {
773
+ path: `/repos/${owner}/${repo}`,
774
+ });
775
+ }
776
+
777
+ case 'github_list_issues': {
778
+ const { owner, repo } = parseOwnerRepo(args.owner_repo);
779
+ const query = { per_page: Math.min(Number(args.max_results) || 30, 100) };
780
+ if (args.state) query.state = args.state;
781
+ if (args.labels) query.labels = args.labels;
782
+ if (args.assignee) query.assignee = args.assignee;
783
+ if (args.sort) query.sort = args.sort;
784
+ if (args.direction) query.direction = args.direction;
785
+ return await githubApiRequest(auth, {
786
+ path: `/repos/${owner}/${repo}/issues`,
787
+ query,
788
+ });
789
+ }
790
+
791
+ case 'github_get_issue': {
792
+ const { owner, repo } = parseOwnerRepo(args.owner_repo);
793
+ return await githubApiRequest(auth, {
794
+ path: `/repos/${owner}/${repo}/issues/${Number(args.issue_number)}`,
795
+ });
796
+ }
797
+
798
+ case 'github_create_issue': {
799
+ const { owner, repo } = parseOwnerRepo(args.owner_repo);
800
+ return await githubApiRequest(auth, {
801
+ method: 'POST',
802
+ path: `/repos/${owner}/${repo}/issues`,
803
+ body: {
804
+ title: String(args.title || ''),
805
+ body: args.body ? String(args.body) : undefined,
806
+ labels: parseCommaSeparatedList(args.labels),
807
+ assignees: parseCommaSeparatedList(args.assignees),
808
+ },
809
+ });
810
+ }
811
+
812
+ case 'github_update_issue': {
813
+ const { owner, repo } = parseOwnerRepo(args.owner_repo);
814
+ const body = {};
815
+ if (args.title) body.title = String(args.title);
816
+ if (args.body !== undefined) body.body = String(args.body);
817
+ if (args.state) body.state = args.state;
818
+ if (args.labels) body.labels = parseCommaSeparatedList(args.labels);
819
+ if (args.assignees) body.assignees = parseCommaSeparatedList(args.assignees);
820
+ return await githubApiRequest(auth, {
821
+ method: 'PATCH',
822
+ path: `/repos/${owner}/${repo}/issues/${Number(args.issue_number)}`,
823
+ body,
824
+ });
825
+ }
826
+
827
+ case 'github_add_issue_labels': {
828
+ const { owner, repo } = parseOwnerRepo(args.owner_repo);
829
+ return await githubApiRequest(auth, {
830
+ method: 'POST',
831
+ path: `/repos/${owner}/${repo}/issues/${Number(args.issue_number)}/labels`,
832
+ body: {
833
+ labels: parseCommaSeparatedList(args.labels),
834
+ },
835
+ });
836
+ }
837
+
838
+ case 'github_add_issue_assignees': {
839
+ const { owner, repo } = parseOwnerRepo(args.owner_repo);
840
+ return await githubApiRequest(auth, {
841
+ method: 'POST',
842
+ path: `/repos/${owner}/${repo}/issues/${Number(args.issue_number)}/assignees`,
843
+ body: {
844
+ assignees: parseCommaSeparatedList(args.assignees),
845
+ },
846
+ });
847
+ }
848
+
849
+ case 'github_list_prs': {
850
+ const { owner, repo } = parseOwnerRepo(args.owner_repo);
851
+ const query = { per_page: Math.min(Number(args.max_results) || 30, 100) };
852
+ if (args.state) query.state = args.state;
853
+ if (args.sort) query.sort = args.sort;
854
+ if (args.direction) query.direction = args.direction;
855
+ return await githubApiRequest(auth, {
856
+ path: `/repos/${owner}/${repo}/pulls`,
857
+ query,
858
+ });
859
+ }
860
+
861
+ case 'github_get_pr': {
862
+ const { owner, repo } = parseOwnerRepo(args.owner_repo);
863
+ return await githubApiRequest(auth, {
864
+ path: `/repos/${owner}/${repo}/pulls/${Number(args.pr_number)}`,
865
+ });
866
+ }
867
+
868
+ case 'github_create_pr': {
869
+ const { owner, repo } = parseOwnerRepo(args.owner_repo);
870
+ return await githubApiRequest(auth, {
871
+ method: 'POST',
872
+ path: `/repos/${owner}/${repo}/pulls`,
873
+ body: {
874
+ title: String(args.title || ''),
875
+ body: args.body ? String(args.body) : undefined,
876
+ head: String(args.head || ''),
877
+ base: args.base ? String(args.base) : 'main',
878
+ draft: args.draft === true,
879
+ maintainer_can_modify: args.maintainer_can_modify === true,
880
+ },
881
+ });
882
+ }
883
+
884
+ case 'github_update_pr': {
885
+ const { owner, repo } = parseOwnerRepo(args.owner_repo);
886
+ const body = {};
887
+ if (args.title) body.title = String(args.title);
888
+ if (args.body !== undefined) body.body = String(args.body);
889
+ if (args.state) body.state = args.state;
890
+ return await githubApiRequest(auth, {
891
+ method: 'PATCH',
892
+ path: `/repos/${owner}/${repo}/pulls/${Number(args.pr_number)}`,
893
+ body,
894
+ });
895
+ }
896
+
897
+ case 'github_merge_pr': {
898
+ const { owner, repo } = parseOwnerRepo(args.owner_repo);
899
+ return await githubApiRequest(auth, {
900
+ method: 'PUT',
901
+ path: `/repos/${owner}/${repo}/pulls/${Number(args.pr_number)}/merge`,
902
+ body: {
903
+ merge_method: args.merge_method || 'squash',
904
+ commit_title: args.commit_title ? String(args.commit_title) : undefined,
905
+ commit_message: args.commit_message ? String(args.commit_message) : undefined,
906
+ },
907
+ });
908
+ }
909
+
910
+ case 'github_list_commits': {
911
+ const { owner, repo } = parseOwnerRepo(args.owner_repo);
912
+ const query = { per_page: Math.min(Number(args.max_results) || 30, 100) };
913
+ if (args.sha) query.sha = String(args.sha);
914
+ if (args.path) query.path = String(args.path);
915
+ return await githubApiRequest(auth, {
916
+ path: `/repos/${owner}/${repo}/commits`,
917
+ query,
918
+ });
919
+ }
920
+
921
+ case 'github_list_branches': {
922
+ const { owner, repo } = parseOwnerRepo(args.owner_repo);
923
+ return await githubApiRequest(auth, {
924
+ path: `/repos/${owner}/${repo}/branches`,
925
+ query: buildPaginationParams({ per_page: args.max_results }),
926
+ });
927
+ }
928
+
929
+ case 'github_get_branch': {
930
+ const { owner, repo } = parseOwnerRepo(args.owner_repo);
931
+ const branch = encodeURIComponent(String(args.branch || ''));
932
+ return await githubApiRequest(auth, {
933
+ path: `/repos/${owner}/${repo}/branches/${branch}`,
934
+ });
935
+ }
936
+
937
+ case 'github_list_collaborators': {
938
+ const { owner, repo } = parseOwnerRepo(args.owner_repo);
939
+ const maxResults = parsePositiveInt(args.max_results, 30);
940
+ const perPage = Math.min(100, maxResults);
941
+ const query = {
942
+ per_page: perPage,
943
+ };
944
+ if (args.affiliation) query.affiliation = args.affiliation;
945
+
946
+ const items = [];
947
+ for (let page = 1; items.length < maxResults; page += 1) {
948
+ const pageItems = await githubApiRequest(auth, {
949
+ path: `/repos/${owner}/${repo}/collaborators`,
950
+ query: { ...query, page },
951
+ });
952
+ const list = Array.isArray(pageItems) ? pageItems : [];
953
+ if (list.length === 0) break;
954
+ items.push(...list);
955
+ if (list.length < perPage) break;
956
+ }
957
+ return items.slice(0, maxResults);
958
+ }
959
+
960
+ case 'github_get_content': {
961
+ const { owner, repo } = parseOwnerRepo(args.owner_repo);
962
+ const query = {};
963
+ if (args.ref) query.ref = String(args.ref);
964
+ return await githubApiRequest(auth, {
965
+ path: `/repos/${owner}/${repo}/contents/${String(args.path || '')}`,
966
+ query,
967
+ });
968
+ }
969
+
970
+ case 'github_create_or_update_file': {
971
+ const { owner, repo } = parseOwnerRepo(args.owner_repo);
972
+ return await githubApiRequest(auth, {
973
+ method: 'PUT',
974
+ path: `/repos/${owner}/${repo}/contents/${String(args.path || '')}`,
975
+ body: {
976
+ message: String(args.message || ''),
977
+ content: String(args.content || ''),
978
+ sha: args.sha ? String(args.sha) : undefined,
979
+ branch: args.branch ? String(args.branch) : undefined,
980
+ },
981
+ });
982
+ }
983
+
984
+ case 'github_delete_file': {
985
+ const { owner, repo } = parseOwnerRepo(args.owner_repo);
986
+ return await githubApiRequest(auth, {
987
+ method: 'DELETE',
988
+ path: `/repos/${owner}/${repo}/contents/${String(args.path || '')}`,
989
+ body: {
990
+ message: String(args.message || ''),
991
+ sha: String(args.sha || ''),
992
+ branch: args.branch ? String(args.branch) : undefined,
993
+ },
994
+ });
995
+ }
996
+
997
+ case 'github_list_workflow_runs': {
998
+ const { owner, repo } = parseOwnerRepo(args.owner_repo);
999
+ let path = `/repos/${owner}/${repo}/actions/runs`;
1000
+ if (args.workflow_id) {
1001
+ path = `/repos/${owner}/${repo}/actions/workflows/${String(args.workflow_id)}/runs`;
1002
+ }
1003
+ const maxResults = parsePositiveInt(args.max_results, 100);
1004
+ const perPage = Math.min(100, maxResults);
1005
+ const query = { per_page: perPage };
1006
+ if (args.branch) query.branch = String(args.branch);
1007
+ if (args.status) query.status = args.status;
1008
+
1009
+ const runs = [];
1010
+ for (let page = 1; runs.length < maxResults; page += 1) {
1011
+ const response = await githubApiRequest(auth, {
1012
+ path,
1013
+ query: { ...query, page },
1014
+ });
1015
+ const pageRuns = Array.isArray(response?.workflow_runs)
1016
+ ? response.workflow_runs
1017
+ : [];
1018
+ if (pageRuns.length === 0) break;
1019
+ runs.push(...pageRuns);
1020
+ if (pageRuns.length < perPage) break;
1021
+ }
1022
+
1023
+ return runs.slice(0, maxResults);
1024
+ }
1025
+
1026
+ case 'github_get_workflow_run': {
1027
+ const { owner, repo } = parseOwnerRepo(args.owner_repo);
1028
+ return await githubApiRequest(auth, {
1029
+ path: `/repos/${owner}/${repo}/actions/runs/${Number(args.run_id)}`,
1030
+ });
1031
+ }
1032
+
1033
+ case 'github_trigger_workflow': {
1034
+ const { owner, repo } = parseOwnerRepo(args.owner_repo);
1035
+ let inputs = {};
1036
+ if (args.inputs) {
1037
+ try {
1038
+ inputs = JSON.parse(String(args.inputs));
1039
+ } catch (error) {
1040
+ throw new Error(`Invalid workflow inputs JSON: ${error?.message || error}`);
1041
+ }
1042
+ }
1043
+ return await githubApiRequest(auth, {
1044
+ method: 'POST',
1045
+ path: `/repos/${owner}/${repo}/actions/workflows/${String(args.workflow_id)}/dispatches`,
1046
+ body: {
1047
+ ref: args.ref ? String(args.ref) : 'main',
1048
+ inputs,
1049
+ },
1050
+ });
1051
+ }
1052
+
1053
+ case 'github_list_workflows': {
1054
+ const { owner, repo } = parseOwnerRepo(args.owner_repo);
1055
+ return await githubApiRequest(auth, {
1056
+ path: `/repos/${owner}/${repo}/actions/workflows`,
1057
+ });
1058
+ }
1059
+
1060
+ case 'github_list_user_repos': {
1061
+ const path = args.username
1062
+ ? `/users/${String(args.username)}/repos`
1063
+ : '/user/repos';
1064
+ const maxResults = parsePositiveInt(args.max_results, 100);
1065
+ const perPage = Math.min(maxResults, 100);
1066
+ const query = { per_page: perPage };
1067
+ if (args.visibility) query.visibility = args.visibility;
1068
+ if (args.sort) query.sort = args.sort;
1069
+ if (args.direction) query.direction = args.direction;
1070
+
1071
+ const repos = [];
1072
+ for (let page = 1; repos.length < maxResults; page += 1) {
1073
+ const pageRepos = await githubApiRequest(auth, {
1074
+ path,
1075
+ query: { ...query, page },
1076
+ });
1077
+ const list = Array.isArray(pageRepos) ? pageRepos : [];
1078
+ if (list.length === 0) break;
1079
+ repos.push(...list);
1080
+ if (list.length < perPage) break;
1081
+ }
1082
+
1083
+ return repos.slice(0, maxResults);
1084
+ }
1085
+
1086
+ case 'github_api_request': {
1087
+ let baseUrl = 'https://api.github.com';
1088
+ let path = String(args.path || '');
1089
+ let query = args.query || null;
1090
+ if (path.startsWith('http')) {
1091
+ const url = new URL(path);
1092
+ const isHttps = url.protocol === 'https:';
1093
+ if (!isHttps) {
1094
+ throw new Error('Only https:// GitHub API URLs are allowed.');
1095
+ }
1096
+ if (!isAllowedGithubApiHost(url.hostname)) {
1097
+ throw new Error(`Host is not allowed for GitHub API requests: ${url.hostname}`);
1098
+ }
1099
+ baseUrl = `${url.protocol}//${url.host}`;
1100
+ path = url.pathname;
1101
+ const parsedQuery = Object.fromEntries(url.searchParams.entries());
1102
+ query = {
1103
+ ...parsedQuery,
1104
+ ...(args.query && typeof args.query === 'object' ? args.query : {}),
1105
+ };
1106
+ }
1107
+ return await githubApiRequest(auth, {
1108
+ method: args.method || 'GET',
1109
+ path,
1110
+ query,
1111
+ body: args.body || null,
1112
+ baseUrl,
1113
+ });
1114
+ }
1115
+
1116
+ default:
1117
+ return null;
1118
+ }
1119
+ }
1120
+
1121
+ module.exports = {
1122
+ executeGithubTool,
1123
+ githubToolDefinitions,
1124
+ };