@nghyane/arcane 0.1.12 → 0.1.14

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 (333) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/package.json +21 -70
  3. package/scripts/format-prompts.ts +1 -3
  4. package/src/cli/args.ts +2 -7
  5. package/src/cli/config-cli.ts +1 -1
  6. package/src/cli/plugin-cli.ts +1 -1
  7. package/src/cli/setup-cli.ts +1 -1
  8. package/src/cli/update-cli.ts +1 -1
  9. package/src/cli/web-search-cli.ts +1 -1
  10. package/src/cli.ts +0 -1
  11. package/src/commands/config.ts +1 -1
  12. package/src/commands/grep.ts +1 -1
  13. package/src/commands/jupyter.ts +1 -1
  14. package/src/commands/plugin.ts +1 -1
  15. package/src/commands/setup.ts +1 -1
  16. package/src/commands/shell.ts +1 -1
  17. package/src/commands/ssh.ts +1 -1
  18. package/src/commands/stats.ts +1 -1
  19. package/src/commands/update.ts +1 -1
  20. package/src/config/model-registry.ts +3 -4
  21. package/src/config/model-resolver.ts +36 -9
  22. package/src/config/prompt-templates.ts +1 -9
  23. package/src/config/settings-schema.ts +32 -88
  24. package/src/config/settings.ts +3 -4
  25. package/src/debug/index.ts +1 -1
  26. package/src/debug/log-formatting.ts +1 -1
  27. package/src/debug/log-viewer.ts +2 -2
  28. package/src/discovery/helpers.ts +13 -3
  29. package/src/exa/company.ts +2 -7
  30. package/src/exa/index.ts +1 -35
  31. package/src/exa/linkedin.ts +2 -7
  32. package/src/exa/mcp-client.ts +21 -11
  33. package/src/exa/render.ts +30 -190
  34. package/src/exa/researcher.ts +2 -12
  35. package/src/exa/search.ts +5 -25
  36. package/src/exa/types.ts +3 -3
  37. package/src/exec/bash-executor.ts +2 -1
  38. package/src/exec/non-interactive-env.ts +43 -0
  39. package/src/export/html/index.ts +1 -1
  40. package/src/extensibility/custom-tools/loader.ts +1 -1
  41. package/src/extensibility/custom-tools/types.ts +5 -1
  42. package/src/extensibility/custom-tools/wrapper.ts +1 -1
  43. package/src/extensibility/extensions/runner.ts +1 -1
  44. package/src/extensibility/extensions/types.ts +1 -1
  45. package/src/extensibility/extensions/wrapper.ts +7 -15
  46. package/src/extensibility/hooks/runner.ts +1 -1
  47. package/src/extensibility/hooks/types.ts +1 -1
  48. package/src/extensibility/plugins/doctor.ts +1 -1
  49. package/src/index.ts +13 -13
  50. package/src/lsp/index.ts +77 -24
  51. package/src/lsp/render.ts +34 -583
  52. package/src/lsp/types.ts +3 -3
  53. package/src/lsp/utils.ts +1 -1
  54. package/src/main.ts +1 -1
  55. package/src/mcp/tool-bridge.ts +1 -24
  56. package/src/modes/components/assistant-message.ts +7 -7
  57. package/src/modes/components/bash-execution.ts +48 -113
  58. package/src/modes/components/bordered-loader.ts +1 -1
  59. package/src/modes/components/branch-summary-message.ts +13 -10
  60. package/src/modes/components/compaction-summary-message.ts +14 -13
  61. package/src/modes/components/context-group.ts +106 -0
  62. package/src/modes/components/custom-message.ts +4 -5
  63. package/src/modes/components/diff.ts +2 -2
  64. package/src/modes/components/dynamic-border.ts +1 -1
  65. package/src/modes/components/extensions/extension-dashboard.ts +2 -2
  66. package/src/modes/components/extensions/extension-list.ts +1 -1
  67. package/src/modes/components/extensions/inspector-panel.ts +8 -3
  68. package/src/modes/components/footer.ts +2 -2
  69. package/src/modes/components/history-search.ts +1 -1
  70. package/src/modes/components/hook-editor.ts +1 -1
  71. package/src/modes/components/hook-input.ts +1 -1
  72. package/src/modes/components/hook-message.ts +4 -5
  73. package/src/modes/components/hook-selector.ts +1 -1
  74. package/src/modes/components/index.ts +0 -2
  75. package/src/modes/components/keybinding-hints.ts +1 -1
  76. package/src/modes/components/login-dialog.ts +1 -1
  77. package/src/modes/components/mcp-add-wizard.ts +1 -1
  78. package/src/modes/components/model-selector.ts +1 -1
  79. package/src/modes/components/oauth-selector.ts +1 -1
  80. package/src/modes/components/plugin-settings.ts +1 -1
  81. package/src/modes/components/python-execution.ts +49 -92
  82. package/src/modes/components/queue-mode-selector.ts +1 -1
  83. package/src/modes/components/session-selector.ts +1 -1
  84. package/src/modes/components/settings-defs.ts +5 -10
  85. package/src/modes/components/settings-selector.ts +1 -1
  86. package/src/modes/components/show-images-selector.ts +1 -1
  87. package/src/modes/components/skill-message.ts +4 -4
  88. package/src/modes/components/status-line/segments.ts +2 -2
  89. package/src/modes/components/status-line/separators.ts +1 -1
  90. package/src/modes/components/status-line-segment-editor.ts +1 -1
  91. package/src/modes/components/status-line.ts +1 -1
  92. package/src/modes/components/theme-selector.ts +1 -1
  93. package/src/modes/components/thinking-selector.ts +1 -1
  94. package/src/modes/components/todo-display.ts +2 -4
  95. package/src/modes/components/todo-reminder.ts +4 -4
  96. package/src/modes/components/tool-execution.ts +118 -440
  97. package/src/modes/components/tool-image-display.ts +107 -0
  98. package/src/modes/components/tree-selector.ts +2 -2
  99. package/src/modes/components/ttsr-notification.ts +4 -17
  100. package/src/modes/components/user-message-selector.ts +1 -1
  101. package/src/modes/components/user-message.ts +9 -10
  102. package/src/modes/components/welcome.ts +1 -1
  103. package/src/modes/controllers/command-controller.ts +1 -1
  104. package/src/modes/controllers/event-controller.ts +58 -187
  105. package/src/modes/controllers/extension-ui-controller.ts +1 -1
  106. package/src/modes/controllers/input-controller.ts +3 -1
  107. package/src/modes/controllers/mcp-command-controller.ts +1 -1
  108. package/src/modes/controllers/selector-controller.ts +3 -26
  109. package/src/modes/controllers/ssh-command-controller.ts +1 -1
  110. package/src/modes/interactive-mode.ts +3 -7
  111. package/src/modes/print-mode.ts +5 -5
  112. package/src/modes/rpc/rpc-mode.ts +1 -1
  113. package/src/modes/types.ts +1 -2
  114. package/src/modes/utils/ui-helpers.ts +34 -32
  115. package/src/patch/edit-tool.ts +742 -0
  116. package/src/patch/index.ts +32 -898
  117. package/src/patch/schemas.ts +208 -0
  118. package/src/patch/shared.ts +83 -151
  119. package/src/prompts/agents/explore.md +22 -37
  120. package/src/prompts/agents/frontmatter.md +1 -1
  121. package/src/prompts/agents/init.md +2 -2
  122. package/src/prompts/agents/librarian.md +30 -21
  123. package/src/prompts/agents/oracle.md +9 -2
  124. package/src/prompts/agents/reviewer.md +15 -49
  125. package/src/prompts/agents/task.md +17 -9
  126. package/src/prompts/compaction/branch-summary-context.md +1 -1
  127. package/src/prompts/compaction/branch-summary-preamble.md +1 -1
  128. package/src/prompts/compaction/branch-summary.md +4 -1
  129. package/src/prompts/compaction/compaction-short-summary.md +1 -1
  130. package/src/prompts/compaction/compaction-summary-context.md +1 -1
  131. package/src/prompts/compaction/compaction-summary.md +4 -1
  132. package/src/prompts/compaction/compaction-turn-prefix.md +1 -1
  133. package/src/prompts/compaction/compaction-update-summary.md +1 -1
  134. package/src/prompts/memories/consolidation.md +1 -1
  135. package/src/prompts/memories/read_path.md +1 -1
  136. package/src/prompts/memories/stage_one_input.md +1 -1
  137. package/src/prompts/memories/stage_one_system.md +1 -1
  138. package/src/prompts/review-request.md +1 -1
  139. package/src/prompts/system/agent-creation-architect.md +1 -1
  140. package/src/prompts/system/agent-creation-user.md +1 -1
  141. package/src/prompts/system/custom-system-prompt.md +1 -1
  142. package/src/prompts/system/file-operations.md +1 -1
  143. package/src/prompts/system/subagent-system-prompt.md +2 -2
  144. package/src/prompts/system/summarization-system.md +1 -1
  145. package/src/prompts/system/system-prompt.md +163 -178
  146. package/src/prompts/system/title-system.md +1 -1
  147. package/src/prompts/system/ttsr-interrupt.md +1 -1
  148. package/src/prompts/system/verification-reminder.md +6 -0
  149. package/src/prompts/system/web-search.md +1 -1
  150. package/src/sdk.ts +0 -9
  151. package/src/session/agent-session.ts +244 -1459
  152. package/src/session/auth-storage.ts +5 -0
  153. package/src/session/model-controller.ts +406 -0
  154. package/src/session/retry-utils.ts +71 -0
  155. package/src/session/session-manager.ts +22 -186
  156. package/src/session/session-types.ts +312 -0
  157. package/src/session/stats.ts +387 -0
  158. package/src/session/streaming-edit.ts +258 -0
  159. package/src/session/ttsr.ts +213 -0
  160. package/src/slash-commands/builtin-registry.ts +0 -8
  161. package/src/ssh/connection-manager.ts +1 -0
  162. package/src/stt/recorder.ts +2 -2
  163. package/src/system-prompt.ts +1 -14
  164. package/src/task/agents.ts +7 -33
  165. package/src/task/executor.ts +50 -438
  166. package/src/task/index.ts +104 -71
  167. package/src/task/progress-tracker.ts +390 -0
  168. package/src/task/render.ts +371 -187
  169. package/src/task/subprocess-tool-registry.ts +1 -1
  170. package/src/task/types.ts +14 -47
  171. package/src/tools/ask.ts +31 -42
  172. package/src/tools/bash-interactive.ts +4 -47
  173. package/src/tools/bash-interceptor.ts +2 -2
  174. package/src/tools/bash-normalize.ts +1 -1
  175. package/src/tools/bash-skill-urls.ts +2 -2
  176. package/src/tools/bash.ts +87 -136
  177. package/src/tools/browser.ts +54 -84
  178. package/src/tools/create-tools.ts +186 -0
  179. package/src/tools/default-renderer.ts +104 -0
  180. package/src/tools/explore.ts +11 -10
  181. package/src/tools/fetch.ts +24 -114
  182. package/src/tools/find.ts +48 -132
  183. package/src/tools/gemini-image.ts +5 -15
  184. package/src/tools/github.ts +450 -0
  185. package/src/tools/grep.ts +43 -179
  186. package/src/tools/index.ts +35 -198
  187. package/src/tools/json-tree.ts +3 -3
  188. package/src/tools/librarian.ts +18 -18
  189. package/src/tools/list-limit.ts +2 -2
  190. package/src/tools/notebook.ts +35 -87
  191. package/src/tools/oracle.ts +25 -25
  192. package/src/tools/output-meta.ts +89 -4
  193. package/src/tools/output-utils.ts +2 -2
  194. package/src/tools/python.ts +86 -637
  195. package/src/tools/read.ts +36 -119
  196. package/src/tools/reviewer-tool.ts +19 -21
  197. package/src/tools/search-code.ts +128 -0
  198. package/src/tools/ssh.ts +67 -126
  199. package/src/tools/subagent-tool.ts +197 -123
  200. package/src/tools/todo-write.ts +15 -31
  201. package/src/tools/tool-errors.ts +0 -30
  202. package/src/tools/undo-edit.ts +30 -67
  203. package/src/tools/write.ts +78 -127
  204. package/src/tui/code-cell.ts +4 -4
  205. package/src/tui/file-list.ts +2 -2
  206. package/src/tui/output-block.ts +1 -1
  207. package/src/tui/status-line.ts +1 -1
  208. package/src/tui/tree-list.ts +2 -2
  209. package/src/tui/types.ts +1 -1
  210. package/src/tui/utils.ts +1 -1
  211. package/src/{tools → ui}/render-utils.ts +87 -126
  212. package/src/utils/external-editor.ts +4 -4
  213. package/src/utils/file-mentions.ts +1 -1
  214. package/src/utils/index.ts +30 -0
  215. package/src/utils/tools-manager.ts +9 -19
  216. package/src/web/github-client.ts +290 -0
  217. package/src/web/scrapers/github.ts +11 -62
  218. package/src/web/search/auth.ts +1 -3
  219. package/src/web/search/index.ts +85 -49
  220. package/src/web/search/provider.ts +11 -16
  221. package/src/web/search/providers/grep.ts +160 -0
  222. package/src/web/search/render.ts +48 -235
  223. package/src/web/search/types.ts +1 -1
  224. package/src/commands/commit.ts +0 -36
  225. package/src/commit/agentic/agent.ts +0 -311
  226. package/src/commit/agentic/fallback.ts +0 -96
  227. package/src/commit/agentic/index.ts +0 -359
  228. package/src/commit/agentic/prompts/analyze-file.md +0 -22
  229. package/src/commit/agentic/prompts/session-user.md +0 -25
  230. package/src/commit/agentic/prompts/split-confirm.md +0 -1
  231. package/src/commit/agentic/prompts/system.md +0 -38
  232. package/src/commit/agentic/state.ts +0 -69
  233. package/src/commit/agentic/tools/analyze-file.ts +0 -118
  234. package/src/commit/agentic/tools/git-file-diff.ts +0 -194
  235. package/src/commit/agentic/tools/git-hunk.ts +0 -50
  236. package/src/commit/agentic/tools/git-overview.ts +0 -84
  237. package/src/commit/agentic/tools/index.ts +0 -56
  238. package/src/commit/agentic/tools/propose-changelog.ts +0 -128
  239. package/src/commit/agentic/tools/propose-commit.ts +0 -154
  240. package/src/commit/agentic/tools/recent-commits.ts +0 -81
  241. package/src/commit/agentic/tools/split-commit.ts +0 -280
  242. package/src/commit/agentic/topo-sort.ts +0 -44
  243. package/src/commit/agentic/trivial.ts +0 -51
  244. package/src/commit/agentic/validation.ts +0 -200
  245. package/src/commit/analysis/conventional.ts +0 -165
  246. package/src/commit/analysis/index.ts +0 -4
  247. package/src/commit/analysis/scope.ts +0 -242
  248. package/src/commit/analysis/summary.ts +0 -112
  249. package/src/commit/analysis/validation.ts +0 -66
  250. package/src/commit/changelog/detect.ts +0 -37
  251. package/src/commit/changelog/generate.ts +0 -110
  252. package/src/commit/changelog/index.ts +0 -234
  253. package/src/commit/changelog/parse.ts +0 -44
  254. package/src/commit/cli.ts +0 -93
  255. package/src/commit/git/diff.ts +0 -148
  256. package/src/commit/git/errors.ts +0 -9
  257. package/src/commit/git/index.ts +0 -211
  258. package/src/commit/git/operations.ts +0 -54
  259. package/src/commit/index.ts +0 -5
  260. package/src/commit/map-reduce/index.ts +0 -64
  261. package/src/commit/map-reduce/map-phase.ts +0 -178
  262. package/src/commit/map-reduce/reduce-phase.ts +0 -145
  263. package/src/commit/map-reduce/utils.ts +0 -9
  264. package/src/commit/message.ts +0 -11
  265. package/src/commit/model-selection.ts +0 -69
  266. package/src/commit/pipeline.ts +0 -243
  267. package/src/commit/prompts/analysis-system.md +0 -148
  268. package/src/commit/prompts/analysis-user.md +0 -38
  269. package/src/commit/prompts/changelog-system.md +0 -50
  270. package/src/commit/prompts/changelog-user.md +0 -18
  271. package/src/commit/prompts/file-observer-system.md +0 -24
  272. package/src/commit/prompts/file-observer-user.md +0 -8
  273. package/src/commit/prompts/reduce-system.md +0 -50
  274. package/src/commit/prompts/reduce-user.md +0 -17
  275. package/src/commit/prompts/summary-retry.md +0 -3
  276. package/src/commit/prompts/summary-system.md +0 -38
  277. package/src/commit/prompts/summary-user.md +0 -13
  278. package/src/commit/prompts/types-description.md +0 -2
  279. package/src/commit/types.ts +0 -109
  280. package/src/commit/utils/exclusions.ts +0 -42
  281. package/src/mcp/render.ts +0 -123
  282. package/src/modes/components/agent-dashboard.ts +0 -1130
  283. package/src/modes/components/codemode-group.ts +0 -369
  284. package/src/modes/components/read-tool-group.ts +0 -119
  285. package/src/modes/components/visual-truncate.ts +0 -63
  286. package/src/prompts/system/subagent-user-prompt.md +0 -8
  287. package/src/prompts/tools/ask.md +0 -44
  288. package/src/prompts/tools/bash.md +0 -24
  289. package/src/prompts/tools/browser.md +0 -33
  290. package/src/prompts/tools/calculator.md +0 -12
  291. package/src/prompts/tools/explore.md +0 -29
  292. package/src/prompts/tools/fetch.md +0 -16
  293. package/src/prompts/tools/find.md +0 -18
  294. package/src/prompts/tools/gemini-image.md +0 -23
  295. package/src/prompts/tools/grep.md +0 -28
  296. package/src/prompts/tools/hashline.md +0 -232
  297. package/src/prompts/tools/librarian.md +0 -24
  298. package/src/prompts/tools/lsp.md +0 -28
  299. package/src/prompts/tools/oracle.md +0 -26
  300. package/src/prompts/tools/patch.md +0 -74
  301. package/src/prompts/tools/python.md +0 -66
  302. package/src/prompts/tools/read.md +0 -36
  303. package/src/prompts/tools/replace.md +0 -38
  304. package/src/prompts/tools/reviewer.md +0 -41
  305. package/src/prompts/tools/ssh.md +0 -51
  306. package/src/prompts/tools/task-summary.md +0 -28
  307. package/src/prompts/tools/task.md +0 -146
  308. package/src/prompts/tools/todo-write.md +0 -65
  309. package/src/prompts/tools/undo-edit.md +0 -7
  310. package/src/prompts/tools/web-search.md +0 -19
  311. package/src/prompts/tools/write.md +0 -18
  312. package/src/task/batch.ts +0 -102
  313. package/src/task/discovery.ts +0 -126
  314. package/src/task/parallel.ts +0 -84
  315. package/src/task/template.ts +0 -32
  316. package/src/tools/calculator.ts +0 -537
  317. package/src/tools/jtd-to-typescript.ts +0 -198
  318. package/src/tools/renderers.ts +0 -60
  319. package/src/tools/tool-result.ts +0 -86
  320. /package/src/{modes/theme → theme}/dark.json +0 -0
  321. /package/src/{modes/theme → theme}/defaults/dark-catppuccin.json +0 -0
  322. /package/src/{modes/theme → theme}/defaults/dark-dracula.json +0 -0
  323. /package/src/{modes/theme → theme}/defaults/dark-gruvbox.json +0 -0
  324. /package/src/{modes/theme → theme}/defaults/dark-solarized.json +0 -0
  325. /package/src/{modes/theme → theme}/defaults/dark-tokyo-night.json +0 -0
  326. /package/src/{modes/theme → theme}/defaults/index.ts +0 -0
  327. /package/src/{modes/theme → theme}/defaults/light-catppuccin.json +0 -0
  328. /package/src/{modes/theme → theme}/defaults/light-github.json +0 -0
  329. /package/src/{modes/theme → theme}/defaults/light-solarized.json +0 -0
  330. /package/src/{modes/theme → theme}/light.json +0 -0
  331. /package/src/{modes/theme → theme}/mermaid-cache.ts +0 -0
  332. /package/src/{modes/theme → theme}/theme-schema.json +0 -0
  333. /package/src/{modes/theme → theme}/theme.ts +0 -0
