@bahulam/code 2.6.0

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 (273) hide show
  1. package/README.md +80 -0
  2. package/package.json +49 -0
  3. package/pulse/app/activity/page.tsx +190 -0
  4. package/pulse/app/api/activity/route.ts +138 -0
  5. package/pulse/app/api/benchmark/route.ts +113 -0
  6. package/pulse/app/api/benchmarks/route.ts +195 -0
  7. package/pulse/app/api/costs/route.ts +88 -0
  8. package/pulse/app/api/export/route.ts +77 -0
  9. package/pulse/app/api/history/route.ts +11 -0
  10. package/pulse/app/api/import/route.ts +31 -0
  11. package/pulse/app/api/memory/route.ts +50 -0
  12. package/pulse/app/api/plans/route.ts +9 -0
  13. package/pulse/app/api/projects/[slug]/route.ts +96 -0
  14. package/pulse/app/api/projects/route.ts +121 -0
  15. package/pulse/app/api/sessions/[id]/replay/route.ts +20 -0
  16. package/pulse/app/api/sessions/[id]/route.ts +31 -0
  17. package/pulse/app/api/sessions/route.ts +112 -0
  18. package/pulse/app/api/settings/route.ts +14 -0
  19. package/pulse/app/api/stats/route.ts +143 -0
  20. package/pulse/app/api/todos/route.ts +9 -0
  21. package/pulse/app/api/tools/route.ts +160 -0
  22. package/pulse/app/benchmarks/page.tsx +224 -0
  23. package/pulse/app/costs/page.tsx +179 -0
  24. package/pulse/app/export/page.tsx +465 -0
  25. package/pulse/app/favicon.ico +0 -0
  26. package/pulse/app/globals.css +263 -0
  27. package/pulse/app/help/page.tsx +143 -0
  28. package/pulse/app/history/page.tsx +157 -0
  29. package/pulse/app/layout.tsx +46 -0
  30. package/pulse/app/memory/page.tsx +365 -0
  31. package/pulse/app/overview-client.tsx +393 -0
  32. package/pulse/app/page.tsx +14 -0
  33. package/pulse/app/plans/page.tsx +308 -0
  34. package/pulse/app/projects/[slug]/page.tsx +390 -0
  35. package/pulse/app/projects/page.tsx +110 -0
  36. package/pulse/app/sessions/[id]/page.tsx +243 -0
  37. package/pulse/app/sessions/page.tsx +39 -0
  38. package/pulse/app/settings/page.tsx +188 -0
  39. package/pulse/app/todos/page.tsx +211 -0
  40. package/pulse/app/tools/page.tsx +249 -0
  41. package/pulse/cli.js +164 -0
  42. package/pulse/components/activity/day-of-week-chart.tsx +35 -0
  43. package/pulse/components/activity/streak-card.tsx +36 -0
  44. package/pulse/components/costs/cache-efficiency-panel.tsx +76 -0
  45. package/pulse/components/costs/cost-by-project-chart.tsx +48 -0
  46. package/pulse/components/costs/cost-over-time-chart.tsx +95 -0
  47. package/pulse/components/costs/model-token-table.tsx +60 -0
  48. package/pulse/components/global-search.tsx +193 -0
  49. package/pulse/components/keyboard-nav-provider.tsx +23 -0
  50. package/pulse/components/layout/bottom-nav.tsx +53 -0
  51. package/pulse/components/layout/client-layout.tsx +31 -0
  52. package/pulse/components/layout/sidebar-context.tsx +50 -0
  53. package/pulse/components/layout/sidebar.tsx +183 -0
  54. package/pulse/components/layout/top-bar.tsx +121 -0
  55. package/pulse/components/overview/activity-heatmap.tsx +107 -0
  56. package/pulse/components/overview/conversation-table.tsx +148 -0
  57. package/pulse/components/overview/model-breakdown-donut.tsx +95 -0
  58. package/pulse/components/overview/peak-hours-chart.tsx +87 -0
  59. package/pulse/components/overview/project-activity-donut.tsx +96 -0
  60. package/pulse/components/overview/stat-card.tsx +102 -0
  61. package/pulse/components/overview/usage-over-time-chart.tsx +166 -0
  62. package/pulse/components/projects/project-card.tsx +175 -0
  63. package/pulse/components/sessions/replay/assistant-markdown.tsx +94 -0
  64. package/pulse/components/sessions/replay/compaction-card.tsx +25 -0
  65. package/pulse/components/sessions/replay/session-sidebar.tsx +231 -0
  66. package/pulse/components/sessions/replay/token-accumulation-chart.tsx +98 -0
  67. package/pulse/components/sessions/replay/tool-call-badge.tsx +127 -0
  68. package/pulse/components/sessions/replay/turn-cards.tsx +220 -0
  69. package/pulse/components/sessions/replay/user-tool-result.tsx +158 -0
  70. package/pulse/components/sessions/session-badges.tsx +49 -0
  71. package/pulse/components/sessions/session-table.tsx +299 -0
  72. package/pulse/components/theme-provider.tsx +44 -0
  73. package/pulse/components/tools/feature-adoption-table.tsx +58 -0
  74. package/pulse/components/tools/mcp-server-panel.tsx +45 -0
  75. package/pulse/components/tools/tool-ranking-chart.tsx +57 -0
  76. package/pulse/components/tools/version-history-table.tsx +32 -0
  77. package/pulse/components/ui/alert.tsx +66 -0
  78. package/pulse/components/ui/badge.tsx +48 -0
  79. package/pulse/components/ui/breadcrumb.tsx +109 -0
  80. package/pulse/components/ui/button.tsx +64 -0
  81. package/pulse/components/ui/calendar.tsx +220 -0
  82. package/pulse/components/ui/card.tsx +92 -0
  83. package/pulse/components/ui/command.tsx +158 -0
  84. package/pulse/components/ui/dialog.tsx +158 -0
  85. package/pulse/components/ui/input.tsx +21 -0
  86. package/pulse/components/ui/popover.tsx +89 -0
  87. package/pulse/components/ui/progress.tsx +31 -0
  88. package/pulse/components/ui/select.tsx +190 -0
  89. package/pulse/components/ui/separator.tsx +28 -0
  90. package/pulse/components/ui/sheet.tsx +143 -0
  91. package/pulse/components/ui/skeleton.tsx +13 -0
  92. package/pulse/components/ui/table.tsx +116 -0
  93. package/pulse/components/ui/tabs.tsx +91 -0
  94. package/pulse/components/ui/tooltip.tsx +57 -0
  95. package/pulse/components/use-global-keyboard-nav.ts +79 -0
  96. package/pulse/components.json +23 -0
  97. package/pulse/eslint.config.mjs +18 -0
  98. package/pulse/lib/bahulam-paths.ts +23 -0
  99. package/pulse/lib/claude-reader.ts +592 -0
  100. package/pulse/lib/decode.ts +129 -0
  101. package/pulse/lib/pricing.ts +102 -0
  102. package/pulse/lib/replay-parser.ts +165 -0
  103. package/pulse/lib/tool-categories.ts +127 -0
  104. package/pulse/lib/utils.ts +6 -0
  105. package/pulse/next-env.d.ts +6 -0
  106. package/pulse/next.config.ts +16 -0
  107. package/pulse/package.json +45 -0
  108. package/pulse/postcss.config.mjs +7 -0
  109. package/pulse/public/activity.png +0 -0
  110. package/pulse/public/cc-lens.png +0 -0
  111. package/pulse/public/command-k.png +0 -0
  112. package/pulse/public/costs.png +0 -0
  113. package/pulse/public/dashboard-dark.png +0 -0
  114. package/pulse/public/dashboard-white.png +0 -0
  115. package/pulse/public/export.png +0 -0
  116. package/pulse/public/file.svg +1 -0
  117. package/pulse/public/globe.svg +1 -0
  118. package/pulse/public/next.svg +1 -0
  119. package/pulse/public/projects.png +0 -0
  120. package/pulse/public/session-chat.png +0 -0
  121. package/pulse/public/todos.png +0 -0
  122. package/pulse/public/tools.png +0 -0
  123. package/pulse/public/vercel.svg +1 -0
  124. package/pulse/public/window.svg +1 -0
  125. package/pulse/tsconfig.json +34 -0
  126. package/pulse/types/claude.ts +294 -0
  127. package/src/agents/loader.mjs +94 -0
  128. package/src/agents/multi_workflow_loader.mjs +330 -0
  129. package/src/agents/parser.mjs +205 -0
  130. package/src/agents/scaffold.mjs +222 -0
  131. package/src/agents/teams.mjs +123 -0
  132. package/src/agents/workflow_loader.mjs +122 -0
  133. package/src/agents/workflow_scaffold.mjs +249 -0
  134. package/src/auth/oauth.mjs +220 -0
  135. package/src/auth/tarang-auth.mjs +306 -0
  136. package/src/commands/agent.mjs +220 -0
  137. package/src/commands/workflow.mjs +581 -0
  138. package/src/config/cli-args.mjs +200 -0
  139. package/src/config/env.mjs +263 -0
  140. package/src/config/hook-runner.mjs +100 -0
  141. package/src/config/memory-loader.mjs +32 -0
  142. package/src/config/settings-loader.mjs +45 -0
  143. package/src/config/settings.mjs +132 -0
  144. package/src/context/ast-parser.mjs +298 -0
  145. package/src/context/bm25.mjs +85 -0
  146. package/src/context/retriever.mjs +308 -0
  147. package/src/context/skeleton.mjs +134 -0
  148. package/src/context/symbol-indexer.mjs +375 -0
  149. package/src/core/agent-history.mjs +111 -0
  150. package/src/core/agent-loop.mjs +486 -0
  151. package/src/core/approval-log.mjs +104 -0
  152. package/src/core/approval.mjs +476 -0
  153. package/src/core/attachments.mjs +380 -0
  154. package/src/core/backend-url.mjs +55 -0
  155. package/src/core/cache-control.mjs +92 -0
  156. package/src/core/cache.mjs +105 -0
  157. package/src/core/callback-client.mjs +180 -0
  158. package/src/core/checkpoints.mjs +142 -0
  159. package/src/core/compact-history.mjs +127 -0
  160. package/src/core/context-envelope.mjs +54 -0
  161. package/src/core/context-manager.mjs +198 -0
  162. package/src/core/error-guidance.mjs +311 -0
  163. package/src/core/file-diff.mjs +217 -0
  164. package/src/core/headless.mjs +448 -0
  165. package/src/core/hooks-manager.mjs +87 -0
  166. package/src/core/jsonl-writer.mjs +449 -0
  167. package/src/core/local-agent.mjs +537 -0
  168. package/src/core/local-store.mjs +836 -0
  169. package/src/core/mode-selector.mjs +51 -0
  170. package/src/core/output-filter.mjs +177 -0
  171. package/src/core/paths.mjs +190 -0
  172. package/src/core/policy-resolver.mjs +156 -0
  173. package/src/core/pricing.mjs +336 -0
  174. package/src/core/project-artifacts.mjs +39 -0
  175. package/src/core/project-context-loader.mjs +139 -0
  176. package/src/core/providers.mjs +219 -0
  177. package/src/core/rate-limit-display.mjs +121 -0
  178. package/src/core/rate-limiter.mjs +119 -0
  179. package/src/core/resume-mode.mjs +192 -0
  180. package/src/core/risk-tier.mjs +337 -0
  181. package/src/core/safety.mjs +203 -0
  182. package/src/core/scheduler.mjs +173 -0
  183. package/src/core/session-manager.mjs +360 -0
  184. package/src/core/session.mjs +143 -0
  185. package/src/core/settings-sync.mjs +85 -0
  186. package/src/core/stagnation.mjs +57 -0
  187. package/src/core/stream-client.mjs +829 -0
  188. package/src/core/streaming.mjs +182 -0
  189. package/src/core/system-prompt.mjs +140 -0
  190. package/src/core/tasks.mjs +196 -0
  191. package/src/core/tool-executor.mjs +1950 -0
  192. package/src/core/trust.mjs +158 -0
  193. package/src/core/work-scope.mjs +248 -0
  194. package/src/hooks/engine.mjs +162 -0
  195. package/src/index.mjs +426 -0
  196. package/src/mcp/client.mjs +253 -0
  197. package/src/mcp/transport-shttp.mjs +130 -0
  198. package/src/mcp/transport-sse.mjs +131 -0
  199. package/src/mcp/transport-ws.mjs +134 -0
  200. package/src/onboarding/preflight.mjs +360 -0
  201. package/src/permissions/checker.mjs +57 -0
  202. package/src/permissions/command-classifier.mjs +652 -0
  203. package/src/permissions/injection-check.mjs +60 -0
  204. package/src/permissions/path-check.mjs +102 -0
  205. package/src/permissions/prompt.mjs +73 -0
  206. package/src/permissions/sandbox.mjs +112 -0
  207. package/src/plugins/loader.mjs +138 -0
  208. package/src/skills/installer.mjs +188 -0
  209. package/src/skills/loader.mjs +252 -0
  210. package/src/skills/runner.mjs +55 -0
  211. package/src/state/orbit.mjs +263 -0
  212. package/src/state/verbosity.mjs +99 -0
  213. package/src/telemetry/index.mjs +96 -0
  214. package/src/terminal/agents.mjs +177 -0
  215. package/src/terminal/analytics.mjs +292 -0
  216. package/src/terminal/ansi.mjs +695 -0
  217. package/src/terminal/init.mjs +145 -0
  218. package/src/terminal/main.mjs +269 -0
  219. package/src/terminal/repl-explore.mjs +35 -0
  220. package/src/terminal/repl-format.mjs +257 -0
  221. package/src/terminal/repl-render.mjs +561 -0
  222. package/src/terminal/repl-resume.mjs +625 -0
  223. package/src/terminal/repl-state.mjs +103 -0
  224. package/src/terminal/repl-utils.mjs +34 -0
  225. package/src/terminal/repl.mjs +3832 -0
  226. package/src/terminal/skills.mjs +54 -0
  227. package/src/terminal/tool-display.mjs +240 -0
  228. package/src/tools/agent.mjs +137 -0
  229. package/src/tools/ask-user.mjs +61 -0
  230. package/src/tools/bash.mjs +231 -0
  231. package/src/tools/cron-create.mjs +120 -0
  232. package/src/tools/cron-delete.mjs +49 -0
  233. package/src/tools/cron-list.mjs +37 -0
  234. package/src/tools/edit.mjs +82 -0
  235. package/src/tools/enter-worktree.mjs +69 -0
  236. package/src/tools/exit-worktree.mjs +57 -0
  237. package/src/tools/glob.mjs +117 -0
  238. package/src/tools/grep.mjs +129 -0
  239. package/src/tools/lint.mjs +71 -0
  240. package/src/tools/ls.mjs +58 -0
  241. package/src/tools/lsp.mjs +115 -0
  242. package/src/tools/multi-edit.mjs +94 -0
  243. package/src/tools/notebook-edit.mjs +96 -0
  244. package/src/tools/project-overview.mjs +641 -0
  245. package/src/tools/read-mcp-resource.mjs +57 -0
  246. package/src/tools/read.mjs +138 -0
  247. package/src/tools/registry.mjs +116 -0
  248. package/src/tools/remote-trigger.mjs +84 -0
  249. package/src/tools/send-message.mjs +64 -0
  250. package/src/tools/skill.mjs +52 -0
  251. package/src/tools/test-runner.mjs +49 -0
  252. package/src/tools/todo-write.mjs +68 -0
  253. package/src/tools/tool-search.mjs +77 -0
  254. package/src/tools/web-fetch.mjs +65 -0
  255. package/src/tools/web-search.mjs +89 -0
  256. package/src/tools/write.mjs +55 -0
  257. package/src/ui/approval.mjs +263 -0
  258. package/src/ui/banner.mjs +235 -0
  259. package/src/ui/commands.mjs +537 -0
  260. package/src/ui/formatter.mjs +409 -0
  261. package/src/ui/icons.mjs +164 -0
  262. package/src/ui/input-dock.mjs +444 -0
  263. package/src/ui/markdown.mjs +278 -0
  264. package/src/ui/mission-report.mjs +296 -0
  265. package/src/ui/palette.mjs +189 -0
  266. package/src/ui/slash-commands.mjs +245 -0
  267. package/src/ui/spinner.mjs +116 -0
  268. package/src/ui/sub-agent.mjs +152 -0
  269. package/src/ui/term.mjs +159 -0
  270. package/src/ui/text-layout.mjs +127 -0
  271. package/src/ui/tool-card.mjs +463 -0
  272. package/src/ui/tool-details.mjs +312 -0
  273. package/src/ui/transcript-block.mjs +21 -0
