1use punch_types::{Capability, ToolCategory, ToolDefinition};
7
8pub fn tools_for_capabilities(capabilities: &[Capability]) -> Vec<ToolDefinition> {
13 let mut tools = Vec::new();
14
15 for cap in capabilities {
16 match cap {
17 Capability::FileRead(_) => {
18 push_unique(&mut tools, file_read());
19 push_unique(&mut tools, file_list());
20 push_unique(&mut tools, file_search());
21 push_unique(&mut tools, file_info());
22 }
23 Capability::FileWrite(_) => {
24 push_unique(&mut tools, file_write());
25 push_unique(&mut tools, patch_apply());
26 }
27 Capability::ShellExec(_) => {
28 push_unique(&mut tools, shell_exec());
29 push_unique(&mut tools, process_list());
30 push_unique(&mut tools, process_kill());
31 push_unique(&mut tools, env_get());
32 push_unique(&mut tools, env_list());
33 }
34 Capability::Network(_) => {
35 push_unique(&mut tools, web_fetch());
36 push_unique(&mut tools, web_search());
37 push_unique(&mut tools, http_request());
38 push_unique(&mut tools, http_post());
39 }
40 Capability::Memory => {
41 push_unique(&mut tools, memory_store());
42 push_unique(&mut tools, memory_recall());
43 }
44 Capability::KnowledgeGraph => {
45 push_unique(&mut tools, knowledge_add_entity());
46 push_unique(&mut tools, knowledge_add_relation());
47 push_unique(&mut tools, knowledge_query());
48 }
49 Capability::AgentSpawn => {
50 push_unique(&mut tools, agent_spawn());
51 }
52 Capability::AgentMessage => {
53 push_unique(&mut tools, agent_message());
54 push_unique(&mut tools, agent_list());
55 }
56 Capability::BrowserControl => {
57 push_unique(&mut tools, browser_navigate());
58 push_unique(&mut tools, browser_screenshot());
59 push_unique(&mut tools, browser_click());
60 push_unique(&mut tools, browser_type());
61 push_unique(&mut tools, browser_content());
62 }
63 Capability::SourceControl => {
64 push_unique(&mut tools, git_status());
65 push_unique(&mut tools, git_diff());
66 push_unique(&mut tools, git_log());
67 push_unique(&mut tools, git_commit());
68 push_unique(&mut tools, git_branch());
69 }
70 Capability::Container => {
71 push_unique(&mut tools, docker_ps());
72 push_unique(&mut tools, docker_run());
73 push_unique(&mut tools, docker_build());
74 push_unique(&mut tools, docker_logs());
75 }
76 Capability::DataManipulation => {
77 push_unique(&mut tools, json_query());
78 push_unique(&mut tools, json_transform());
79 push_unique(&mut tools, yaml_parse());
80 push_unique(&mut tools, regex_match());
81 push_unique(&mut tools, regex_replace());
82 push_unique(&mut tools, text_diff());
83 push_unique(&mut tools, text_count());
84 }
85 Capability::Schedule => {
86 push_unique(&mut tools, schedule_task());
87 push_unique(&mut tools, schedule_list());
88 push_unique(&mut tools, schedule_cancel());
89 }
90 Capability::CodeAnalysis => {
91 push_unique(&mut tools, code_search());
92 push_unique(&mut tools, code_symbols());
93 }
94 Capability::Archive => {
95 push_unique(&mut tools, archive_create());
96 push_unique(&mut tools, archive_extract());
97 push_unique(&mut tools, archive_list());
98 }
99 Capability::Template => {
100 push_unique(&mut tools, template_render());
101 }
102 Capability::Crypto => {
103 push_unique(&mut tools, hash_compute());
104 push_unique(&mut tools, hash_verify());
105 }
106 _ => {}
107 }
108 }
109
110 tools
111}
112
113pub fn all_tools() -> Vec<ToolDefinition> {
115 vec![
116 file_read(),
117 file_write(),
118 file_list(),
119 patch_apply(),
120 shell_exec(),
121 web_fetch(),
122 web_search(),
123 memory_store(),
124 memory_recall(),
125 knowledge_add_entity(),
126 knowledge_add_relation(),
127 knowledge_query(),
128 agent_spawn(),
129 agent_message(),
130 agent_list(),
131 browser_navigate(),
132 browser_screenshot(),
133 browser_click(),
134 browser_type(),
135 browser_content(),
136 git_status(),
138 git_diff(),
139 git_log(),
140 git_commit(),
141 git_branch(),
142 docker_ps(),
144 docker_run(),
145 docker_build(),
146 docker_logs(),
147 http_request(),
149 http_post(),
150 json_query(),
152 json_transform(),
153 yaml_parse(),
154 regex_match(),
155 regex_replace(),
156 process_list(),
158 process_kill(),
159 schedule_task(),
161 schedule_list(),
162 schedule_cancel(),
163 code_search(),
165 code_symbols(),
166 archive_create(),
168 archive_extract(),
169 archive_list(),
170 template_render(),
172 hash_compute(),
174 hash_verify(),
175 env_get(),
177 env_list(),
178 text_diff(),
180 text_count(),
181 file_search(),
183 file_info(),
184 ]
185}
186
187fn push_unique(tools: &mut Vec<ToolDefinition>, tool: ToolDefinition) {
188 if !tools.iter().any(|t| t.name == tool.name) {
189 tools.push(tool);
190 }
191}
192
193fn file_read() -> ToolDefinition {
198 ToolDefinition {
199 name: "file_read".into(),
200 description: "Read the contents of a file at the given path.".into(),
201 input_schema: serde_json::json!({
202 "type": "object",
203 "properties": {
204 "path": {
205 "type": "string",
206 "description": "The file path to read (relative to working directory or absolute)."
207 }
208 },
209 "required": ["path"]
210 }),
211 category: ToolCategory::FileSystem,
212 }
213}
214
215fn file_write() -> ToolDefinition {
216 ToolDefinition {
217 name: "file_write".into(),
218 description:
219 "Write content to a file at the given path. Creates parent directories if needed."
220 .into(),
221 input_schema: serde_json::json!({
222 "type": "object",
223 "properties": {
224 "path": {
225 "type": "string",
226 "description": "The file path to write to."
227 },
228 "content": {
229 "type": "string",
230 "description": "The content to write to the file."
231 }
232 },
233 "required": ["path", "content"]
234 }),
235 category: ToolCategory::FileSystem,
236 }
237}
238
239fn file_list() -> ToolDefinition {
240 ToolDefinition {
241 name: "file_list".into(),
242 description: "List files and directories at the given path.".into(),
243 input_schema: serde_json::json!({
244 "type": "object",
245 "properties": {
246 "path": {
247 "type": "string",
248 "description": "The directory path to list (defaults to working directory)."
249 }
250 }
251 }),
252 category: ToolCategory::FileSystem,
253 }
254}
255
256fn shell_exec() -> ToolDefinition {
257 ToolDefinition {
258 name: "shell_exec".into(),
259 description: "Execute a shell command and return stdout, stderr, and exit code.".into(),
260 input_schema: serde_json::json!({
261 "type": "object",
262 "properties": {
263 "command": {
264 "type": "string",
265 "description": "The shell command to execute."
266 }
267 },
268 "required": ["command"]
269 }),
270 category: ToolCategory::Shell,
271 }
272}
273
274fn web_fetch() -> ToolDefinition {
275 ToolDefinition {
276 name: "web_fetch".into(),
277 description: "Fetch the content of a URL via HTTP GET.".into(),
278 input_schema: serde_json::json!({
279 "type": "object",
280 "properties": {
281 "url": {
282 "type": "string",
283 "description": "The URL to fetch."
284 }
285 },
286 "required": ["url"]
287 }),
288 category: ToolCategory::Web,
289 }
290}
291
292fn web_search() -> ToolDefinition {
293 ToolDefinition {
294 name: "web_search".into(),
295 description:
296 "Search the web using DuckDuckGo and return the top results with titles and URLs."
297 .into(),
298 input_schema: serde_json::json!({
299 "type": "object",
300 "properties": {
301 "query": {
302 "type": "string",
303 "description": "The search query."
304 }
305 },
306 "required": ["query"]
307 }),
308 category: ToolCategory::Web,
309 }
310}
311
312fn memory_store() -> ToolDefinition {
313 ToolDefinition {
314 name: "memory_store".into(),
315 description: "Store a key-value pair in your persistent memory. Use this to remember important facts, user preferences, or context across conversations.".into(),
316 input_schema: serde_json::json!({
317 "type": "object",
318 "properties": {
319 "key": {
320 "type": "string",
321 "description": "A short descriptive key for the memory."
322 },
323 "value": {
324 "type": "string",
325 "description": "The value to remember."
326 },
327 "confidence": {
328 "type": "number",
329 "description": "Confidence level from 0.0 to 1.0 (default: 0.9)."
330 }
331 },
332 "required": ["key", "value"]
333 }),
334 category: ToolCategory::Memory,
335 }
336}
337
338fn memory_recall() -> ToolDefinition {
339 ToolDefinition {
340 name: "memory_recall".into(),
341 description: "Search your persistent memory for previously stored information.".into(),
342 input_schema: serde_json::json!({
343 "type": "object",
344 "properties": {
345 "query": {
346 "type": "string",
347 "description": "Search query to find relevant memories."
348 },
349 "limit": {
350 "type": "integer",
351 "description": "Maximum number of results (default: 10)."
352 }
353 },
354 "required": ["query"]
355 }),
356 category: ToolCategory::Memory,
357 }
358}
359
360fn knowledge_add_entity() -> ToolDefinition {
361 ToolDefinition {
362 name: "knowledge_add_entity".into(),
363 description: "Add an entity to your knowledge graph.".into(),
364 input_schema: serde_json::json!({
365 "type": "object",
366 "properties": {
367 "name": {
368 "type": "string",
369 "description": "Name of the entity."
370 },
371 "entity_type": {
372 "type": "string",
373 "description": "Type of entity (e.g. 'person', 'company', 'concept')."
374 },
375 "properties": {
376 "type": "object",
377 "description": "Additional properties as key-value pairs."
378 }
379 },
380 "required": ["name", "entity_type"]
381 }),
382 category: ToolCategory::Knowledge,
383 }
384}
385
386fn knowledge_add_relation() -> ToolDefinition {
387 ToolDefinition {
388 name: "knowledge_add_relation".into(),
389 description: "Add a relation between two entities in your knowledge graph.".into(),
390 input_schema: serde_json::json!({
391 "type": "object",
392 "properties": {
393 "from": {
394 "type": "string",
395 "description": "Source entity name."
396 },
397 "relation": {
398 "type": "string",
399 "description": "The relation type (e.g. 'works_at', 'depends_on')."
400 },
401 "to": {
402 "type": "string",
403 "description": "Target entity name."
404 },
405 "properties": {
406 "type": "object",
407 "description": "Additional properties."
408 }
409 },
410 "required": ["from", "relation", "to"]
411 }),
412 category: ToolCategory::Knowledge,
413 }
414}
415
416fn knowledge_query() -> ToolDefinition {
417 ToolDefinition {
418 name: "knowledge_query".into(),
419 description: "Search your knowledge graph for entities and their relations.".into(),
420 input_schema: serde_json::json!({
421 "type": "object",
422 "properties": {
423 "query": {
424 "type": "string",
425 "description": "Search query to find entities."
426 }
427 },
428 "required": ["query"]
429 }),
430 category: ToolCategory::Knowledge,
431 }
432}
433
434fn agent_spawn() -> ToolDefinition {
439 ToolDefinition {
440 name: "agent_spawn".into(),
441 description: "Spawn a new fighter (AI agent). Returns the new fighter's ID. Use this to create subordinate agents that can handle specialized tasks.".into(),
442 input_schema: serde_json::json!({
443 "type": "object",
444 "properties": {
445 "name": {
446 "type": "string",
447 "description": "A human-readable name for the new fighter."
448 },
449 "system_prompt": {
450 "type": "string",
451 "description": "The system prompt that shapes the new fighter's behavior and specialization."
452 },
453 "description": {
454 "type": "string",
455 "description": "A short description of the fighter's purpose (optional)."
456 },
457 "capabilities": {
458 "type": "array",
459 "description": "Capabilities to grant the new fighter (optional). Each item is a capability object.",
460 "items": {
461 "type": "object"
462 }
463 }
464 },
465 "required": ["name", "system_prompt"]
466 }),
467 category: ToolCategory::Agent,
468 }
469}
470
471fn agent_message() -> ToolDefinition {
472 ToolDefinition {
473 name: "agent_message".into(),
474 description: "Send a message to another fighter by ID or name and get its response. Use this for inter-agent coordination and delegation.".into(),
475 input_schema: serde_json::json!({
476 "type": "object",
477 "properties": {
478 "fighter_id": {
479 "type": "string",
480 "description": "The UUID of the target fighter (provide either this or 'name')."
481 },
482 "name": {
483 "type": "string",
484 "description": "The name of the target fighter (provide either this or 'fighter_id')."
485 },
486 "message": {
487 "type": "string",
488 "description": "The message to send to the target fighter."
489 }
490 },
491 "required": ["message"]
492 }),
493 category: ToolCategory::Agent,
494 }
495}
496
497fn agent_list() -> ToolDefinition {
498 ToolDefinition {
499 name: "agent_list".into(),
500 description: "List all active fighters (AI agents) with their IDs, names, and status."
501 .into(),
502 input_schema: serde_json::json!({
503 "type": "object",
504 "properties": {}
505 }),
506 category: ToolCategory::Agent,
507 }
508}
509
510fn patch_apply() -> ToolDefinition {
515 ToolDefinition {
516 name: "patch_apply".into(),
517 description: "Apply a unified diff patch to a file. Reads the file, validates the patch, \
518 applies it, and writes the result back. Supports standard unified diff format."
519 .into(),
520 input_schema: serde_json::json!({
521 "type": "object",
522 "properties": {
523 "path": {
524 "type": "string",
525 "description": "The file path to patch (relative to working directory or absolute)."
526 },
527 "diff": {
528 "type": "string",
529 "description": "The unified diff text to apply to the file."
530 }
531 },
532 "required": ["path", "diff"]
533 }),
534 category: ToolCategory::FileSystem,
535 }
536}
537
538fn browser_navigate() -> ToolDefinition {
543 ToolDefinition {
544 name: "browser_navigate".into(),
545 description: "Navigate the browser to a URL. Opens the page and waits for it to load."
546 .into(),
547 input_schema: serde_json::json!({
548 "type": "object",
549 "properties": {
550 "url": {
551 "type": "string",
552 "description": "The URL to navigate to."
553 }
554 },
555 "required": ["url"]
556 }),
557 category: ToolCategory::Browser,
558 }
559}
560
561fn browser_screenshot() -> ToolDefinition {
562 ToolDefinition {
563 name: "browser_screenshot".into(),
564 description: "Take a screenshot of the current page. Returns a base64-encoded PNG image."
565 .into(),
566 input_schema: serde_json::json!({
567 "type": "object",
568 "properties": {
569 "full_page": {
570 "type": "boolean",
571 "description": "Capture the full scrollable page (true) or just the viewport (false). Default: false."
572 }
573 }
574 }),
575 category: ToolCategory::Browser,
576 }
577}
578
579fn browser_click() -> ToolDefinition {
580 ToolDefinition {
581 name: "browser_click".into(),
582 description: "Click an element on the page matching the given CSS selector.".into(),
583 input_schema: serde_json::json!({
584 "type": "object",
585 "properties": {
586 "selector": {
587 "type": "string",
588 "description": "CSS selector of the element to click."
589 }
590 },
591 "required": ["selector"]
592 }),
593 category: ToolCategory::Browser,
594 }
595}
596
597fn browser_type() -> ToolDefinition {
598 ToolDefinition {
599 name: "browser_type".into(),
600 description: "Type text into an input element matching the given CSS selector.".into(),
601 input_schema: serde_json::json!({
602 "type": "object",
603 "properties": {
604 "selector": {
605 "type": "string",
606 "description": "CSS selector of the input element."
607 },
608 "text": {
609 "type": "string",
610 "description": "The text to type into the element."
611 }
612 },
613 "required": ["selector", "text"]
614 }),
615 category: ToolCategory::Browser,
616 }
617}
618
619fn browser_content() -> ToolDefinition {
620 ToolDefinition {
621 name: "browser_content".into(),
622 description:
623 "Get the text content of the page or a specific element. Useful for extracting readable text from a web page."
624 .into(),
625 input_schema: serde_json::json!({
626 "type": "object",
627 "properties": {
628 "selector": {
629 "type": "string",
630 "description": "Optional CSS selector. If omitted, returns the full page text content."
631 }
632 }
633 }),
634 category: ToolCategory::Browser,
635 }
636}
637
638fn git_status() -> ToolDefinition {
643 ToolDefinition {
644 name: "git_status".into(),
645 description: "Run `git status --porcelain` in the working directory to show changed files."
646 .into(),
647 input_schema: serde_json::json!({
648 "type": "object",
649 "properties": {}
650 }),
651 category: ToolCategory::SourceControl,
652 }
653}
654
655fn git_diff() -> ToolDefinition {
656 ToolDefinition {
657 name: "git_diff".into(),
658 description:
659 "Run `git diff` to show unstaged changes. Use `staged: true` to see staged changes."
660 .into(),
661 input_schema: serde_json::json!({
662 "type": "object",
663 "properties": {
664 "staged": {
665 "type": "boolean",
666 "description": "If true, show staged changes (--staged). Default: false."
667 },
668 "path": {
669 "type": "string",
670 "description": "Optional file path to restrict the diff to."
671 }
672 }
673 }),
674 category: ToolCategory::SourceControl,
675 }
676}
677
678fn git_log() -> ToolDefinition {
679 ToolDefinition {
680 name: "git_log".into(),
681 description: "Show recent git commits with `git log --oneline`.".into(),
682 input_schema: serde_json::json!({
683 "type": "object",
684 "properties": {
685 "count": {
686 "type": "integer",
687 "description": "Number of commits to show (default: 10)."
688 }
689 }
690 }),
691 category: ToolCategory::SourceControl,
692 }
693}
694
695fn git_commit() -> ToolDefinition {
696 ToolDefinition {
697 name: "git_commit".into(),
698 description: "Stage files and create a git commit with the given message.".into(),
699 input_schema: serde_json::json!({
700 "type": "object",
701 "properties": {
702 "message": {
703 "type": "string",
704 "description": "The commit message."
705 },
706 "files": {
707 "type": "array",
708 "items": { "type": "string" },
709 "description": "Files to stage before committing. If empty, commits all staged changes."
710 }
711 },
712 "required": ["message"]
713 }),
714 category: ToolCategory::SourceControl,
715 }
716}
717
718fn git_branch() -> ToolDefinition {
719 ToolDefinition {
720 name: "git_branch".into(),
721 description: "List, create, or switch git branches.".into(),
722 input_schema: serde_json::json!({
723 "type": "object",
724 "properties": {
725 "action": {
726 "type": "string",
727 "enum": ["list", "create", "switch"],
728 "description": "Action to perform: list, create, or switch. Default: list."
729 },
730 "name": {
731 "type": "string",
732 "description": "Branch name (required for create and switch)."
733 }
734 }
735 }),
736 category: ToolCategory::SourceControl,
737 }
738}
739
740fn docker_ps() -> ToolDefinition {
745 ToolDefinition {
746 name: "docker_ps".into(),
747 description: "List running Docker containers.".into(),
748 input_schema: serde_json::json!({
749 "type": "object",
750 "properties": {
751 "all": {
752 "type": "boolean",
753 "description": "Show all containers, not just running ones. Default: false."
754 }
755 }
756 }),
757 category: ToolCategory::Container,
758 }
759}
760
761fn docker_run() -> ToolDefinition {
762 ToolDefinition {
763 name: "docker_run".into(),
764 description: "Run a Docker container from an image.".into(),
765 input_schema: serde_json::json!({
766 "type": "object",
767 "properties": {
768 "image": {
769 "type": "string",
770 "description": "The Docker image to run."
771 },
772 "command": {
773 "type": "string",
774 "description": "Optional command to run inside the container."
775 },
776 "env": {
777 "type": "object",
778 "description": "Environment variables as key-value pairs."
779 },
780 "ports": {
781 "type": "array",
782 "items": { "type": "string" },
783 "description": "Port mappings (e.g. '8080:80')."
784 },
785 "detach": {
786 "type": "boolean",
787 "description": "Run in detached mode. Default: false."
788 },
789 "name": {
790 "type": "string",
791 "description": "Optional container name."
792 }
793 },
794 "required": ["image"]
795 }),
796 category: ToolCategory::Container,
797 }
798}
799
800fn docker_build() -> ToolDefinition {
801 ToolDefinition {
802 name: "docker_build".into(),
803 description: "Build a Docker image from a Dockerfile.".into(),
804 input_schema: serde_json::json!({
805 "type": "object",
806 "properties": {
807 "path": {
808 "type": "string",
809 "description": "Path to the build context directory (default: '.')."
810 },
811 "tag": {
812 "type": "string",
813 "description": "Tag for the built image (e.g. 'myapp:latest')."
814 },
815 "dockerfile": {
816 "type": "string",
817 "description": "Path to the Dockerfile (default: 'Dockerfile')."
818 }
819 }
820 }),
821 category: ToolCategory::Container,
822 }
823}
824
825fn docker_logs() -> ToolDefinition {
826 ToolDefinition {
827 name: "docker_logs".into(),
828 description: "Get logs from a Docker container.".into(),
829 input_schema: serde_json::json!({
830 "type": "object",
831 "properties": {
832 "container": {
833 "type": "string",
834 "description": "Container ID or name."
835 },
836 "tail": {
837 "type": "integer",
838 "description": "Number of lines to show from the end (default: 100)."
839 }
840 },
841 "required": ["container"]
842 }),
843 category: ToolCategory::Container,
844 }
845}
846
847fn http_request() -> ToolDefinition {
852 ToolDefinition {
853 name: "http_request".into(),
854 description: "Send a full HTTP request with custom method, headers, body, and timeout."
855 .into(),
856 input_schema: serde_json::json!({
857 "type": "object",
858 "properties": {
859 "url": {
860 "type": "string",
861 "description": "The URL to send the request to."
862 },
863 "method": {
864 "type": "string",
865 "enum": ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD"],
866 "description": "HTTP method. Default: GET."
867 },
868 "headers": {
869 "type": "object",
870 "description": "Request headers as key-value pairs."
871 },
872 "body": {
873 "type": "string",
874 "description": "Request body."
875 },
876 "timeout_secs": {
877 "type": "integer",
878 "description": "Request timeout in seconds (default: 30)."
879 }
880 },
881 "required": ["url"]
882 }),
883 category: ToolCategory::Web,
884 }
885}
886
887fn http_post() -> ToolDefinition {
888 ToolDefinition {
889 name: "http_post".into(),
890 description: "Shorthand for an HTTP POST request with a JSON body.".into(),
891 input_schema: serde_json::json!({
892 "type": "object",
893 "properties": {
894 "url": {
895 "type": "string",
896 "description": "The URL to POST to."
897 },
898 "json": {
899 "type": "object",
900 "description": "JSON body to send."
901 },
902 "headers": {
903 "type": "object",
904 "description": "Additional headers."
905 }
906 },
907 "required": ["url", "json"]
908 }),
909 category: ToolCategory::Web,
910 }
911}
912
913fn json_query() -> ToolDefinition {
918 ToolDefinition {
919 name: "json_query".into(),
920 description: "Query a JSON value using a dot-separated path (e.g. 'users.0.name'). Array indices are numeric.".into(),
921 input_schema: serde_json::json!({
922 "type": "object",
923 "properties": {
924 "data": {
925 "description": "The JSON data to query (object, array, or string to parse)."
926 },
927 "path": {
928 "type": "string",
929 "description": "Dot-separated path to query (e.g. 'a.b.0.c')."
930 }
931 },
932 "required": ["data", "path"]
933 }),
934 category: ToolCategory::Data,
935 }
936}
937
938fn json_transform() -> ToolDefinition {
939 ToolDefinition {
940 name: "json_transform".into(),
941 description: "Transform JSON data: extract specific keys, rename keys, or filter an array of objects.".into(),
942 input_schema: serde_json::json!({
943 "type": "object",
944 "properties": {
945 "data": {
946 "description": "The JSON data to transform."
947 },
948 "extract": {
949 "type": "array",
950 "items": { "type": "string" },
951 "description": "List of keys to extract from each object."
952 },
953 "rename": {
954 "type": "object",
955 "description": "Key rename mapping (old_name -> new_name)."
956 },
957 "filter_key": {
958 "type": "string",
959 "description": "Key to filter array items by."
960 },
961 "filter_value": {
962 "type": "string",
963 "description": "Value the filter_key must match."
964 }
965 },
966 "required": ["data"]
967 }),
968 category: ToolCategory::Data,
969 }
970}
971
972fn yaml_parse() -> ToolDefinition {
973 ToolDefinition {
974 name: "yaml_parse".into(),
975 description: "Parse a YAML string and return it as JSON.".into(),
976 input_schema: serde_json::json!({
977 "type": "object",
978 "properties": {
979 "content": {
980 "type": "string",
981 "description": "The YAML string to parse."
982 }
983 },
984 "required": ["content"]
985 }),
986 category: ToolCategory::Data,
987 }
988}
989
990fn regex_match() -> ToolDefinition {
991 ToolDefinition {
992 name: "regex_match".into(),
993 description: "Match a regex pattern against text and return all captures.".into(),
994 input_schema: serde_json::json!({
995 "type": "object",
996 "properties": {
997 "pattern": {
998 "type": "string",
999 "description": "The regex pattern."
1000 },
1001 "text": {
1002 "type": "string",
1003 "description": "The text to match against."
1004 },
1005 "global": {
1006 "type": "boolean",
1007 "description": "Find all matches (true) or just the first (false). Default: false."
1008 }
1009 },
1010 "required": ["pattern", "text"]
1011 }),
1012 category: ToolCategory::Data,
1013 }
1014}
1015
1016fn regex_replace() -> ToolDefinition {
1017 ToolDefinition {
1018 name: "regex_replace".into(),
1019 description: "Find and replace text using a regex pattern. Supports capture group references ($1, $2, etc.) in the replacement.".into(),
1020 input_schema: serde_json::json!({
1021 "type": "object",
1022 "properties": {
1023 "pattern": {
1024 "type": "string",
1025 "description": "The regex pattern to find."
1026 },
1027 "replacement": {
1028 "type": "string",
1029 "description": "The replacement string (supports $1, $2, etc. for captures)."
1030 },
1031 "text": {
1032 "type": "string",
1033 "description": "The text to perform replacement on."
1034 }
1035 },
1036 "required": ["pattern", "replacement", "text"]
1037 }),
1038 category: ToolCategory::Data,
1039 }
1040}
1041
1042fn process_list() -> ToolDefinition {
1047 ToolDefinition {
1048 name: "process_list".into(),
1049 description: "List running processes with PID, name, and CPU/memory usage.".into(),
1050 input_schema: serde_json::json!({
1051 "type": "object",
1052 "properties": {
1053 "filter": {
1054 "type": "string",
1055 "description": "Optional filter string to match process names."
1056 }
1057 }
1058 }),
1059 category: ToolCategory::Shell,
1060 }
1061}
1062
1063fn process_kill() -> ToolDefinition {
1064 ToolDefinition {
1065 name: "process_kill".into(),
1066 description: "Kill a process by PID.".into(),
1067 input_schema: serde_json::json!({
1068 "type": "object",
1069 "properties": {
1070 "pid": {
1071 "type": "integer",
1072 "description": "The process ID to kill."
1073 },
1074 "signal": {
1075 "type": "string",
1076 "description": "Signal to send (e.g. 'TERM', 'KILL'). Default: 'TERM'."
1077 }
1078 },
1079 "required": ["pid"]
1080 }),
1081 category: ToolCategory::Shell,
1082 }
1083}
1084
1085fn schedule_task() -> ToolDefinition {
1090 ToolDefinition {
1091 name: "schedule_task".into(),
1092 description: "Schedule a one-shot or recurring task. Returns a task ID.".into(),
1093 input_schema: serde_json::json!({
1094 "type": "object",
1095 "properties": {
1096 "name": {
1097 "type": "string",
1098 "description": "Human-readable name for the task."
1099 },
1100 "command": {
1101 "type": "string",
1102 "description": "Shell command to execute when the task fires."
1103 },
1104 "delay_secs": {
1105 "type": "integer",
1106 "description": "Delay in seconds before first execution."
1107 },
1108 "interval_secs": {
1109 "type": "integer",
1110 "description": "Interval in seconds for recurring execution. If omitted, the task runs once."
1111 }
1112 },
1113 "required": ["name", "command", "delay_secs"]
1114 }),
1115 category: ToolCategory::Schedule,
1116 }
1117}
1118
1119fn schedule_list() -> ToolDefinition {
1120 ToolDefinition {
1121 name: "schedule_list".into(),
1122 description: "List all scheduled tasks with their IDs, names, and status.".into(),
1123 input_schema: serde_json::json!({
1124 "type": "object",
1125 "properties": {}
1126 }),
1127 category: ToolCategory::Schedule,
1128 }
1129}
1130
1131fn schedule_cancel() -> ToolDefinition {
1132 ToolDefinition {
1133 name: "schedule_cancel".into(),
1134 description: "Cancel a scheduled task by its ID.".into(),
1135 input_schema: serde_json::json!({
1136 "type": "object",
1137 "properties": {
1138 "task_id": {
1139 "type": "string",
1140 "description": "The UUID of the task to cancel."
1141 }
1142 },
1143 "required": ["task_id"]
1144 }),
1145 category: ToolCategory::Schedule,
1146 }
1147}
1148
1149fn code_search() -> ToolDefinition {
1154 ToolDefinition {
1155 name: "code_search".into(),
1156 description: "Search for text or a regex pattern in files recursively under a directory. Returns matching lines with file paths and line numbers.".into(),
1157 input_schema: serde_json::json!({
1158 "type": "object",
1159 "properties": {
1160 "pattern": {
1161 "type": "string",
1162 "description": "The regex pattern to search for."
1163 },
1164 "path": {
1165 "type": "string",
1166 "description": "Root directory to search in (default: working directory)."
1167 },
1168 "file_pattern": {
1169 "type": "string",
1170 "description": "Glob pattern to filter files (e.g. '*.rs', '*.py')."
1171 },
1172 "max_results": {
1173 "type": "integer",
1174 "description": "Maximum number of matches to return (default: 50)."
1175 }
1176 },
1177 "required": ["pattern"]
1178 }),
1179 category: ToolCategory::CodeAnalysis,
1180 }
1181}
1182
1183fn code_symbols() -> ToolDefinition {
1184 ToolDefinition {
1185 name: "code_symbols".into(),
1186 description: "Extract function, struct, class, and method definitions from a source file using regex-based heuristics.".into(),
1187 input_schema: serde_json::json!({
1188 "type": "object",
1189 "properties": {
1190 "path": {
1191 "type": "string",
1192 "description": "Path to the source file to analyze."
1193 }
1194 },
1195 "required": ["path"]
1196 }),
1197 category: ToolCategory::CodeAnalysis,
1198 }
1199}
1200
1201fn archive_create() -> ToolDefinition {
1206 ToolDefinition {
1207 name: "archive_create".into(),
1208 description: "Create a tar.gz archive from a list of file or directory paths.".into(),
1209 input_schema: serde_json::json!({
1210 "type": "object",
1211 "properties": {
1212 "output_path": {
1213 "type": "string",
1214 "description": "Path for the output .tar.gz archive file."
1215 },
1216 "paths": {
1217 "type": "array",
1218 "items": { "type": "string" },
1219 "description": "List of file or directory paths to include in the archive."
1220 }
1221 },
1222 "required": ["output_path", "paths"]
1223 }),
1224 category: ToolCategory::Archive,
1225 }
1226}
1227
1228fn archive_extract() -> ToolDefinition {
1229 ToolDefinition {
1230 name: "archive_extract".into(),
1231 description: "Extract a tar.gz archive to a destination directory.".into(),
1232 input_schema: serde_json::json!({
1233 "type": "object",
1234 "properties": {
1235 "archive_path": {
1236 "type": "string",
1237 "description": "Path to the .tar.gz archive to extract."
1238 },
1239 "destination": {
1240 "type": "string",
1241 "description": "Directory to extract the archive into."
1242 }
1243 },
1244 "required": ["archive_path", "destination"]
1245 }),
1246 category: ToolCategory::Archive,
1247 }
1248}
1249
1250fn archive_list() -> ToolDefinition {
1251 ToolDefinition {
1252 name: "archive_list".into(),
1253 description: "List the contents of a tar.gz archive without extracting.".into(),
1254 input_schema: serde_json::json!({
1255 "type": "object",
1256 "properties": {
1257 "archive_path": {
1258 "type": "string",
1259 "description": "Path to the .tar.gz archive to list."
1260 }
1261 },
1262 "required": ["archive_path"]
1263 }),
1264 category: ToolCategory::Archive,
1265 }
1266}
1267
1268fn template_render() -> ToolDefinition {
1273 ToolDefinition {
1274 name: "template_render".into(),
1275 description: "Render a Handlebars-style template by substituting {{variable}} placeholders with provided values.".into(),
1276 input_schema: serde_json::json!({
1277 "type": "object",
1278 "properties": {
1279 "template": {
1280 "type": "string",
1281 "description": "The template string containing {{variable}} placeholders."
1282 },
1283 "variables": {
1284 "type": "object",
1285 "description": "Key-value pairs mapping variable names to their values."
1286 }
1287 },
1288 "required": ["template", "variables"]
1289 }),
1290 category: ToolCategory::Template,
1291 }
1292}
1293
1294fn hash_compute() -> ToolDefinition {
1299 ToolDefinition {
1300 name: "hash_compute".into(),
1301 description: "Compute a cryptographic hash (SHA-256, SHA-512, or MD5) of a string or file."
1302 .into(),
1303 input_schema: serde_json::json!({
1304 "type": "object",
1305 "properties": {
1306 "algorithm": {
1307 "type": "string",
1308 "enum": ["sha256", "sha512", "md5"],
1309 "description": "Hash algorithm to use. Default: sha256."
1310 },
1311 "input": {
1312 "type": "string",
1313 "description": "The string to hash (provide either this or 'file')."
1314 },
1315 "file": {
1316 "type": "string",
1317 "description": "Path to a file to hash (provide either this or 'input')."
1318 }
1319 }
1320 }),
1321 category: ToolCategory::Crypto,
1322 }
1323}
1324
1325fn hash_verify() -> ToolDefinition {
1326 ToolDefinition {
1327 name: "hash_verify".into(),
1328 description: "Verify that a hash matches an expected value.".into(),
1329 input_schema: serde_json::json!({
1330 "type": "object",
1331 "properties": {
1332 "algorithm": {
1333 "type": "string",
1334 "enum": ["sha256", "sha512", "md5"],
1335 "description": "Hash algorithm to use. Default: sha256."
1336 },
1337 "input": {
1338 "type": "string",
1339 "description": "The string to hash (provide either this or 'file')."
1340 },
1341 "file": {
1342 "type": "string",
1343 "description": "Path to a file to hash (provide either this or 'input')."
1344 },
1345 "expected": {
1346 "type": "string",
1347 "description": "The expected hex-encoded hash value to compare against."
1348 }
1349 },
1350 "required": ["expected"]
1351 }),
1352 category: ToolCategory::Crypto,
1353 }
1354}
1355
1356fn env_get() -> ToolDefinition {
1361 ToolDefinition {
1362 name: "env_get".into(),
1363 description: "Get the value of an environment variable.".into(),
1364 input_schema: serde_json::json!({
1365 "type": "object",
1366 "properties": {
1367 "name": {
1368 "type": "string",
1369 "description": "The environment variable name."
1370 }
1371 },
1372 "required": ["name"]
1373 }),
1374 category: ToolCategory::Shell,
1375 }
1376}
1377
1378fn env_list() -> ToolDefinition {
1379 ToolDefinition {
1380 name: "env_list".into(),
1381 description: "List all environment variables, with optional prefix filter.".into(),
1382 input_schema: serde_json::json!({
1383 "type": "object",
1384 "properties": {
1385 "prefix": {
1386 "type": "string",
1387 "description": "Optional prefix to filter environment variable names by."
1388 }
1389 }
1390 }),
1391 category: ToolCategory::Shell,
1392 }
1393}
1394
1395fn text_diff() -> ToolDefinition {
1400 ToolDefinition {
1401 name: "text_diff".into(),
1402 description: "Compute a unified diff between two text strings.".into(),
1403 input_schema: serde_json::json!({
1404 "type": "object",
1405 "properties": {
1406 "old_text": {
1407 "type": "string",
1408 "description": "The original text."
1409 },
1410 "new_text": {
1411 "type": "string",
1412 "description": "The modified text."
1413 },
1414 "label": {
1415 "type": "string",
1416 "description": "Optional label for the diff output (default: 'a' / 'b')."
1417 }
1418 },
1419 "required": ["old_text", "new_text"]
1420 }),
1421 category: ToolCategory::Data,
1422 }
1423}
1424
1425fn text_count() -> ToolDefinition {
1426 ToolDefinition {
1427 name: "text_count".into(),
1428 description: "Count lines, words, and characters in text.".into(),
1429 input_schema: serde_json::json!({
1430 "type": "object",
1431 "properties": {
1432 "text": {
1433 "type": "string",
1434 "description": "The text to count."
1435 }
1436 },
1437 "required": ["text"]
1438 }),
1439 category: ToolCategory::Data,
1440 }
1441}
1442
1443fn file_search() -> ToolDefinition {
1448 ToolDefinition {
1449 name: "file_search".into(),
1450 description: "Search for files by name pattern (glob) recursively under a directory."
1451 .into(),
1452 input_schema: serde_json::json!({
1453 "type": "object",
1454 "properties": {
1455 "pattern": {
1456 "type": "string",
1457 "description": "Glob pattern to match file names (e.g. '*.rs', 'Cargo.*')."
1458 },
1459 "path": {
1460 "type": "string",
1461 "description": "Root directory to search in (default: working directory)."
1462 },
1463 "max_results": {
1464 "type": "integer",
1465 "description": "Maximum number of results to return (default: 100)."
1466 }
1467 },
1468 "required": ["pattern"]
1469 }),
1470 category: ToolCategory::FileSystem,
1471 }
1472}
1473
1474fn file_info() -> ToolDefinition {
1475 ToolDefinition {
1476 name: "file_info".into(),
1477 description: "Get file metadata: size, modified time, permissions, and type.".into(),
1478 input_schema: serde_json::json!({
1479 "type": "object",
1480 "properties": {
1481 "path": {
1482 "type": "string",
1483 "description": "Path to the file or directory to inspect."
1484 }
1485 },
1486 "required": ["path"]
1487 }),
1488 category: ToolCategory::FileSystem,
1489 }
1490}
1491
1492#[cfg(test)]
1497mod tests {
1498 use super::*;
1499
1500 #[test]
1501 fn test_browser_tool_definitions_correct() {
1502 let nav = browser_navigate();
1503 assert_eq!(nav.name, "browser_navigate");
1504 assert_eq!(nav.category, ToolCategory::Browser);
1505 assert!(nav.input_schema["required"]
1506 .as_array()
1507 .expect("required should be array")
1508 .iter()
1509 .any(|v| v == "url"));
1510
1511 let ss = browser_screenshot();
1512 assert_eq!(ss.name, "browser_screenshot");
1513 assert_eq!(ss.category, ToolCategory::Browser);
1514
1515 let click = browser_click();
1516 assert_eq!(click.name, "browser_click");
1517 assert!(click.input_schema["required"]
1518 .as_array()
1519 .expect("required should be array")
1520 .iter()
1521 .any(|v| v == "selector"));
1522
1523 let typ = browser_type();
1524 assert_eq!(typ.name, "browser_type");
1525 let required = typ.input_schema["required"]
1526 .as_array()
1527 .expect("required should be array");
1528 assert!(required.iter().any(|v| v == "selector"));
1529 assert!(required.iter().any(|v| v == "text"));
1530
1531 let content = browser_content();
1532 assert_eq!(content.name, "browser_content");
1533 assert_eq!(content.category, ToolCategory::Browser);
1534 }
1535
1536 #[test]
1537 fn test_browser_tools_require_browser_control_capability() {
1538 let caps = vec![Capability::BrowserControl];
1539 let tools = tools_for_capabilities(&caps);
1540
1541 let names: Vec<&str> = tools.iter().map(|t| t.name.as_str()).collect();
1542 assert!(
1543 names.contains(&"browser_navigate"),
1544 "missing browser_navigate"
1545 );
1546 assert!(
1547 names.contains(&"browser_screenshot"),
1548 "missing browser_screenshot"
1549 );
1550 assert!(names.contains(&"browser_click"), "missing browser_click");
1551 assert!(names.contains(&"browser_type"), "missing browser_type");
1552 assert!(
1553 names.contains(&"browser_content"),
1554 "missing browser_content"
1555 );
1556 }
1557
1558 #[test]
1559 fn test_browser_tools_absent_without_capability() {
1560 let caps = vec![Capability::Memory];
1561 let tools = tools_for_capabilities(&caps);
1562
1563 let names: Vec<&str> = tools.iter().map(|t| t.name.as_str()).collect();
1564 assert!(!names.iter().any(|n| n.starts_with("browser_")));
1565 }
1566
1567 #[test]
1568 fn test_all_tools_includes_browser() {
1569 let tools = all_tools();
1570 let names: Vec<&str> = tools.iter().map(|t| t.name.as_str()).collect();
1571 assert!(names.contains(&"browser_navigate"));
1572 assert!(names.contains(&"browser_screenshot"));
1573 assert!(names.contains(&"browser_click"));
1574 assert!(names.contains(&"browser_type"));
1575 assert!(names.contains(&"browser_content"));
1576 }
1577
1578 #[test]
1583 fn test_file_read_definition() {
1584 let t = file_read();
1585 assert_eq!(t.name, "file_read");
1586 assert_eq!(t.category, ToolCategory::FileSystem);
1587 assert_eq!(t.input_schema["type"], "object");
1588 let required = t.input_schema["required"].as_array().unwrap();
1589 assert!(required.iter().any(|v| v == "path"));
1590 }
1591
1592 #[test]
1593 fn test_file_write_definition() {
1594 let t = file_write();
1595 assert_eq!(t.name, "file_write");
1596 assert_eq!(t.category, ToolCategory::FileSystem);
1597 let required = t.input_schema["required"].as_array().unwrap();
1598 assert!(required.iter().any(|v| v == "path"));
1599 assert!(required.iter().any(|v| v == "content"));
1600 }
1601
1602 #[test]
1603 fn test_file_list_definition() {
1604 let t = file_list();
1605 assert_eq!(t.name, "file_list");
1606 assert_eq!(t.category, ToolCategory::FileSystem);
1607 assert_eq!(t.input_schema["type"], "object");
1608 }
1609
1610 #[test]
1611 fn test_file_search_definition() {
1612 let t = file_search();
1613 assert_eq!(t.name, "file_search");
1614 assert_eq!(t.category, ToolCategory::FileSystem);
1615 let required = t.input_schema["required"].as_array().unwrap();
1616 assert!(required.iter().any(|v| v == "pattern"));
1617 }
1618
1619 #[test]
1620 fn test_file_info_definition() {
1621 let t = file_info();
1622 assert_eq!(t.name, "file_info");
1623 assert_eq!(t.category, ToolCategory::FileSystem);
1624 let required = t.input_schema["required"].as_array().unwrap();
1625 assert!(required.iter().any(|v| v == "path"));
1626 }
1627
1628 #[test]
1629 fn test_patch_apply_definition() {
1630 let t = patch_apply();
1631 assert_eq!(t.name, "patch_apply");
1632 assert_eq!(t.category, ToolCategory::FileSystem);
1633 let required = t.input_schema["required"].as_array().unwrap();
1634 assert!(required.iter().any(|v| v == "path"));
1635 assert!(required.iter().any(|v| v == "diff"));
1636 }
1637
1638 #[test]
1639 fn test_shell_exec_definition() {
1640 let t = shell_exec();
1641 assert_eq!(t.name, "shell_exec");
1642 assert_eq!(t.category, ToolCategory::Shell);
1643 let required = t.input_schema["required"].as_array().unwrap();
1644 assert!(required.iter().any(|v| v == "command"));
1645 }
1646
1647 #[test]
1648 fn test_web_fetch_definition() {
1649 let t = web_fetch();
1650 assert_eq!(t.name, "web_fetch");
1651 assert_eq!(t.category, ToolCategory::Web);
1652 let required = t.input_schema["required"].as_array().unwrap();
1653 assert!(required.iter().any(|v| v == "url"));
1654 }
1655
1656 #[test]
1657 fn test_web_search_definition() {
1658 let t = web_search();
1659 assert_eq!(t.name, "web_search");
1660 assert_eq!(t.category, ToolCategory::Web);
1661 let required = t.input_schema["required"].as_array().unwrap();
1662 assert!(required.iter().any(|v| v == "query"));
1663 }
1664
1665 #[test]
1666 fn test_memory_store_definition() {
1667 let t = memory_store();
1668 assert_eq!(t.name, "memory_store");
1669 assert_eq!(t.category, ToolCategory::Memory);
1670 let required = t.input_schema["required"].as_array().unwrap();
1671 assert!(required.iter().any(|v| v == "key"));
1672 assert!(required.iter().any(|v| v == "value"));
1673 }
1674
1675 #[test]
1676 fn test_memory_recall_definition() {
1677 let t = memory_recall();
1678 assert_eq!(t.name, "memory_recall");
1679 assert_eq!(t.category, ToolCategory::Memory);
1680 let required = t.input_schema["required"].as_array().unwrap();
1681 assert!(required.iter().any(|v| v == "query"));
1682 }
1683
1684 #[test]
1685 fn test_knowledge_tools_definitions() {
1686 let ae = knowledge_add_entity();
1687 assert_eq!(ae.name, "knowledge_add_entity");
1688 assert_eq!(ae.category, ToolCategory::Knowledge);
1689 let required = ae.input_schema["required"].as_array().unwrap();
1690 assert!(required.iter().any(|v| v == "name"));
1691 assert!(required.iter().any(|v| v == "entity_type"));
1692
1693 let ar = knowledge_add_relation();
1694 assert_eq!(ar.name, "knowledge_add_relation");
1695 let required = ar.input_schema["required"].as_array().unwrap();
1696 assert!(required.iter().any(|v| v == "from"));
1697 assert!(required.iter().any(|v| v == "relation"));
1698 assert!(required.iter().any(|v| v == "to"));
1699
1700 let kq = knowledge_query();
1701 assert_eq!(kq.name, "knowledge_query");
1702 let required = kq.input_schema["required"].as_array().unwrap();
1703 assert!(required.iter().any(|v| v == "query"));
1704 }
1705
1706 #[test]
1707 fn test_agent_tools_definitions() {
1708 let spawn = agent_spawn();
1709 assert_eq!(spawn.name, "agent_spawn");
1710 assert_eq!(spawn.category, ToolCategory::Agent);
1711 let required = spawn.input_schema["required"].as_array().unwrap();
1712 assert!(required.iter().any(|v| v == "name"));
1713 assert!(required.iter().any(|v| v == "system_prompt"));
1714
1715 let msg = agent_message();
1716 assert_eq!(msg.name, "agent_message");
1717 assert_eq!(msg.category, ToolCategory::Agent);
1718 let required = msg.input_schema["required"].as_array().unwrap();
1719 assert!(required.iter().any(|v| v == "message"));
1720
1721 let list = agent_list();
1722 assert_eq!(list.name, "agent_list");
1723 assert_eq!(list.category, ToolCategory::Agent);
1724 }
1725
1726 #[test]
1727 fn test_git_tools_definitions() {
1728 let status = git_status();
1729 assert_eq!(status.name, "git_status");
1730 assert_eq!(status.category, ToolCategory::SourceControl);
1731
1732 let diff = git_diff();
1733 assert_eq!(diff.name, "git_diff");
1734
1735 let log = git_log();
1736 assert_eq!(log.name, "git_log");
1737
1738 let commit = git_commit();
1739 assert_eq!(commit.name, "git_commit");
1740 let required = commit.input_schema["required"].as_array().unwrap();
1741 assert!(required.iter().any(|v| v == "message"));
1742
1743 let branch = git_branch();
1744 assert_eq!(branch.name, "git_branch");
1745 }
1746
1747 #[test]
1748 fn test_docker_tools_definitions() {
1749 let ps = docker_ps();
1750 assert_eq!(ps.name, "docker_ps");
1751 assert_eq!(ps.category, ToolCategory::Container);
1752
1753 let run = docker_run();
1754 assert_eq!(run.name, "docker_run");
1755 let required = run.input_schema["required"].as_array().unwrap();
1756 assert!(required.iter().any(|v| v == "image"));
1757
1758 let build = docker_build();
1759 assert_eq!(build.name, "docker_build");
1760
1761 let logs = docker_logs();
1762 assert_eq!(logs.name, "docker_logs");
1763 let required = logs.input_schema["required"].as_array().unwrap();
1764 assert!(required.iter().any(|v| v == "container"));
1765 }
1766
1767 #[test]
1768 fn test_http_tools_definitions() {
1769 let req = http_request();
1770 assert_eq!(req.name, "http_request");
1771 assert_eq!(req.category, ToolCategory::Web);
1772 let required = req.input_schema["required"].as_array().unwrap();
1773 assert!(required.iter().any(|v| v == "url"));
1774
1775 let post = http_post();
1776 assert_eq!(post.name, "http_post");
1777 let required = post.input_schema["required"].as_array().unwrap();
1778 assert!(required.iter().any(|v| v == "url"));
1779 assert!(required.iter().any(|v| v == "json"));
1780 }
1781
1782 #[test]
1783 fn test_data_tools_definitions() {
1784 let jq = json_query();
1785 assert_eq!(jq.name, "json_query");
1786 assert_eq!(jq.category, ToolCategory::Data);
1787 let required = jq.input_schema["required"].as_array().unwrap();
1788 assert!(required.iter().any(|v| v == "data"));
1789 assert!(required.iter().any(|v| v == "path"));
1790
1791 let jt = json_transform();
1792 assert_eq!(jt.name, "json_transform");
1793 let required = jt.input_schema["required"].as_array().unwrap();
1794 assert!(required.iter().any(|v| v == "data"));
1795
1796 let yp = yaml_parse();
1797 assert_eq!(yp.name, "yaml_parse");
1798 let required = yp.input_schema["required"].as_array().unwrap();
1799 assert!(required.iter().any(|v| v == "content"));
1800
1801 let rm = regex_match();
1802 assert_eq!(rm.name, "regex_match");
1803 let required = rm.input_schema["required"].as_array().unwrap();
1804 assert!(required.iter().any(|v| v == "pattern"));
1805 assert!(required.iter().any(|v| v == "text"));
1806
1807 let rr = regex_replace();
1808 assert_eq!(rr.name, "regex_replace");
1809 let required = rr.input_schema["required"].as_array().unwrap();
1810 assert!(required.iter().any(|v| v == "pattern"));
1811 assert!(required.iter().any(|v| v == "replacement"));
1812 assert!(required.iter().any(|v| v == "text"));
1813 }
1814
1815 #[test]
1816 fn test_process_tools_definitions() {
1817 let pl = process_list();
1818 assert_eq!(pl.name, "process_list");
1819 assert_eq!(pl.category, ToolCategory::Shell);
1820
1821 let pk = process_kill();
1822 assert_eq!(pk.name, "process_kill");
1823 let required = pk.input_schema["required"].as_array().unwrap();
1824 assert!(required.iter().any(|v| v == "pid"));
1825 }
1826
1827 #[test]
1828 fn test_schedule_tools_definitions() {
1829 let st = schedule_task();
1830 assert_eq!(st.name, "schedule_task");
1831 assert_eq!(st.category, ToolCategory::Schedule);
1832 let required = st.input_schema["required"].as_array().unwrap();
1833 assert!(required.iter().any(|v| v == "name"));
1834 assert!(required.iter().any(|v| v == "command"));
1835 assert!(required.iter().any(|v| v == "delay_secs"));
1836
1837 let sl = schedule_list();
1838 assert_eq!(sl.name, "schedule_list");
1839
1840 let sc = schedule_cancel();
1841 assert_eq!(sc.name, "schedule_cancel");
1842 let required = sc.input_schema["required"].as_array().unwrap();
1843 assert!(required.iter().any(|v| v == "task_id"));
1844 }
1845
1846 #[test]
1847 fn test_code_analysis_tools_definitions() {
1848 let cs = code_search();
1849 assert_eq!(cs.name, "code_search");
1850 assert_eq!(cs.category, ToolCategory::CodeAnalysis);
1851 let required = cs.input_schema["required"].as_array().unwrap();
1852 assert!(required.iter().any(|v| v == "pattern"));
1853
1854 let sym = code_symbols();
1855 assert_eq!(sym.name, "code_symbols");
1856 let required = sym.input_schema["required"].as_array().unwrap();
1857 assert!(required.iter().any(|v| v == "path"));
1858 }
1859
1860 #[test]
1861 fn test_archive_tools_definitions() {
1862 let ac = archive_create();
1863 assert_eq!(ac.name, "archive_create");
1864 assert_eq!(ac.category, ToolCategory::Archive);
1865 let required = ac.input_schema["required"].as_array().unwrap();
1866 assert!(required.iter().any(|v| v == "output_path"));
1867 assert!(required.iter().any(|v| v == "paths"));
1868
1869 let ae = archive_extract();
1870 assert_eq!(ae.name, "archive_extract");
1871 let required = ae.input_schema["required"].as_array().unwrap();
1872 assert!(required.iter().any(|v| v == "archive_path"));
1873 assert!(required.iter().any(|v| v == "destination"));
1874
1875 let al = archive_list();
1876 assert_eq!(al.name, "archive_list");
1877 let required = al.input_schema["required"].as_array().unwrap();
1878 assert!(required.iter().any(|v| v == "archive_path"));
1879 }
1880
1881 #[test]
1882 fn test_template_render_definition() {
1883 let t = template_render();
1884 assert_eq!(t.name, "template_render");
1885 assert_eq!(t.category, ToolCategory::Template);
1886 let required = t.input_schema["required"].as_array().unwrap();
1887 assert!(required.iter().any(|v| v == "template"));
1888 assert!(required.iter().any(|v| v == "variables"));
1889 }
1890
1891 #[test]
1892 fn test_crypto_tools_definitions() {
1893 let hc = hash_compute();
1894 assert_eq!(hc.name, "hash_compute");
1895 assert_eq!(hc.category, ToolCategory::Crypto);
1896
1897 let hv = hash_verify();
1898 assert_eq!(hv.name, "hash_verify");
1899 let required = hv.input_schema["required"].as_array().unwrap();
1900 assert!(required.iter().any(|v| v == "expected"));
1901 }
1902
1903 #[test]
1904 fn test_env_tools_definitions() {
1905 let eg = env_get();
1906 assert_eq!(eg.name, "env_get");
1907 assert_eq!(eg.category, ToolCategory::Shell);
1908 let required = eg.input_schema["required"].as_array().unwrap();
1909 assert!(required.iter().any(|v| v == "name"));
1910
1911 let el = env_list();
1912 assert_eq!(el.name, "env_list");
1913 }
1914
1915 #[test]
1916 fn test_text_tools_definitions() {
1917 let td = text_diff();
1918 assert_eq!(td.name, "text_diff");
1919 assert_eq!(td.category, ToolCategory::Data);
1920 let required = td.input_schema["required"].as_array().unwrap();
1921 assert!(required.iter().any(|v| v == "old_text"));
1922 assert!(required.iter().any(|v| v == "new_text"));
1923
1924 let tc = text_count();
1925 assert_eq!(tc.name, "text_count");
1926 let required = tc.input_schema["required"].as_array().unwrap();
1927 assert!(required.iter().any(|v| v == "text"));
1928 }
1929
1930 #[test]
1935 fn test_tools_for_file_read_capability() {
1936 let caps = vec![Capability::FileRead("**".into())];
1937 let tools = tools_for_capabilities(&caps);
1938 let names: Vec<&str> = tools.iter().map(|t| t.name.as_str()).collect();
1939 assert!(names.contains(&"file_read"));
1940 assert!(names.contains(&"file_list"));
1941 assert!(names.contains(&"file_search"));
1942 assert!(names.contains(&"file_info"));
1943 assert!(!names.contains(&"file_write"));
1944 }
1945
1946 #[test]
1947 fn test_tools_for_file_write_capability() {
1948 let caps = vec![Capability::FileWrite("**".into())];
1949 let tools = tools_for_capabilities(&caps);
1950 let names: Vec<&str> = tools.iter().map(|t| t.name.as_str()).collect();
1951 assert!(names.contains(&"file_write"));
1952 assert!(names.contains(&"patch_apply"));
1953 assert!(!names.contains(&"file_read"));
1954 }
1955
1956 #[test]
1957 fn test_tools_for_shell_exec_capability() {
1958 let caps = vec![Capability::ShellExec("*".into())];
1959 let tools = tools_for_capabilities(&caps);
1960 let names: Vec<&str> = tools.iter().map(|t| t.name.as_str()).collect();
1961 assert!(names.contains(&"shell_exec"));
1962 assert!(names.contains(&"process_list"));
1963 assert!(names.contains(&"process_kill"));
1964 assert!(names.contains(&"env_get"));
1965 assert!(names.contains(&"env_list"));
1966 }
1967
1968 #[test]
1969 fn test_tools_for_network_capability() {
1970 let caps = vec![Capability::Network("*".into())];
1971 let tools = tools_for_capabilities(&caps);
1972 let names: Vec<&str> = tools.iter().map(|t| t.name.as_str()).collect();
1973 assert!(names.contains(&"web_fetch"));
1974 assert!(names.contains(&"web_search"));
1975 assert!(names.contains(&"http_request"));
1976 assert!(names.contains(&"http_post"));
1977 }
1978
1979 #[test]
1980 fn test_tools_for_memory_capability() {
1981 let caps = vec![Capability::Memory];
1982 let tools = tools_for_capabilities(&caps);
1983 let names: Vec<&str> = tools.iter().map(|t| t.name.as_str()).collect();
1984 assert!(names.contains(&"memory_store"));
1985 assert!(names.contains(&"memory_recall"));
1986 assert_eq!(names.len(), 2);
1987 }
1988
1989 #[test]
1990 fn test_tools_for_knowledge_graph_capability() {
1991 let caps = vec![Capability::KnowledgeGraph];
1992 let tools = tools_for_capabilities(&caps);
1993 let names: Vec<&str> = tools.iter().map(|t| t.name.as_str()).collect();
1994 assert!(names.contains(&"knowledge_add_entity"));
1995 assert!(names.contains(&"knowledge_add_relation"));
1996 assert!(names.contains(&"knowledge_query"));
1997 assert_eq!(names.len(), 3);
1998 }
1999
2000 #[test]
2001 fn test_tools_for_agent_spawn_capability() {
2002 let caps = vec![Capability::AgentSpawn];
2003 let tools = tools_for_capabilities(&caps);
2004 let names: Vec<&str> = tools.iter().map(|t| t.name.as_str()).collect();
2005 assert!(names.contains(&"agent_spawn"));
2006 assert_eq!(names.len(), 1);
2007 }
2008
2009 #[test]
2010 fn test_tools_for_agent_message_capability() {
2011 let caps = vec![Capability::AgentMessage];
2012 let tools = tools_for_capabilities(&caps);
2013 let names: Vec<&str> = tools.iter().map(|t| t.name.as_str()).collect();
2014 assert!(names.contains(&"agent_message"));
2015 assert!(names.contains(&"agent_list"));
2016 assert_eq!(names.len(), 2);
2017 }
2018
2019 #[test]
2020 fn test_tools_for_source_control_capability() {
2021 let caps = vec![Capability::SourceControl];
2022 let tools = tools_for_capabilities(&caps);
2023 let names: Vec<&str> = tools.iter().map(|t| t.name.as_str()).collect();
2024 assert!(names.contains(&"git_status"));
2025 assert!(names.contains(&"git_diff"));
2026 assert!(names.contains(&"git_log"));
2027 assert!(names.contains(&"git_commit"));
2028 assert!(names.contains(&"git_branch"));
2029 assert_eq!(names.len(), 5);
2030 }
2031
2032 #[test]
2033 fn test_tools_for_container_capability() {
2034 let caps = vec![Capability::Container];
2035 let tools = tools_for_capabilities(&caps);
2036 let names: Vec<&str> = tools.iter().map(|t| t.name.as_str()).collect();
2037 assert!(names.contains(&"docker_ps"));
2038 assert!(names.contains(&"docker_run"));
2039 assert!(names.contains(&"docker_build"));
2040 assert!(names.contains(&"docker_logs"));
2041 assert_eq!(names.len(), 4);
2042 }
2043
2044 #[test]
2045 fn test_tools_for_data_manipulation_capability() {
2046 let caps = vec![Capability::DataManipulation];
2047 let tools = tools_for_capabilities(&caps);
2048 let names: Vec<&str> = tools.iter().map(|t| t.name.as_str()).collect();
2049 assert!(names.contains(&"json_query"));
2050 assert!(names.contains(&"json_transform"));
2051 assert!(names.contains(&"yaml_parse"));
2052 assert!(names.contains(&"regex_match"));
2053 assert!(names.contains(&"regex_replace"));
2054 assert!(names.contains(&"text_diff"));
2055 assert!(names.contains(&"text_count"));
2056 assert_eq!(names.len(), 7);
2057 }
2058
2059 #[test]
2060 fn test_tools_for_schedule_capability() {
2061 let caps = vec![Capability::Schedule];
2062 let tools = tools_for_capabilities(&caps);
2063 let names: Vec<&str> = tools.iter().map(|t| t.name.as_str()).collect();
2064 assert!(names.contains(&"schedule_task"));
2065 assert!(names.contains(&"schedule_list"));
2066 assert!(names.contains(&"schedule_cancel"));
2067 assert_eq!(names.len(), 3);
2068 }
2069
2070 #[test]
2071 fn test_tools_for_code_analysis_capability() {
2072 let caps = vec![Capability::CodeAnalysis];
2073 let tools = tools_for_capabilities(&caps);
2074 let names: Vec<&str> = tools.iter().map(|t| t.name.as_str()).collect();
2075 assert!(names.contains(&"code_search"));
2076 assert!(names.contains(&"code_symbols"));
2077 assert_eq!(names.len(), 2);
2078 }
2079
2080 #[test]
2081 fn test_tools_for_archive_capability() {
2082 let caps = vec![Capability::Archive];
2083 let tools = tools_for_capabilities(&caps);
2084 let names: Vec<&str> = tools.iter().map(|t| t.name.as_str()).collect();
2085 assert!(names.contains(&"archive_create"));
2086 assert!(names.contains(&"archive_extract"));
2087 assert!(names.contains(&"archive_list"));
2088 assert_eq!(names.len(), 3);
2089 }
2090
2091 #[test]
2092 fn test_tools_for_template_capability() {
2093 let caps = vec![Capability::Template];
2094 let tools = tools_for_capabilities(&caps);
2095 let names: Vec<&str> = tools.iter().map(|t| t.name.as_str()).collect();
2096 assert!(names.contains(&"template_render"));
2097 assert_eq!(names.len(), 1);
2098 }
2099
2100 #[test]
2101 fn test_tools_for_crypto_capability() {
2102 let caps = vec![Capability::Crypto];
2103 let tools = tools_for_capabilities(&caps);
2104 let names: Vec<&str> = tools.iter().map(|t| t.name.as_str()).collect();
2105 assert!(names.contains(&"hash_compute"));
2106 assert!(names.contains(&"hash_verify"));
2107 assert_eq!(names.len(), 2);
2108 }
2109
2110 #[test]
2111 fn test_tools_for_empty_capabilities() {
2112 let caps: Vec<Capability> = vec![];
2113 let tools = tools_for_capabilities(&caps);
2114 assert!(tools.is_empty());
2115 }
2116
2117 #[test]
2118 fn test_tools_for_event_publish_returns_empty() {
2119 let caps = vec![Capability::EventPublish];
2121 let tools = tools_for_capabilities(&caps);
2122 assert!(tools.is_empty());
2123 }
2124
2125 #[test]
2130 fn test_push_unique_dedup() {
2131 let mut tools = Vec::new();
2132 push_unique(&mut tools, file_read());
2133 push_unique(&mut tools, file_read());
2134 push_unique(&mut tools, file_read());
2135 assert_eq!(tools.len(), 1);
2136 }
2137
2138 #[test]
2139 fn test_push_unique_different_tools() {
2140 let mut tools = Vec::new();
2141 push_unique(&mut tools, file_read());
2142 push_unique(&mut tools, file_write());
2143 push_unique(&mut tools, shell_exec());
2144 assert_eq!(tools.len(), 3);
2145 }
2146
2147 #[test]
2148 fn test_tools_for_multiple_capabilities_dedup() {
2149 let caps = vec![
2151 Capability::FileRead("src/**".into()),
2152 Capability::FileRead("tests/**".into()),
2153 ];
2154 let tools = tools_for_capabilities(&caps);
2155 let file_read_count = tools.iter().filter(|t| t.name == "file_read").count();
2156 assert_eq!(file_read_count, 1);
2157 }
2158
2159 #[test]
2164 fn test_all_tools_count() {
2165 let tools = all_tools();
2166 assert!(
2168 tools.len() >= 50,
2169 "expected at least 50 tools, got {}",
2170 tools.len()
2171 );
2172 }
2173
2174 #[test]
2175 fn test_all_tools_unique_names() {
2176 let tools = all_tools();
2177 let mut names: Vec<&str> = tools.iter().map(|t| t.name.as_str()).collect();
2178 let original_len = names.len();
2179 names.sort();
2180 names.dedup();
2181 assert_eq!(names.len(), original_len, "all_tools has duplicate names");
2182 }
2183
2184 #[test]
2185 fn test_all_tools_valid_schemas() {
2186 let tools = all_tools();
2187 for tool in &tools {
2188 assert_eq!(
2189 tool.input_schema["type"], "object",
2190 "tool {} has non-object schema",
2191 tool.name
2192 );
2193 assert!(!tool.name.is_empty(), "tool has empty name");
2194 assert!(
2195 !tool.description.is_empty(),
2196 "tool {} has empty description",
2197 tool.name
2198 );
2199 }
2200 }
2201}