@@ -0,0 +1,450 @@
1
+ import type { AgentTool, AgentToolResult } from "@nghyane/arcane-agent";
2
+ import { type Static, Type } from "@sinclair/typebox";
3
+ import type { Theme } from "../theme/theme";
4
+ import { type GitHubResponse, githubClient } from "../web/github-client";
5
+ import type { ToolSession } from ".";
6
+ import { type OutputMeta, toolResult } from "./output-meta";
7
+
8
+ // =============================================================================
9
+ // Schema
10
+ // =============================================================================
11
+
12
+ const ActionEnum = Type.Union([
13
+ Type.Literal("get_repo"),
14
+ Type.Literal("get_file"),
15
+ Type.Literal("get_tree"),
16
+ Type.Literal("search_repos"),
17
+ Type.Literal("get_issue"),
18
+ Type.Literal("list_issues"),
19
+ Type.Literal("get_pull"),
20
+ Type.Literal("list_pulls"),
21
+ Type.Literal("list_commits"),
22
+ Type.Literal("get_commit"),
23
+ ]);
24
+
25
+ const schema = Type.Object({
26
+ action: ActionEnum,
27
+ owner: Type.String({ description: "Repository owner (user or org)" }),
28
+ repo: Type.String({ description: "Repository name" }),
29
+ path: Type.Optional(Type.String({ description: "File or directory path within the repo" })),
30
+ ref: Type.Optional(Type.String({ description: "Branch, tag, or commit SHA" })),
31
+ number: Type.Optional(Type.Number({ description: "Issue or PR number" })),
32
+ query: Type.Optional(Type.String({ description: "Search query or SSR pattern" })),
33
+ state: Type.Optional(Type.String({ description: "Filter by state (open, closed, all)" })),
34
+ labels: Type.Optional(Type.String({ description: "Comma-separated label filter" })),
35
+ sha: Type.Optional(Type.String({ description: "Commit SHA" })),
36
+ include_diff: Type.Optional(Type.Boolean({ description: "Include diff in commit details" })),
37
+ recursive: Type.Optional(Type.Boolean({ description: "Recursively list tree contents" })),
38
+ limit: Type.Optional(Type.Number({ description: "Max number of results" })),
39
+ });
40
+
41
+ type GitHubInput = Static<typeof schema>;
42
+
43
+ // =============================================================================
44
+ // Details
45
+ // =============================================================================
46
+
47
+ export interface GitHubToolDetails {
48
+ action: string;
49
+ owner: string;
50
+ repo: string;
51
+ meta?: OutputMeta;
52
+ }
53
+
54
+ // =============================================================================
55
+ // Response Formatters
56
+ // =============================================================================
57
+
58
+ function formatRepo(data: any): string {
59
+ return [
60
+ `# ${data.full_name}`,
61
+ data.description ? `${data.description}` : "",
62
+ "",
63
+ `- Default branch: ${data.default_branch}`,
64
+ `- Language: ${data.language ?? "N/A"}`,
65
+ `- Stars: ${data.stargazers_count} | Forks: ${data.forks_count}`,
66
+ `- Topics: ${data.topics?.length ? data.topics.join(", ") : "none"}`,
67
+ `- License: ${data.license?.spdx_id ?? "N/A"}`,
68
+ `- Created: ${data.created_at} | Updated: ${data.updated_at}`,
69
+ data.homepage ? `- Homepage: ${data.homepage}` : "",
70
+ ]
71
+ .filter(Boolean)
72
+ .join("\n");
73
+ }
74
+
75
+ function formatTreeEntry(entry: any): string {
76
+ const icon = entry.type === "dir" || entry.type === "tree" ? "dir" : "file";
77
+ const size = entry.size ? ` (${formatSize(entry.size)})` : "";
78
+ const name = entry.path ?? entry.name;
79
+ return `[${icon}] ${name}${size}`;
80
+ }
81
+
82
+ function formatSize(bytes: number): string {
83
+ if (bytes < 1024) return `${bytes}B`;
84
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}K`;
85
+ return `${(bytes / (1024 * 1024)).toFixed(1)}M`;
86
+ }
87
+
88
+ function formatIssue(data: any, comments: any[] = []): string {
89
+ const lines = [
90
+ `# #${data.number}: ${data.title}`,
91
+ `State: ${data.state} | Author: @${data.user?.login} | Created: ${data.created_at}`,
92
+ data.labels?.length ? `Labels: ${data.labels.map((l: any) => l.name).join(", ")}` : "",
93
+ data.assignees?.length ? `Assignees: ${data.assignees.map((a: any) => `@${a.login}`).join(", ")}` : "",
94
+ "",
95
+ data.body ?? "(no description)",
96
+ ].filter(l => l !== "");
97
+
98
+ for (const comment of comments) {
99
+ lines.push("", `---`, `**@${comment.user?.login}** on ${comment.created_at}:`, "", comment.body ?? "");
100
+ }
101
+
102
+ return lines.join("\n");
103
+ }
104
+
105
+ function formatIssueMinimal(issue: any): string {
106
+ const labels = issue.labels?.length ? ` [${issue.labels.map((l: any) => l.name).join(", ")}]` : "";
107
+ return `#${issue.number} [${issue.state}] ${issue.title}${labels} (@${issue.user?.login}, ${issue.created_at})`;
108
+ }
109
+
110
+ function formatPR(data: any, diff?: string): string {
111
+ const lines = [
112
+ `# PR #${data.number}: ${data.title}`,
113
+ `State: ${data.state}${data.merged ? " (merged)" : ""} | Author: @${data.user?.login}`,
114
+ `Base: ${data.base?.ref} <- Head: ${data.head?.ref}`,
115
+ `Changed files: ${data.changed_files ?? "?"} | +${data.additions ?? 0} -${data.deletions ?? 0}`,
116
+ "",
117
+ data.body ?? "(no description)",
118
+ ];
119
+
120
+ if (diff) {
121
+ lines.push("", "## Diff", "", `\`\`\`diff`, diff, `\`\`\``);
122
+ }
123
+
124
+ return lines.join("\n");
125
+ }
126
+
127
+ function formatPRMinimal(pr: any): string {
128
+ const merged = pr.merged_at ? " (merged)" : "";
129
+ return `#${pr.number} [${pr.state}${merged}] ${pr.title} (@${pr.user?.login}, ${pr.base?.ref} <- ${pr.head?.ref})`;
130
+ }
131
+
132
+ function formatCommit(data: any, diff?: string): string {
133
+ const lines = [
134
+ `Commit: ${data.sha}`,
135
+ `Author: ${data.commit?.author?.name} <${data.commit?.author?.email}>`,
136
+ `Date: ${data.commit?.author?.date}`,
137
+ "",
138
+ data.commit?.message ?? "",
139
+ ];
140
+
141
+ if (data.stats) {
142
+ lines.push("", `Files changed: ${data.stats.total} | +${data.stats.additions} -${data.stats.deletions}`);
143
+ }
144
+
145
+ if (data.files?.length && !diff) {
146
+ lines.push("", "Files:");
147
+ for (const f of data.files) {
148
+ lines.push(` ${f.status} ${f.filename} (+${f.additions} -${f.deletions})`);
149
+ }
150
+ }
151
+
152
+ if (diff) {
153
+ lines.push("", "## Diff", "", `\`\`\`diff`, diff, `\`\`\``);
154
+ }
155
+
156
+ return lines.join("\n");
157
+ }
158
+
159
+ function formatCommitMinimal(c: any): string {
160
+ const sha = (c.sha ?? "").slice(0, 7);
161
+ const msg = (c.commit?.message ?? "").split("\n")[0];
162
+ const author = c.commit?.author?.name ?? c.author?.login ?? "?";
163
+ const date = c.commit?.author?.date ?? "";
164
+ return `${sha} ${msg} (${author}, ${date})`;
165
+ }
166
+
167
+ function formatSearchReposResult(data: any): string {
168
+ const items = data.items ?? [];
169
+ const lines = [`Found ${data.total_count} repositories (showing ${items.length}):`, ""];
170
+ for (const item of items) {
171
+ lines.push(`${item.full_name} (${item.stargazers_count}*) - ${item.description ?? "no description"}`);
172
+ }
173
+ return lines.join("\n");
174
+ }
175
+
176
+ // =============================================================================
177
+ // Action Handlers
178
+ // =============================================================================
179
+
180
+ const MAX_FILE_LINES = 500;
181
+ const MAX_DIFF_CHARS = 50_000;
182
+ const MAX_COMMENTS_PAGES = 5;
183
+
184
+ async function handleAction(input: GitHubInput, signal?: AbortSignal): Promise<{ text: string; url?: string }> {
185
+ const { action, owner, repo } = input;
186
+ const opts = { signal };
187
+ const base = `/repos/${owner}/${repo}`;
188
+
189
+ switch (action) {
190
+ case "get_repo": {
191
+ const res = await githubClient.request(base, opts);
192
+ if (!res.ok) return error(res, "repository");
193
+ return { text: formatRepo(res.data), url: `https://github.com/${owner}/${repo}` };
194
+ }
195
+
196
+ case "get_file": {
197
+ const filePath = input.path ?? "README.md";
198
+ const ref = input.ref ? `?ref=${input.ref}` : "";
199
+ let res = await githubClient.request<string>(`${base}/contents/${filePath}${ref}`, {
200
+ ...opts,
201
+ mediaType: "application/vnd.github.v3.raw",
202
+ });
203
+ // Fallback to Blob API for files >1MB (raw mediaType returns 403)
204
+ if (!res.ok && res.status === 403) {
205
+ const metaRes = await githubClient.request<{ sha: string; size: number }>(
206
+ `${base}/contents/${filePath}${ref}`,
207
+ opts,
208
+ );
209
+ if (metaRes.ok && metaRes.data?.sha) {
210
+ const blobRes = await githubClient.request<{ content: string; encoding: string }>(
211
+ `${base}/git/blobs/${metaRes.data.sha}`,
212
+ opts,
213
+ );
214
+ if (blobRes.ok && blobRes.data?.content) {
215
+ const decoded =
216
+ blobRes.data.encoding === "base64"
217
+ ? Buffer.from(blobRes.data.content, "base64").toString("utf-8")
218
+ : blobRes.data.content;
219
+ res = { data: decoded as any, ok: true, status: 200 };
220
+ }
221
+ }
222
+ }
223
+ if (!res.ok) return error(res, `file ${filePath}`);
224
+ const content = String(res.data);
225
+ const lines = content.split("\n");
226
+ const truncated = lines.length > MAX_FILE_LINES;
227
+ const output = truncated ? lines.slice(0, MAX_FILE_LINES).join("\n") : content;
228
+ const note = truncated ? `\n\n[Truncated: showing ${MAX_FILE_LINES}/${lines.length} lines]` : "";
229
+ return {
230
+ text: `# ${owner}/${repo}:${filePath}${input.ref ? ` @${input.ref}` : ""}\n\n${output}${note}`,
231
+ url: `https://github.com/${owner}/${repo}/blob/${input.ref ?? "HEAD"}/${filePath}`,
232
+ };
233
+ }
234
+
235
+ case "get_tree": {
236
+ const treePath = input.path ?? "";
237
+ if (input.recursive) {
238
+ const ref = input.ref ?? "HEAD";
239
+ const res = await githubClient.request<any>(`${base}/git/trees/${ref}?recursive=1`, opts);
240
+ if (!res.ok) return error(res, "tree");
241
+ const entries = (res.data.tree ?? [])
242
+ .filter((e: any) => !treePath || e.path.startsWith(treePath))
243
+ .slice(0, 500);
244
+ return {
245
+ text: `# Tree: ${owner}/${repo}${treePath ? `/${treePath}` : ""} (recursive)\n\n${entries.map(formatTreeEntry).join("\n")}`,
246
+ };
247
+ }
248
+ const ref = input.ref ? `?ref=${input.ref}` : "";
249
+ const endpoint = treePath ? `${base}/contents/${treePath}${ref}` : `${base}/contents${ref}`;
250
+ const res = await githubClient.request<any[]>(endpoint, opts);
251
+ if (!res.ok) return error(res, "directory");
252
+ const entries = Array.isArray(res.data) ? res.data : [res.data];
253
+ return {
254
+ text: `# ${owner}/${repo}/${treePath}\n\n${entries.map(formatTreeEntry).join("\n")}`,
255
+ };
256
+ }
257
+
258
+ case "search_repos": {
259
+ const q = input.query ?? `${owner}/${repo}`;
260
+ const perPage = Math.min(input.limit ?? 30, 100);
261
+ const res = await githubClient.request<any>(
262
+ `/search/repositories?q=${encodeURIComponent(q)}&per_page=${perPage}`,
263
+ opts,
264
+ );
265
+ if (!res.ok) return error(res, "repository search");
266
+ return { text: formatSearchReposResult(res.data) };
267
+ }
268
+
269
+ case "get_issue": {
270
+ const num = input.number;
271
+ if (!num) return { text: "Error: 'number' is required for get_issue" };
272
+ const [issueRes, commentsRes] = await Promise.all([
273
+ githubClient.request<any>(`${base}/issues/${num}`, opts),
274
+ githubClient.requestPaginated<any>(`${base}/issues/${num}/comments`, {
275
+ ...opts,
276
+ perPage: 100,
277
+ maxPages: MAX_COMMENTS_PAGES,
278
+ }),
279
+ ]);
280
+ if (!issueRes.ok) return error(issueRes, `issue #${num}`);
281
+ return {
282
+ text: formatIssue(issueRes.data, commentsRes.ok ? commentsRes.data : []),
283
+ url: `https://github.com/${owner}/${repo}/issues/${num}`,
284
+ };
285
+ }
286
+
287
+ case "list_issues": {
288
+ const params = new URLSearchParams();
289
+ if (input.state) params.set("state", input.state);
290
+ if (input.labels) params.set("labels", input.labels);
291
+ const limit = Math.min(input.limit ?? 100, 500);
292
+ const perPage = Math.min(limit, 100);
293
+ const maxPages = Math.ceil(limit / perPage);
294
+ const res = await githubClient.requestPaginated<any>(`${base}/issues?${params}`, {
295
+ ...opts,
296
+ perPage,
297
+ maxPages,
298
+ });
299
+ if (!res.ok) return error(res, "issues");
300
+ const issues = (res.data ?? []).filter((i: any) => !i.pull_request).slice(0, limit);
301
+ const header = `${issues.length} issue(s)${issues.length >= limit ? " (limit reached, increase limit for more)" : ""}`;
302
+ return {
303
+ text: issues.length ? `${header}\n${issues.map(formatIssueMinimal).join("\n")}` : "No issues found.",
304
+ };
305
+ }
306
+
307
+ case "get_pull": {
308
+ const num = input.number;
309
+ if (!num) return { text: "Error: 'number' is required for get_pull" };
310
+ const prRes = await githubClient.request<any>(`${base}/pulls/${num}`, opts);
311
+ if (!prRes.ok) return error(prRes, `PR #${num}`);
312
+
313
+ let diff: string | undefined;
314
+ if (input.include_diff) {
315
+ const diffRes = await githubClient.request<string>(`${base}/pulls/${num}`, {
316
+ ...opts,
317
+ mediaType: "application/vnd.github.v3.diff",
318
+ });
319
+ if (diffRes.ok) {
320
+ diff = String(diffRes.data);
321
+ if (diff.length > MAX_DIFF_CHARS) {
322
+ diff = `${diff.slice(0, MAX_DIFF_CHARS)}\n[Truncated at ${MAX_DIFF_CHARS} chars]`;
323
+ }
324
+ }
325
+ }
326
+
327
+ return {
328
+ text: formatPR(prRes.data, diff),
329
+ url: `https://github.com/${owner}/${repo}/pull/${num}`,
330
+ };
331
+ }
332
+
333
+ case "list_pulls": {
334
+ const params = new URLSearchParams();
335
+ if (input.state) params.set("state", input.state);
336
+ const limit = Math.min(input.limit ?? 100, 500);
337
+ const perPage = Math.min(limit, 100);
338
+ const maxPages = Math.ceil(limit / perPage);
339
+ const res = await githubClient.requestPaginated<any>(`${base}/pulls?${params}`, {
340
+ ...opts,
341
+ perPage,
342
+ maxPages,
343
+ });
344
+ if (!res.ok) return error(res, "pull requests");
345
+ const pulls = (res.data ?? []).slice(0, limit);
346
+ const header = `${pulls.length} PR(s)${pulls.length >= limit ? " (limit reached, increase limit for more)" : ""}`;
347
+ return {
348
+ text: pulls.length ? `${header}\n${pulls.map(formatPRMinimal).join("\n")}` : "No pull requests found.",
349
+ };
350
+ }
351
+
352
+ case "list_commits": {
353
+ const params = new URLSearchParams();
354
+ if (input.sha) params.set("sha", input.sha);
355
+ if (input.path) params.set("path", input.path);
356
+ const limit = Math.min(input.limit ?? 100, 500);
357
+ const perPage = Math.min(limit, 100);
358
+ const maxPages = Math.ceil(limit / perPage);
359
+ const res = await githubClient.requestPaginated<any>(`${base}/commits?${params}`, {
360
+ ...opts,
361
+ perPage,
362
+ maxPages,
363
+ });
364
+ if (!res.ok) return error(res, "commits");
365
+ const commits = (res.data ?? []).slice(0, limit);
366
+ const header = `${commits.length} commit(s)${commits.length >= limit ? " (limit reached, increase limit for more)" : ""}`;
367
+ return {
368
+ text: commits.length ? `${header}\n${commits.map(formatCommitMinimal).join("\n")}` : "No commits found.",
369
+ };
370
+ }
371
+
372
+ case "get_commit": {
373
+ const sha = input.sha;
374
+ if (!sha) return { text: "Error: 'sha' is required for get_commit" };
375
+ const res = await githubClient.request<any>(`${base}/commits/${sha}`, opts);
376
+ if (!res.ok) return error(res, `commit ${sha}`);
377
+
378
+ let diff: string | undefined;
379
+ if (input.include_diff) {
380
+ const diffRes = await githubClient.request<string>(`${base}/commits/${sha}`, {
381
+ ...opts,
382
+ mediaType: "application/vnd.github.v3.diff",
383
+ });
384
+ if (diffRes.ok) {
385
+ diff = String(diffRes.data);
386
+ if (diff.length > MAX_DIFF_CHARS) {
387
+ diff = `${diff.slice(0, MAX_DIFF_CHARS)}\n[Truncated at ${MAX_DIFF_CHARS} chars]`;
388
+ }
389
+ }
390
+ }
391
+
392
+ return {
393
+ text: formatCommit(res.data, diff),
394
+ url: `https://github.com/${owner}/${repo}/commit/${sha}`,
395
+ };
396
+ }
397
+
398
+ default:
399
+ return { text: `Unknown action: ${action}` };
400
+ }
401
+ }
402
+
403
+ function error(res: GitHubResponse, resource: string): { text: string } {
404
+ const status = res.status;
405
+ if (status === 404) return { text: `Error: ${resource} not found (404)` };
406
+ if (status === 403) {
407
+ const rl = res.rateLimit;
408
+ if (rl && rl.remaining === 0) {
409
+ return { text: `Error: GitHub API rate limit exceeded. Resets at ${new Date(rl.reset * 1000).toISOString()}` };
410
+ }
411
+ return { text: `Error: Access denied to ${resource} (403). Check token permissions.` };
412
+ }
413
+ if (status === 401)
414
+ return { text: `Error: Authentication failed (401). Check GITHUB_TOKEN or run 'gh auth login'.` };
415
+ return { text: `Error: Failed to fetch ${resource} (HTTP ${status})` };
416
+ }
417
+
418
+ // =============================================================================
419
+ // Tool Class
420
+ // =============================================================================
421
+
422
+ export class GitHubTool implements AgentTool<typeof schema, GitHubToolDetails, Theme> {
423
+ readonly name = "github";
424
+ readonly label = "GitHub";
425
+ readonly parameters = schema;
426
+ description = "Interact with GitHub API: repos, issues, PRs, commits";
427
+
428
+ constructor(readonly _session: ToolSession) {}
429
+
430
+ async execute(
431
+ _toolCallId: string,
432
+ params: Record<string, unknown>,
433
+ signal?: AbortSignal,
434
+ ): Promise<AgentToolResult<GitHubToolDetails>> {
435
+ const input = params as unknown as GitHubInput;
436
+ const details: GitHubToolDetails = {
437
+ action: input.action,
438
+ owner: input.owner,
439
+ repo: input.repo,
440
+ };
441
+
442
+ const result = await handleAction(input, signal);
443
+
444
+ const builder = toolResult(details).text(result.text);
445
+ if (result.url) {
446
+ builder.sourceUrl(result.url);
447
+ }
448
+ return builder.done();
449
+ }
450
+ }