@@ -0,0 +1,138 @@
1
+ /**
2
+ * Read Tool — matches Claude Code's exact behavior.
3
+ *
4
+ * Features:
5
+ * - pages parameter for PDF files
6
+ * - Binary file detection
7
+ * - Default 2000 line limit
8
+ * - Line number prefix (cat -n format)
9
+ * - Graceful file not found handling
10
+ * - Tracks read files for Edit/Write verification
11
+ */
12
+ import fs from 'fs';
13
+ import path from 'path';
14
+
15
+ const DEFAULT_LIMIT = 2000;
16
+
17
+ // Track which files have been read (used by Edit and Write tools)
18
+ const readFiles = new Set();
19
+
20
+ export function hasBeenRead(filePath) {
21
+ return readFiles.has(path.resolve(filePath));
22
+ }
23
+
24
+ export function markRead(filePath) {
25
+ readFiles.add(path.resolve(filePath));
26
+ }
27
+
28
+ // Binary detection: check for null bytes in first 8KB
29
+ function isBinary(buffer) {
30
+ const len = Math.min(buffer.length, 8192);
31
+ for (let i = 0; i < len; i++) {
32
+ if (buffer[i] === 0) return true;
33
+ }
34
+ return false;
35
+ }
36
+
37
+ export const ReadTool = {
38
+ name: 'Read',
39
+ description: 'Read a file from the local filesystem.',
40
+ inputSchema: {
41
+ type: 'object',
42
+ properties: {
43
+ file_path: { type: 'string', description: 'Absolute path to the file' },
44
+ offset: { type: 'number', description: 'Line number to start reading from (0-based)' },
45
+ limit: { type: 'number', description: 'Number of lines to read (default 2000)' },
46
+ pages: { type: 'string', description: 'Page range for PDF files (e.g. "1-5")' },
47
+ },
48
+ required: ['file_path'],
49
+ },
50
+ validateInput(input) {
51
+ const errors = [];
52
+ if (!input.file_path) errors.push('file_path is required');
53
+ return errors;
54
+ },
55
+ async call(input) {
56
+ const filePath = path.resolve(input.file_path);
57
+
58
+ // Check existence
59
+ if (!fs.existsSync(filePath)) {
60
+ return `Error: File not found: ${filePath}`;
61
+ }
62
+
63
+ // Check if directory
64
+ const stat = fs.statSync(filePath);
65
+ if (stat.isDirectory()) {
66
+ return `Error: ${filePath} is a directory, not a file. Use Bash with ls to list directory contents.`;
67
+ }
68
+
69
+ // PDF handling
70
+ if (filePath.endsWith('.pdf')) {
71
+ return readPdf(filePath, input.pages);
72
+ }
73
+
74
+ // Binary detection
75
+ try {
76
+ const fd = fs.openSync(filePath, 'r');
77
+ const buf = Buffer.alloc(8192);
78
+ const bytesRead = fs.readSync(fd, buf, 0, 8192, 0);
79
+ fs.closeSync(fd);
80
+ if (isBinary(buf.subarray(0, bytesRead))) {
81
+ return `Error: ${filePath} appears to be a binary file. Cannot display binary content.`;
82
+ }
83
+ } catch (e) {
84
+ return `Error: ${e.message}`;
85
+ }
86
+
87
+ try {
88
+ const content = fs.readFileSync(filePath, 'utf-8');
89
+ const lines = content.split('\n');
90
+ const start = input.offset || 0;
91
+ const limit = input.limit || DEFAULT_LIMIT;
92
+ const end = Math.min(start + limit, lines.length);
93
+
94
+ // Track as read
95
+ readFiles.add(filePath);
96
+
97
+ // Handle empty files
98
+ if (content === '' || (content.length === 0)) {
99
+ return '[File exists but is empty]';
100
+ }
101
+
102
+ const output = lines
103
+ .slice(start, end)
104
+ .map((l, i) => `${start + i + 1}\t${l}`)
105
+ .join('\n');
106
+
107
+ if (end < lines.length) {
108
+ return output + `\n\n[File has ${lines.length} lines total. Showing lines ${start + 1}-${end}. Use offset/limit for more.]`;
109
+ }
110
+
111
+ return output;
112
+ } catch (e) {
113
+ return `Error: ${e.message}`;
114
+ }
115
+ },
116
+ };
117
+
118
+ function readPdf(filePath, pages) {
119
+ // PDF reading requires external tools; provide a best-effort
120
+ // text extraction using a simple approach
121
+ try {
122
+ const { execSync } = await_import_child_process();
123
+ const pageArg = pages ? `-f ${pages.split('-')[0]} -l ${pages.split('-').pop()}` : '-f 1 -l 20';
124
+ const text = execSync(`pdftotext ${pageArg} "${filePath}" - 2>/dev/null`, {
125
+ encoding: 'utf-8',
126
+ timeout: 30000,
127
+ maxBuffer: 1024 * 1024,
128
+ });
129
+ return text || `[PDF file at ${filePath} — could not extract text. Use a PDF viewer.]`;
130
+ } catch {
131
+ return `[PDF file at ${filePath} — pdftotext not available. Install poppler-utils for PDF support.]`;
132
+ }
133
+ }
134
+
135
+ // Lazy import helper
136
+ function await_import_child_process() {
137
+ return require('child_process');
138
+ }
@@ -0,0 +1,116 @@
1
+ /**
2
+ * Tool Registry — validateInput/call interface.
3
+ * Mirrors Claude Code's tool dispatch system.
4
+ * Registers all 25+ built-in tools.
5
+ */
6
+
7
+ import { BashTool } from './bash.mjs';
8
+ import { ReadTool } from './read.mjs';
9
+ import { EditTool } from './edit.mjs';
10
+ import { WriteTool } from './write.mjs';
11
+ import { GlobTool } from './glob.mjs';
12
+ import { GrepTool } from './grep.mjs';
13
+ import { AgentTool } from './agent.mjs';
14
+ import { WebFetchTool } from './web-fetch.mjs';
15
+ import { WebSearchTool } from './web-search.mjs';
16
+ import { TodoWriteTool } from './todo-write.mjs';
17
+ import { NotebookEditTool } from './notebook-edit.mjs';
18
+ import { MultiEditTool } from './multi-edit.mjs';
19
+ import { LsTool } from './ls.mjs';
20
+ import { ToolSearchTool } from './tool-search.mjs';
21
+ import { AskUserTool } from './ask-user.mjs';
22
+ import { EnterWorktreeTool } from './enter-worktree.mjs';
23
+ import { ExitWorktreeTool } from './exit-worktree.mjs';
24
+ import { SkillTool } from './skill.mjs';
25
+ import { SendMessageTool } from './send-message.mjs';
26
+ import { RemoteTriggerTool } from './remote-trigger.mjs';
27
+ import { CronCreateTool } from './cron-create.mjs';
28
+ import { CronDeleteTool } from './cron-delete.mjs';
29
+ import { CronListTool } from './cron-list.mjs';
30
+ import { LspTool } from './lsp.mjs';
31
+ import { ReadMcpResourceTool } from './read-mcp-resource.mjs';
32
+
33
+ const BUILTIN_TOOLS = [
34
+ BashTool,
35
+ ReadTool,
36
+ EditTool,
37
+ WriteTool,
38
+ GlobTool,
39
+ GrepTool,
40
+ AgentTool,
41
+ WebFetchTool,
42
+ WebSearchTool,
43
+ TodoWriteTool,
44
+ NotebookEditTool,
45
+ MultiEditTool,
46
+ LsTool,
47
+ ToolSearchTool,
48
+ AskUserTool,
49
+ EnterWorktreeTool,
50
+ ExitWorktreeTool,
51
+ SkillTool,
52
+ SendMessageTool,
53
+ RemoteTriggerTool,
54
+ CronCreateTool,
55
+ CronDeleteTool,
56
+ CronListTool,
57
+ LspTool,
58
+ ReadMcpResourceTool,
59
+ ];
60
+
61
+ export function createToolRegistry() {
62
+ const tools = new Map();
63
+ for (const Tool of BUILTIN_TOOLS) {
64
+ tools.set(Tool.name, Tool);
65
+ }
66
+
67
+ const registry = {
68
+ list() {
69
+ return [...tools.values()].map(t => ({
70
+ name: t.name,
71
+ description: t.description,
72
+ input_schema: t.inputSchema,
73
+ }));
74
+ },
75
+
76
+ async call(name, input) {
77
+ const tool = tools.get(name);
78
+ if (!tool) throw new Error(`Unknown tool: ${name}`);
79
+ const errors = tool.validateInput?.(input) || [];
80
+ if (errors.length > 0) return `Validation error: ${errors.join(', ')}`;
81
+ const result = await tool.call(input);
82
+
83
+ return result;
84
+ },
85
+
86
+ register(tool) {
87
+ tools.set(tool.name, tool);
88
+ },
89
+
90
+ get(name) {
91
+ return tools.get(name);
92
+ },
93
+
94
+ has(name) {
95
+ return tools.has(name);
96
+ },
97
+
98
+ registerMcpTools(mcpTools, callFn) {
99
+ ToolSearchTool._mcpTools = mcpTools;
100
+
101
+ for (const mcpTool of mcpTools) {
102
+ const wrapper = {
103
+ name: mcpTool.name,
104
+ description: mcpTool.description || '',
105
+ inputSchema: mcpTool.inputSchema || { type: 'object', properties: {} },
106
+ validateInput() { return []; },
107
+ async call(input) { return callFn(mcpTool.name, input); },
108
+ };
109
+ tools.set(mcpTool.name, wrapper);
110
+ }
111
+ },
112
+ };
113
+
114
+ ToolSearchTool._registry = registry;
115
+ return registry;
116
+ }
@@ -0,0 +1,84 @@
1
+ /**
2
+ * RemoteTrigger Tool — trigger remote execution of a task.
3
+ *
4
+ * Sends a task to a remote agent endpoint for execution.
5
+ * Used for distributed agent workflows and scheduled tasks.
6
+ */
7
+
8
+ export const RemoteTriggerTool = {
9
+ name: 'RemoteTrigger',
10
+ description: 'Trigger remote execution of a task on a remote agent.',
11
+ inputSchema: {
12
+ type: 'object',
13
+ properties: {
14
+ endpoint: {
15
+ type: 'string',
16
+ description: 'Remote agent endpoint URL',
17
+ },
18
+ task: {
19
+ type: 'string',
20
+ description: 'Task description or prompt to execute remotely',
21
+ },
22
+ timeout: {
23
+ type: 'number',
24
+ description: 'Timeout in milliseconds (default: 300000)',
25
+ },
26
+ async: {
27
+ type: 'boolean',
28
+ description: 'Fire-and-forget mode (default: false)',
29
+ },
30
+ },
31
+ required: ['task'],
32
+ },
33
+
34
+ validateInput(input) {
35
+ return input.task ? [] : ['task is required'];
36
+ },
37
+
38
+ async call(input) {
39
+ const endpoint = input.endpoint || process.env.REMOTE_AGENT_URL;
40
+ if (!endpoint) {
41
+ return 'No remote endpoint configured. Set endpoint parameter or REMOTE_AGENT_URL env var.';
42
+ }
43
+
44
+ try {
45
+ const controller = new AbortController();
46
+ const timeout = setTimeout(
47
+ () => controller.abort(),
48
+ input.timeout || 300000
49
+ );
50
+
51
+ const res = await fetch(endpoint, {
52
+ method: 'POST',
53
+ headers: {
54
+ 'Content-Type': 'application/json',
55
+ ...(process.env.REMOTE_AGENT_TOKEN && {
56
+ Authorization: `Bearer ${process.env.REMOTE_AGENT_TOKEN}`,
57
+ }),
58
+ },
59
+ body: JSON.stringify({
60
+ task: input.task,
61
+ async: input.async || false,
62
+ }),
63
+ signal: controller.signal,
64
+ });
65
+
66
+ clearTimeout(timeout);
67
+
68
+ if (!res.ok) {
69
+ return `Remote error: HTTP ${res.status} ${res.statusText}`;
70
+ }
71
+
72
+ if (input.async) {
73
+ return `Task submitted to ${endpoint} (async mode)`;
74
+ }
75
+
76
+ const data = await res.json();
77
+ return typeof data.result === 'string'
78
+ ? data.result
79
+ : JSON.stringify(data, null, 2);
80
+ } catch (err) {
81
+ return `Remote trigger error: ${err.message}`;
82
+ }
83
+ },
84
+ };
@@ -0,0 +1,64 @@
1
+ /**
2
+ * SendMessage Tool — send a message to a teammate agent.
3
+ *
4
+ * Used in multi-agent (teams) mode to communicate between agents.
5
+ * Messages are stored in a shared message queue.
6
+ */
7
+
8
+ const messageQueue = new Map(); // agentId -> messages[]
9
+
10
+ export const SendMessageTool = {
11
+ name: 'SendMessage',
12
+ description: 'Send a message to a teammate agent in multi-agent mode.',
13
+ inputSchema: {
14
+ type: 'object',
15
+ properties: {
16
+ to: {
17
+ type: 'string',
18
+ description: 'Target agent ID or name',
19
+ },
20
+ content: {
21
+ type: 'string',
22
+ description: 'Message content to send',
23
+ },
24
+ type: {
25
+ type: 'string',
26
+ enum: ['request', 'response', 'notification', 'handoff'],
27
+ description: 'Message type (default: notification)',
28
+ },
29
+ },
30
+ required: ['to', 'content'],
31
+ },
32
+
33
+ validateInput(input) {
34
+ const errors = [];
35
+ if (!input.to) errors.push('to is required');
36
+ if (!input.content) errors.push('content is required');
37
+ return errors;
38
+ },
39
+
40
+ async call(input) {
41
+ const msg = {
42
+ id: `msg_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
43
+ from: process.env.AGENT_ID || 'main',
44
+ to: input.to,
45
+ type: input.type || 'notification',
46
+ content: input.content,
47
+ timestamp: new Date().toISOString(),
48
+ };
49
+
50
+ if (!messageQueue.has(input.to)) {
51
+ messageQueue.set(input.to, []);
52
+ }
53
+ messageQueue.get(input.to).push(msg);
54
+
55
+ return `Message sent to "${input.to}" (id: ${msg.id}, type: ${msg.type})`;
56
+ },
57
+
58
+ // Helper: receive messages for an agent
59
+ receive(agentId) {
60
+ const messages = messageQueue.get(agentId) || [];
61
+ messageQueue.set(agentId, []);
62
+ return messages;
63
+ },
64
+ };
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Skill Tool — invoke a loaded skill by name.
3
+ *
4
+ * Skills are loaded from .claude/skills/{name}/SKILL.md and provide
5
+ * specialized prompts injected into the conversation.
6
+ */
7
+
8
+ export const SkillTool = {
9
+ name: 'Skill',
10
+ description: 'Invoke a skill within the conversation. Skills provide specialized capabilities.',
11
+ inputSchema: {
12
+ type: 'object',
13
+ properties: {
14
+ skill: {
15
+ type: 'string',
16
+ description: 'The skill name to invoke (e.g., "commit", "review-pr")',
17
+ },
18
+ args: {
19
+ type: 'string',
20
+ description: 'Optional arguments for the skill',
21
+ },
22
+ },
23
+ required: ['skill'],
24
+ },
25
+
26
+ // Set by skills loader
27
+ _skillsLoader: null,
28
+
29
+ validateInput(input) {
30
+ return input.skill ? [] : ['skill name is required'];
31
+ },
32
+
33
+ async call(input) {
34
+ if (!this._skillsLoader) {
35
+ return 'Skills system not initialized. No skills available.';
36
+ }
37
+
38
+ try {
39
+ const skill = this._skillsLoader.get(input.skill);
40
+ if (!skill) {
41
+ const available = this._skillsLoader.list();
42
+ const names = available.map(s => s.name).join(', ');
43
+ return `Unknown skill: "${input.skill}". Available: ${names || 'none'}`;
44
+ }
45
+
46
+ const result = await this._skillsLoader.run(input.skill, input.args);
47
+ return result;
48
+ } catch (err) {
49
+ return `Skill error: ${err.message}`;
50
+ }
51
+ },
52
+ };
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Test Runner Tool — run test suites in contained environment.
3
+ *
4
+ * Executes in .venv/sandbox when available.
5
+ * Agent should use this instead of shell("pytest tests/").
6
+ *
7
+ * Execution: contained (.venv + OS sandbox if available).
8
+ */
9
+
10
+ import { BashTool } from './bash.mjs';
11
+
12
+ export const TestRunnerTool = {
13
+ name: 'TestRunner',
14
+ description: 'Run tests. Executes in project environment. Use this instead of Bash for pytest, jest, vitest, cargo test, go test.',
15
+ inputSchema: {
16
+ type: 'object',
17
+ properties: {
18
+ command: {
19
+ type: 'string',
20
+ description: 'Test command (e.g., "pytest tests/test_auth.py -v", "npm test", "cargo test")',
21
+ },
22
+ timeout: {
23
+ type: 'number',
24
+ description: 'Timeout in ms (default: 120000, max: 300000)',
25
+ default: 120000,
26
+ },
27
+ },
28
+ required: ['command'],
29
+ },
30
+ validateInput(input) {
31
+ const errors = [];
32
+ if (!input.command) errors.push('command is required');
33
+ return errors;
34
+ },
35
+ async call(input) {
36
+ const timeout = Math.min(input.timeout || 120000, 300000);
37
+
38
+ // TODO: Wire sandbox.mjs here when sandbox execution is ready
39
+ // For now, execute directly (same as Bash but with metadata)
40
+ const result = await BashTool.call({
41
+ command: input.command,
42
+ timeout,
43
+ description: `Test: ${input.command.slice(0, 60)}`,
44
+ });
45
+
46
+ return result;
47
+ },
48
+ _executionPolicy: 'contained',
49
+ };
@@ -0,0 +1,68 @@
1
+ /**
2
+ * TodoWrite Tool — in-memory task management.
3
+ *
4
+ * Maintains a task list that the agent can use to track work items.
5
+ * State persists for the duration of the session.
6
+ */
7
+
8
+ const todos = [];
9
+ let nextId = 1;
10
+
11
+ export const TodoWriteTool = {
12
+ name: 'TodoWrite',
13
+ description: 'Manage a task list. Supports add, update, complete, and list operations.',
14
+ inputSchema: {
15
+ type: 'object',
16
+ properties: {
17
+ todos: {
18
+ type: 'array',
19
+ items: {
20
+ type: 'object',
21
+ properties: {
22
+ id: { type: 'string', description: 'Task ID (auto-assigned if adding)' },
23
+ content: { type: 'string', description: 'Task description' },
24
+ status: {
25
+ type: 'string',
26
+ enum: ['pending', 'in_progress', 'completed'],
27
+ description: 'Task status',
28
+ },
29
+ priority: {
30
+ type: 'string',
31
+ enum: ['high', 'medium', 'low'],
32
+ description: 'Task priority',
33
+ },
34
+ },
35
+ required: ['content', 'status'],
36
+ },
37
+ description: 'Array of todo items to write (replaces all todos)',
38
+ },
39
+ },
40
+ required: ['todos'],
41
+ },
42
+
43
+ validateInput(input) {
44
+ if (!input.todos || !Array.isArray(input.todos)) return ['todos must be an array'];
45
+ return [];
46
+ },
47
+
48
+ async call(input) {
49
+ // Replace entire todo list (matches Claude Code behavior)
50
+ todos.length = 0;
51
+ nextId = 1;
52
+
53
+ for (const item of input.todos) {
54
+ todos.push({
55
+ id: item.id || String(nextId++),
56
+ content: item.content,
57
+ status: item.status || 'pending',
58
+ priority: item.priority || 'medium',
59
+ });
60
+ }
61
+
62
+ const summary = todos.map(t =>
63
+ `[${t.status === 'completed' ? 'x' : t.status === 'in_progress' ? '~' : ' '}] ${t.id}. ${t.content} (${t.priority})`
64
+ ).join('\n');
65
+
66
+ return `Updated ${todos.length} todos:\n${summary}`;
67
+ },
68
+ };
@@ -0,0 +1,77 @@
1
+ /**
2
+ * ToolSearch Tool — deferred tool loading / search.
3
+ *
4
+ * Allows the agent to discover and load tools that are not
5
+ * in the default set. Supports searching MCP servers and
6
+ * deferred tool definitions.
7
+ */
8
+
9
+ export const ToolSearchTool = {
10
+ name: 'ToolSearch',
11
+ description: 'Search for and load deferred tools by name or keyword.',
12
+ inputSchema: {
13
+ type: 'object',
14
+ properties: {
15
+ query: {
16
+ type: 'string',
17
+ description: 'Tool name or search keywords',
18
+ },
19
+ max_results: {
20
+ type: 'number',
21
+ description: 'Maximum results to return (default: 5)',
22
+ },
23
+ },
24
+ required: ['query'],
25
+ },
26
+
27
+ // Will be set by the registry when MCP tools are available
28
+ _mcpTools: [],
29
+ _registry: null,
30
+
31
+ validateInput(input) {
32
+ return input.query ? [] : ['query is required'];
33
+ },
34
+
35
+ async call(input) {
36
+ const query = input.query.toLowerCase();
37
+ const maxResults = input.max_results || 5;
38
+ const matches = [];
39
+
40
+ // Search MCP tools
41
+ for (const tool of this._mcpTools) {
42
+ const name = (tool.name || '').toLowerCase();
43
+ const desc = (tool.description || '').toLowerCase();
44
+ if (name.includes(query) || desc.includes(query)) {
45
+ matches.push({
46
+ name: tool.name,
47
+ description: tool.description,
48
+ source: 'mcp',
49
+ });
50
+ }
51
+ }
52
+
53
+ // Search registry tools
54
+ if (this._registry) {
55
+ for (const tool of this._registry.list()) {
56
+ const name = (tool.name || '').toLowerCase();
57
+ const desc = (tool.description || '').toLowerCase();
58
+ if (name.includes(query) || desc.includes(query)) {
59
+ matches.push({
60
+ name: tool.name,
61
+ description: tool.description,
62
+ source: 'builtin',
63
+ });
64
+ }
65
+ }
66
+ }
67
+
68
+ if (matches.length === 0) {
69
+ return `No tools found matching "${input.query}"`;
70
+ }
71
+
72
+ const limited = matches.slice(0, maxResults);
73
+ return limited.map(m =>
74
+ `[${m.source}] ${m.name}: ${m.description}`
75
+ ).join('\n');
76
+ },
77
+ };