@bahulam/code 0.1.1

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 (278) hide show
  1. package/README.md +93 -0
  2. package/package.json +56 -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 +223 -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 +312 -0
  136. package/src/commands/agent.mjs +221 -0
  137. package/src/commands/workflow.mjs +581 -0
  138. package/src/config/cli-args.mjs +202 -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/model-catalog.mjs +57 -0
  143. package/src/config/settings-loader.mjs +45 -0
  144. package/src/config/settings.mjs +132 -0
  145. package/src/context/ast-parser.mjs +298 -0
  146. package/src/context/bm25.mjs +85 -0
  147. package/src/context/prose-chunker.mjs +255 -0
  148. package/src/context/retriever.mjs +425 -0
  149. package/src/context/skeleton.mjs +134 -0
  150. package/src/context/symbol-indexer.mjs +375 -0
  151. package/src/core/agent-history.mjs +111 -0
  152. package/src/core/agent-loop.mjs +486 -0
  153. package/src/core/approval-log.mjs +145 -0
  154. package/src/core/approval.mjs +700 -0
  155. package/src/core/attachments.mjs +666 -0
  156. package/src/core/backend-url.mjs +68 -0
  157. package/src/core/bundled-runtime.mjs +418 -0
  158. package/src/core/cache-control.mjs +92 -0
  159. package/src/core/cache.mjs +105 -0
  160. package/src/core/callback-client.mjs +180 -0
  161. package/src/core/checkpoints.mjs +142 -0
  162. package/src/core/compact-history.mjs +127 -0
  163. package/src/core/context-envelope.mjs +54 -0
  164. package/src/core/context-manager.mjs +198 -0
  165. package/src/core/error-guidance.mjs +331 -0
  166. package/src/core/file-diff.mjs +217 -0
  167. package/src/core/headless.mjs +460 -0
  168. package/src/core/hooks-manager.mjs +87 -0
  169. package/src/core/jsonl-writer.mjs +449 -0
  170. package/src/core/local-agent.mjs +538 -0
  171. package/src/core/local-store.mjs +836 -0
  172. package/src/core/mode-selector.mjs +51 -0
  173. package/src/core/output-filter.mjs +177 -0
  174. package/src/core/paths.mjs +190 -0
  175. package/src/core/policy-resolver.mjs +156 -0
  176. package/src/core/pricing.mjs +336 -0
  177. package/src/core/project-artifacts.mjs +39 -0
  178. package/src/core/project-context-loader.mjs +139 -0
  179. package/src/core/providers.mjs +219 -0
  180. package/src/core/rate-limit-display.mjs +121 -0
  181. package/src/core/rate-limiter.mjs +119 -0
  182. package/src/core/resume-mode.mjs +192 -0
  183. package/src/core/risk-tier.mjs +388 -0
  184. package/src/core/safety.mjs +260 -0
  185. package/src/core/scheduler.mjs +173 -0
  186. package/src/core/session-manager.mjs +360 -0
  187. package/src/core/session.mjs +143 -0
  188. package/src/core/settings-sync.mjs +85 -0
  189. package/src/core/stagnation.mjs +57 -0
  190. package/src/core/stream-client.mjs +957 -0
  191. package/src/core/streaming.mjs +182 -0
  192. package/src/core/system-prompt.mjs +140 -0
  193. package/src/core/tasks.mjs +196 -0
  194. package/src/core/tool-executor.mjs +2231 -0
  195. package/src/core/trust.mjs +160 -0
  196. package/src/core/work-scope.mjs +248 -0
  197. package/src/hooks/engine.mjs +162 -0
  198. package/src/mcp/client.mjs +253 -0
  199. package/src/mcp/transport-shttp.mjs +130 -0
  200. package/src/mcp/transport-sse.mjs +131 -0
  201. package/src/mcp/transport-ws.mjs +134 -0
  202. package/src/onboarding/preflight.mjs +374 -0
  203. package/src/permissions/checker.mjs +57 -0
  204. package/src/permissions/command-classifier.mjs +700 -0
  205. package/src/permissions/injection-check.mjs +60 -0
  206. package/src/permissions/path-check.mjs +102 -0
  207. package/src/permissions/prompt.mjs +73 -0
  208. package/src/permissions/sandbox.mjs +112 -0
  209. package/src/plugins/loader.mjs +138 -0
  210. package/src/skills/installer.mjs +188 -0
  211. package/src/skills/loader.mjs +252 -0
  212. package/src/skills/runner.mjs +55 -0
  213. package/src/state/orbit.mjs +263 -0
  214. package/src/state/verbosity.mjs +99 -0
  215. package/src/telemetry/index.mjs +122 -0
  216. package/src/terminal/agents.mjs +353 -0
  217. package/src/terminal/analytics.mjs +292 -0
  218. package/src/terminal/ansi.mjs +695 -0
  219. package/src/terminal/init.mjs +145 -0
  220. package/src/terminal/main.mjs +310 -0
  221. package/src/terminal/repl-ask-form.mjs +120 -0
  222. package/src/terminal/repl-explore.mjs +44 -0
  223. package/src/terminal/repl-format.mjs +317 -0
  224. package/src/terminal/repl-model-form.mjs +132 -0
  225. package/src/terminal/repl-render.mjs +833 -0
  226. package/src/terminal/repl-resume.mjs +640 -0
  227. package/src/terminal/repl-state.mjs +120 -0
  228. package/src/terminal/repl-utils.mjs +34 -0
  229. package/src/terminal/repl.mjs +5032 -0
  230. package/src/terminal/skills.mjs +54 -0
  231. package/src/terminal/tool-display.mjs +392 -0
  232. package/src/tools/agent.mjs +137 -0
  233. package/src/tools/ask-user.mjs +61 -0
  234. package/src/tools/bash.mjs +231 -0
  235. package/src/tools/cron-create.mjs +120 -0
  236. package/src/tools/cron-delete.mjs +49 -0
  237. package/src/tools/cron-list.mjs +37 -0
  238. package/src/tools/edit.mjs +82 -0
  239. package/src/tools/enter-worktree.mjs +69 -0
  240. package/src/tools/exit-worktree.mjs +57 -0
  241. package/src/tools/glob.mjs +117 -0
  242. package/src/tools/grep.mjs +129 -0
  243. package/src/tools/lint.mjs +71 -0
  244. package/src/tools/ls.mjs +58 -0
  245. package/src/tools/lsp.mjs +115 -0
  246. package/src/tools/multi-edit.mjs +94 -0
  247. package/src/tools/notebook-edit.mjs +96 -0
  248. package/src/tools/project-overview.mjs +703 -0
  249. package/src/tools/read-mcp-resource.mjs +57 -0
  250. package/src/tools/read.mjs +138 -0
  251. package/src/tools/registry.mjs +116 -0
  252. package/src/tools/remote-trigger.mjs +84 -0
  253. package/src/tools/send-message.mjs +64 -0
  254. package/src/tools/skill.mjs +52 -0
  255. package/src/tools/test-runner.mjs +49 -0
  256. package/src/tools/todo-write.mjs +68 -0
  257. package/src/tools/tool-search.mjs +77 -0
  258. package/src/tools/web-fetch.mjs +65 -0
  259. package/src/tools/web-search.mjs +89 -0
  260. package/src/tools/write.mjs +55 -0
  261. package/src/ui/approval.mjs +510 -0
  262. package/src/ui/banner.mjs +232 -0
  263. package/src/ui/commands.mjs +537 -0
  264. package/src/ui/formatter.mjs +409 -0
  265. package/src/ui/icons.mjs +170 -0
  266. package/src/ui/input-dock.mjs +772 -0
  267. package/src/ui/markdown.mjs +278 -0
  268. package/src/ui/mission-report.mjs +296 -0
  269. package/src/ui/palette.mjs +189 -0
  270. package/src/ui/render-queue.mjs +500 -0
  271. package/src/ui/slash-commands.mjs +257 -0
  272. package/src/ui/spinner.mjs +116 -0
  273. package/src/ui/sub-agent.mjs +167 -0
  274. package/src/ui/term.mjs +174 -0
  275. package/src/ui/text-layout.mjs +127 -0
  276. package/src/ui/tool-card.mjs +740 -0
  277. package/src/ui/tool-details.mjs +504 -0
  278. package/src/ui/transcript-block.mjs +20 -0
@@ -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
+ };
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Web Fetch Tool — fetch URL content using built-in Node.js fetch.
3
+ */
4
+
5
+ export const WebFetchTool = {
6
+ name: 'WebFetch',
7
+ description: 'Fetch content from a URL. Returns the response body as text.',
8
+ inputSchema: {
9
+ type: 'object',
10
+ properties: {
11
+ url: { type: 'string', description: 'The URL to fetch' },
12
+ headers: {
13
+ type: 'object',
14
+ description: 'Optional HTTP headers',
15
+ },
16
+ max_length: {
17
+ type: 'number',
18
+ description: 'Max response length in characters (default: 50000)',
19
+ },
20
+ },
21
+ required: ['url'],
22
+ },
23
+
24
+ validateInput(input) {
25
+ const errors = [];
26
+ if (!input.url) errors.push('url is required');
27
+ try {
28
+ new URL(input.url);
29
+ } catch {
30
+ errors.push('url must be a valid URL');
31
+ }
32
+ return errors;
33
+ },
34
+
35
+ async call(input) {
36
+ const maxLength = input.max_length || 50000;
37
+
38
+ try {
39
+ const controller = new AbortController();
40
+ const timeout = setTimeout(() => controller.abort(), 30000);
41
+
42
+ const res = await fetch(input.url, {
43
+ headers: input.headers || {},
44
+ signal: controller.signal,
45
+ redirect: 'follow',
46
+ });
47
+
48
+ clearTimeout(timeout);
49
+
50
+ if (!res.ok) {
51
+ return `HTTP ${res.status}: ${res.statusText}`;
52
+ }
53
+
54
+ const contentType = res.headers.get('content-type') || '';
55
+ const text = await res.text();
56
+ const truncated = text.length > maxLength
57
+ ? text.slice(0, maxLength) + `\n...[truncated at ${maxLength} chars]`
58
+ : text;
59
+
60
+ return `Content-Type: ${contentType}\nLength: ${text.length}\n\n${truncated}`;
61
+ } catch (err) {
62
+ return `Fetch error: ${err.message}`;
63
+ }
64
+ },
65
+ };
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Web Search Tool — search the web via configurable API backends.
3
+ *
4
+ * Supports:
5
+ * - Brave Search API (BRAVE_API_KEY)
6
+ * - SearXNG (self-hosted, SEARXNG_URL)
7
+ * - Fallback: returns instructions to use web-fetch instead
8
+ */
9
+
10
+ export const WebSearchTool = {
11
+ name: 'WebSearch',
12
+ description: 'Search the web for information. Requires BRAVE_API_KEY or SEARXNG_URL.',
13
+ inputSchema: {
14
+ type: 'object',
15
+ properties: {
16
+ query: { type: 'string', description: 'Search query' },
17
+ limit: { type: 'number', description: 'Max results (default: 5)' },
18
+ },
19
+ required: ['query'],
20
+ },
21
+
22
+ validateInput(input) {
23
+ return input.query ? [] : ['query is required'];
24
+ },
25
+
26
+ async call(input) {
27
+ const limit = input.limit || 5;
28
+
29
+ // Try Brave Search API
30
+ const braveKey = process.env.BRAVE_API_KEY;
31
+ if (braveKey) {
32
+ return searchBrave(input.query, limit, braveKey);
33
+ }
34
+
35
+ // Try SearXNG
36
+ const searxUrl = process.env.SEARXNG_URL;
37
+ if (searxUrl) {
38
+ return searchSearxng(input.query, limit, searxUrl);
39
+ }
40
+
41
+ return 'No search API configured. Set BRAVE_API_KEY or SEARXNG_URL environment variable. ' +
42
+ 'Alternatively, use the WebFetch tool to fetch specific URLs directly.';
43
+ },
44
+ };
45
+
46
+ async function searchBrave(query, limit, apiKey) {
47
+ try {
48
+ const url = `https://api.search.brave.com/res/v1/web/search?q=${encodeURIComponent(query)}&count=${limit}`;
49
+ const res = await fetch(url, {
50
+ headers: {
51
+ 'Accept': 'application/json',
52
+ 'X-Subscription-Token': apiKey,
53
+ },
54
+ });
55
+
56
+ if (!res.ok) return `Brave API error: ${res.status}`;
57
+
58
+ const data = await res.json();
59
+ const results = (data.web?.results || []).slice(0, limit);
60
+
61
+ if (results.length === 0) return 'No results found.';
62
+
63
+ return results.map((r, i) =>
64
+ `${i + 1}. ${r.title}\n ${r.url}\n ${r.description || ''}`
65
+ ).join('\n\n');
66
+ } catch (err) {
67
+ return `Search error: ${err.message}`;
68
+ }
69
+ }
70
+
71
+ async function searchSearxng(query, limit, baseUrl) {
72
+ try {
73
+ const url = `${baseUrl}/search?q=${encodeURIComponent(query)}&format=json&categories=general`;
74
+ const res = await fetch(url);
75
+
76
+ if (!res.ok) return `SearXNG error: ${res.status}`;
77
+
78
+ const data = await res.json();
79
+ const results = (data.results || []).slice(0, limit);
80
+
81
+ if (results.length === 0) return 'No results found.';
82
+
83
+ return results.map((r, i) =>
84
+ `${i + 1}. ${r.title}\n ${r.url}\n ${r.content || ''}`
85
+ ).join('\n\n');
86
+ } catch (err) {
87
+ return `Search error: ${err.message}`;
88
+ }
89
+ }
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Write Tool — matches Claude Code's exact behavior.
3
+ *
4
+ * Features:
5
+ * - Creates parent directories if needed
6
+ * - Requires Read first for existing file overwrites
7
+ * - No README creation unless explicitly asked
8
+ */
9
+ import fs from 'fs';
10
+ import path from 'path';
11
+ import { hasBeenRead, markRead } from './read.mjs';
12
+
13
+ export const WriteTool = {
14
+ name: 'Write',
15
+ description: 'Write content to a file. Creates parent dirs if needed.',
16
+ inputSchema: {
17
+ type: 'object',
18
+ properties: {
19
+ file_path: { type: 'string', description: 'Absolute path to the file' },
20
+ content: { type: 'string', description: 'The content to write' },
21
+ },
22
+ required: ['file_path', 'content'],
23
+ },
24
+ validateInput(input) {
25
+ const errors = [];
26
+ if (!input.file_path) errors.push('file_path required');
27
+ return errors;
28
+ },
29
+ async call(input) {
30
+ const filePath = path.resolve(input.file_path);
31
+
32
+ // Check if file already exists — require Read first for overwrites
33
+ if (fs.existsSync(filePath)) {
34
+ if (!hasBeenRead(filePath)) {
35
+ return `Error: File ${filePath} already exists. You must Read it first before overwriting.`;
36
+ }
37
+ }
38
+
39
+ // Create parent directory if it doesn't exist
40
+ const dir = path.dirname(filePath);
41
+ try {
42
+ fs.mkdirSync(dir, { recursive: true });
43
+ } catch (e) {
44
+ return `Error creating directory ${dir}: ${e.message}`;
45
+ }
46
+
47
+ try {
48
+ fs.writeFileSync(filePath, input.content);
49
+ markRead(filePath); // Mark as read after writing
50
+ return `File written: ${filePath}`;
51
+ } catch (e) {
52
+ return `Error writing file: ${e.message}`;
53
+ }
54
+ },
55
+ };