1#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub struct ToolDefinition {
16 pub name: &'static str,
18 pub summary: &'static str,
20 pub description: &'static str,
25 pub keywords: &'static [&'static str],
30 pub inputs: &'static [&'static str],
32 pub side_effects: &'static [&'static str],
34 pub examples: &'static [&'static str],
36}
37
38pub fn code_tool_names() -> &'static [&'static str] {
41 const CODE_TOOL_NAMES: &[&str] = &[
42 "code",
43 "code_read_files",
44 "code_edit_files",
45 "code_outline",
46 "code_symbols",
47 "code_references",
48 "code_definition",
49 "code_glob",
50 "code_grep",
51 "code_map",
52 "code_stats",
53 "code_deps",
54 "code_lint",
55 "code_test",
56 ];
57
58 CODE_TOOL_NAMES
59}
60
61pub fn is_code_tool_name(name: &str) -> bool {
63 code_tool_names()
64 .iter()
65 .any(|tool_name| tool_name.eq_ignore_ascii_case(name.trim()))
66}
67
68pub fn all_tools() -> &'static [ToolDefinition] {
70 static TOOLS: &[ToolDefinition] = &[
71 ToolDefinition {
72 name: "file",
73 summary: "Read/write/list files and directories (workspace & sandbox-aware)",
74 description: "Read, write, edit, list, search, and navigate files and directories in \
75 the workspace. Use this tool whenever the user wants to open, create, modify, \
76 delete, or inspect file or directory contents. Workspace-sandboxed to prevent \
77 accidental access outside the project root.",
78 keywords: &[
79 "file",
80 "read",
81 "write",
82 "edit",
83 "create",
84 "delete",
85 "save",
86 "directory",
87 "folder",
88 "path",
89 "list",
90 "search",
91 "content",
92 "text",
93 "document",
94 "open",
95 "load",
96 "cat",
97 "find",
98 "ls",
99 "tree",
100 ],
101 inputs: &[
102 "path",
103 "operation (read/write/list)",
104 "content (for write)",
105 "options",
106 ],
107 side_effects: &["Reads local files", "May write/modify local files"],
108 examples: &[
109 "gestura tools file read ./README.md",
110 "gestura tools file write ./notes.txt --content \"hello\"",
111 ],
112 },
113 ToolDefinition {
114 name: "shell",
115 summary: "Run shell commands in a controlled environment",
116 description: "Execute shell commands, scripts, and programs in a controlled \
117 environment. Use this tool to run build systems (cargo, npm, make), run tests, \
118 lint code, install packages, or perform any other command-line operation. \
119 Runs in the workspace directory by default.",
120 keywords: &[
121 "shell", "run", "execute", "command", "terminal", "bash", "script", "process",
122 "npm", "cargo", "make", "build", "test", "lint", "install", "start", "stop",
123 "restart", "deploy", "compile", "launch",
124 ],
125 inputs: &["command", "cwd", "env"],
126 side_effects: &[
127 "Executes local processes",
128 "May modify local state depending on command",
129 ],
130 examples: &[
131 "gestura tools shell run -- command=\"ls -la\"",
132 "gestura tools shell run -- cwd=. -- command=\"cargo test\"",
133 ],
134 },
135 ToolDefinition {
136 name: "git",
137 summary: "Git status/diff/log operations for code workflows",
138 description: "Interact with Git repositories: show status, view diffs, read commit \
139 history, manage branches, stage and commit changes, stash, and resolve conflicts. \
140 Use this tool whenever the user asks about code changes, version history, \
141 branches, or anything related to source control.",
142 keywords: &[
143 "git",
144 "commit",
145 "branch",
146 "diff",
147 "status",
148 "log",
149 "merge",
150 "push",
151 "pull",
152 "repository",
153 "version",
154 "history",
155 "staged",
156 "stash",
157 "rebase",
158 "checkout",
159 "blame",
160 "conflict",
161 "tag",
162 "remote",
163 ],
164 inputs: &[
165 "repository path",
166 "operation (status/diff/log/etc)",
167 "options",
168 ],
169 side_effects: &[
170 "Reads git metadata",
171 "May change repo state for write operations",
172 ],
173 examples: &[
174 "gestura tools git status",
175 "gestura tools git diff --staged",
176 ],
177 },
178 ToolDefinition {
179 name: "code",
180 summary: "Analyze, search, edit, lint, and test code with a compatibility-friendly aggregate interface",
181 description: "Inspect and operate on source code using the legacy aggregate code tool interface. Use this when the model or session configuration expects a single `code` tool that can route to operations like stats, map, symbols, grep, batch_read, batch_edit, lint, and test. The runtime also exposes stricter split code tools, but this compatibility entry remains available for existing prompts, policies, and sessions.",
182 keywords: &[
183 "code",
184 "source",
185 "read",
186 "edit",
187 "refactor",
188 "search",
189 "grep",
190 "symbols",
191 "definition",
192 "references",
193 "outline",
194 "map",
195 "stats",
196 "deps",
197 "lint",
198 "test",
199 "batch",
200 "files",
201 ],
202 inputs: &[
203 "operation",
204 "path",
205 "symbol / pattern (operation-specific)",
206 "paths / edits / options (operation-specific)",
207 ],
208 side_effects: &[
209 "Reads local code files",
210 "May write local code files for batch_edit",
211 "May execute local subprocesses for lint/test",
212 ],
213 examples: &[
214 "Code stats: {operation:stats, path:.}",
215 "Read files: {operation:batch_read, paths:[src/main.rs, src/lib.rs]}",
216 "Edit files: {operation:batch_edit, edits:[{path:src/lib.rs, old_str:old, new_str:new}]}",
217 ],
218 },
219 ToolDefinition {
220 name: "code_read_files",
221 summary: "Read one or more exact source files with a strict file-only contract",
222 description: "Read exact file contents for known source files. Use this after you know the specific file paths you need. Pass `paths`; do not pass directory roots or edit-style arguments.",
223 keywords: &[
224 "code", "read", "file", "source", "contents", "open", "inspect",
225 ],
226 inputs: &["paths"],
227 side_effects: &["Reads local code files"],
228 examples: &["Read files: {paths:[src/main.rs, src/lib.rs]}"],
229 },
230 ToolDefinition {
231 name: "code_edit_files",
232 summary: "Apply exact str-replace edits across one or more files with a strict schema",
233 description: "Edit existing files using an `edits` array. Each edit must include only `path`, `old_str`, and `new_str`. This tool is strict: do not pass directories, `pattern`, `symbol`, or read-style fields.",
234 keywords: &["code", "edit", "replace", "refactor", "rewrite", "modify"],
235 inputs: &["edits"],
236 side_effects: &["Writes local code files"],
237 examples: &[
238 "Edit files: {edits:[{path:src/lib.rs, old_str:old_name, new_str:new_name}]}",
239 ],
240 },
241 ToolDefinition {
242 name: "code_outline",
243 summary: "Return a structured outline for one exact source file",
244 description: "Extract the outline of a single file, including functions, structs, enums, and impls. Requires an exact file path.",
245 keywords: &["code", "outline", "api", "file", "structure", "symbols"],
246 inputs: &["path"],
247 side_effects: &["Reads local code files"],
248 examples: &["Outline a file: {path:crates/my-crate/src/lib.rs}"],
249 },
250 ToolDefinition {
251 name: "code_symbols",
252 summary: "Extract top-level symbols from one exact source file",
253 description: "List the top-level symbols from a file. Requires an exact file path.",
254 keywords: &["code", "symbols", "file", "function", "struct", "enum"],
255 inputs: &["path"],
256 side_effects: &["Reads local code files"],
257 examples: &["Symbols: {path:crates/my-crate/src/lib.rs}"],
258 },
259 ToolDefinition {
260 name: "code_references",
261 summary: "Find references to a symbol across an existing code path",
262 description: "Find references for a symbol inside a file or directory path.",
263 keywords: &["code", "references", "symbol", "usage", "find"],
264 inputs: &["symbol", "path"],
265 side_effects: &["Reads local code files"],
266 examples: &["References: {symbol:MyStruct, path:.}"],
267 },
268 ToolDefinition {
269 name: "code_definition",
270 summary: "Jump to the first definition of a symbol across an existing code path",
271 description: "Find the first definition for a symbol inside a file or directory path.",
272 keywords: &["code", "definition", "symbol", "declare", "where"],
273 inputs: &["symbol", "path"],
274 side_effects: &["Reads local code files"],
275 examples: &["Definition: {symbol:execute_tool, path:.}"],
276 },
277 ToolDefinition {
278 name: "code_glob",
279 summary: "Find files by glob pattern inside a directory",
280 description: "Search for files using a glob pattern relative to a directory root.",
281 keywords: &["code", "glob", "files", "match", "pattern", "discover"],
282 inputs: &["path", "pattern", "max_results"],
283 side_effects: &["Reads local code metadata"],
284 examples: &["Glob files: {path:., pattern:**/*.rs}"],
285 },
286 ToolDefinition {
287 name: "code_grep",
288 summary: "Regex search code contents inside a directory",
289 description: "Search file contents with a regex pattern, optional file glob filtering, and context lines.",
290 keywords: &["code", "grep", "search", "regex", "text", "pattern"],
291 inputs: &[
292 "path",
293 "pattern",
294 "file_glob",
295 "context_lines",
296 "max_results",
297 ],
298 side_effects: &["Reads local code files"],
299 examples: &[
300 "Grep code: {path:., pattern:fn handle_request, file_glob:*.rs, context_lines:3}",
301 ],
302 },
303 ToolDefinition {
304 name: "code_map",
305 summary: "Summarize repository structure for a directory",
306 description: "Build a repository map for a directory root.",
307 keywords: &["code", "map", "repo", "structure", "directory", "overview"],
308 inputs: &["path", "max_depth"],
309 side_effects: &["Reads local code metadata"],
310 examples: &["Map repo: {path:., max_depth:3}"],
311 },
312 ToolDefinition {
313 name: "code_stats",
314 summary: "Compute line and language statistics for an existing path",
315 description: "Compute language and line-count statistics for a file or directory path.",
316 keywords: &["code", "stats", "lines", "languages", "count"],
317 inputs: &["path"],
318 side_effects: &["Reads local code metadata"],
319 examples: &["Code stats: {path:.}"],
320 },
321 ToolDefinition {
322 name: "code_deps",
323 summary: "Inspect Cargo dependencies for an existing path",
324 description: "Inspect Cargo dependencies for a manifest or project path.",
325 keywords: &["code", "deps", "dependencies", "cargo", "manifest"],
326 inputs: &["path"],
327 side_effects: &["Reads local manifests"],
328 examples: &["Dependencies: {path:.}"],
329 },
330 ToolDefinition {
331 name: "code_lint",
332 summary: "Run linting for a project directory",
333 description: "Run cargo clippy for a project directory. This executes a subprocess.",
334 keywords: &["code", "lint", "clippy", "cargo", "verify"],
335 inputs: &["path", "fix"],
336 side_effects: &[
337 "Executes local lint subprocesses",
338 "May modify files when fix=true",
339 ],
340 examples: &["Lint: {path:.}"],
341 },
342 ToolDefinition {
343 name: "code_test",
344 summary: "Run tests for a project directory",
345 description: "Run cargo test for a project directory, optionally filtered.",
346 keywords: &["code", "test", "cargo", "verify", "unit", "integration"],
347 inputs: &["path", "filter"],
348 side_effects: &["Executes local test subprocesses"],
349 examples: &["Test: {path:., filter:my_test_name}"],
350 },
351 ToolDefinition {
352 name: "web",
353 summary: "Fetch web pages and summarize content",
354 description: "Fetch and read content from web pages, converting HTML to readable \
355 text. Use this tool when the user provides a URL or asks to retrieve information \
356 from a specific website, read documentation, check a webpage, or download content \
357 from the internet. Handles redirects and extracts main body text.",
358 keywords: &[
359 "web",
360 "fetch",
361 "url",
362 "page",
363 "website",
364 "http",
365 "https",
366 "browse",
367 "download",
368 "documentation",
369 "internet",
370 "online",
371 "link",
372 "site",
373 "visit",
374 "open",
375 "retrieve",
376 "get",
377 "html",
378 "content",
379 ],
380 inputs: &["url", "options"],
381 side_effects: &["Performs network requests"],
382 examples: &["gestura tools web fetch https://example.com"],
383 },
384 ToolDefinition {
385 name: "web_search",
386 summary: "Search the web using configurable providers (Local/SerpAPI/DuckDuckGo/Brave)",
387 description: "Search the internet using configurable search providers (DuckDuckGo, \
388 Brave, SerpAPI). Use this tool when the user wants to find information online, \
389 look up a topic, research a question, find recent news or events, or discover \
390 relevant web resources. Returns ranked result snippets with URLs.",
391 keywords: &[
392 "search",
393 "google",
394 "find",
395 "lookup",
396 "query",
397 "results",
398 "internet",
399 "online",
400 "discover",
401 "research",
402 "news",
403 "information",
404 "duckduckgo",
405 "brave",
406 "look up",
407 "what is",
408 "how to",
409 "latest",
410 ],
411 inputs: &["query", "max_results", "provider (optional)"],
412 side_effects: &["Performs network requests", "May use API quotas"],
413 examples: &[
414 "gestura tools web_search \"rust async patterns\"",
415 "gestura tools web_search \"latest AI news\" --max-results 5",
416 ],
417 },
418 ToolDefinition {
419 name: "a2a",
420 summary: "Agent-to-Agent protocol for delegating tasks to remote agents",
421 description: "Delegate tasks to remote AI agents using the Agent-to-Agent (A2A) \
422 protocol. Use this tool when the user wants to offload a subtask to a specialized \
423 remote agent, discover what remote agents are available, or orchestrate multi-agent \
424 workflows where different agents collaborate on a larger goal.",
425 keywords: &[
426 "agent",
427 "a2a",
428 "delegate",
429 "remote",
430 "orchestrate",
431 "multi-agent",
432 "protocol",
433 "worker",
434 "supervisor",
435 "task",
436 "handoff",
437 "subagent",
438 "collaborate",
439 ],
440 inputs: &["agent_url", "task_message", "auth_token (optional)"],
441 side_effects: &[
442 "Performs network requests",
443 "May execute tasks on remote agents",
444 ],
445 examples: &[
446 "gestura tools a2a discover https://agent.example.com",
447 "gestura tools a2a send https://agent.example.com \"Summarize this document\"",
448 ],
449 },
450 ToolDefinition {
451 name: "permissions",
452 summary: "Check/request OS-level permissions (platform-specific)",
453 description: "Check and request OS-level permissions such as microphone access, \
454 accessibility, screen recording, and camera. Use this tool when a feature \
455 requires a system permission that may not yet be granted, or when the user \
456 asks what permissions the app has or needs.",
457 keywords: &[
458 "permission",
459 "permissions",
460 "microphone",
461 "accessibility",
462 "access",
463 "privacy",
464 "camera",
465 "system",
466 "grant",
467 "request",
468 "check",
469 "screen recording",
470 "allow",
471 ],
472 inputs: &["permission kind (microphone, accessibility, etc.)"],
473 side_effects: &["May prompt the OS", "May open system settings"],
474 examples: &[
475 "gestura tools permissions check",
476 "gestura tools permissions request microphone",
477 ],
478 },
479 ToolDefinition {
480 name: "mcp",
481 summary: "Search, evaluate, install, enable, disable, and manage MCP servers from the official registry",
482 description: "Discover and manage Model Context Protocol (MCP) servers from the official \
483 registry at registry.modelcontextprotocol.io. Use this tool when the user wants to \
484 find new MCP servers by keyword, evaluate a specific server's capabilities and \
485 requirements, install a server into .mcp.json, enable or disable configured servers, \
486 list installed servers, or remove a server. Always start with operation=search to \
487 find candidates, then operation=evaluate to review details, then operation=install \
488 to add the server — the tool provides LLM workflow guidance at each step.",
489 keywords: &[
490 "mcp",
491 "install",
492 "search",
493 "discover",
494 "registry",
495 "manage",
496 "configure",
497 "browse",
498 "npm",
499 "npx",
500 "enable",
501 "disable",
502 "remove",
503 "server",
504 "protocol",
505 "extension",
506 "plugin",
507 "external",
508 "connect",
509 "capability",
510 "model context",
511 "integration",
512 "list",
513 "add",
514 "setup",
515 "pypi",
516 "docker",
517 "oci",
518 "stdio",
519 "http",
520 "tool",
521 ],
522 inputs: &[
523 "operation (search/evaluate/install/enable/disable/list/remove/info)",
524 "query (for search)",
525 "limit (for search, default 20)",
526 "server_id (for evaluate/install/info)",
527 "name (local alias for install/enable/disable/remove)",
528 "scope (project|user, default project)",
529 "transport (stdio|http, for install override)",
530 "command (for install stdio override)",
531 "args (array, for install stdio override)",
532 "url (for install http override)",
533 "env (object, env vars for install)",
534 ],
535 side_effects: &[
536 "search/evaluate/info: network request to registry.modelcontextprotocol.io",
537 "install/enable/disable/remove: modifies .mcp.json on disk",
538 ],
539 examples: &[
540 "{\"operation\":\"search\",\"query\":\"filesystem\"}",
541 "{\"operation\":\"search\",\"query\":\"github\",\"limit\":10}",
542 "{\"operation\":\"evaluate\",\"server_id\":\"io.github.modelcontextprotocol/server-filesystem\"}",
543 "{\"operation\":\"install\",\"server_id\":\"io.github.modelcontextprotocol/server-filesystem\",\"name\":\"filesystem\",\"scope\":\"project\"}",
544 "{\"operation\":\"install\",\"server_id\":\"io.github.exa/exa\",\"env\":{\"EXA_API_KEY\":\"<key>\"}}",
545 "{\"operation\":\"list\",\"scope\":\"project\"}",
546 "{\"operation\":\"enable\",\"name\":\"filesystem\"}",
547 "{\"operation\":\"disable\",\"name\":\"filesystem\"}",
548 "{\"operation\":\"remove\",\"name\":\"filesystem\",\"scope\":\"project\"}",
549 "{\"operation\":\"info\",\"server_id\":\"io.github.modelcontextprotocol/server-github\"}",
550 ],
551 },
552 ToolDefinition {
553 name: "task",
554 summary: "Manage tasks for the current session: create, update details, update status, delete, list, inspect hierarchies",
555 description: "Create, update, list, and organize tasks and work items for the \
556 current session. Use this tool when the user wants to track progress on work, \
557 create a to-do list, mark items as done, build subtask hierarchies, or manage \
558 any kind of structured checklist or work breakdown. For `update`, provide \
559 `task_id` plus at least one of `name` or `description`. For `update_status`, ALWAYS \
560 provide both `task_id` and `status` in the same call; do not call `update_status` \
561 with only `task_id`, and do not use it just to confirm/preserve the current state. \
562 If no status changed, skip the task update and continue the real work.",
563 keywords: &[
564 "task",
565 "todo",
566 "work",
567 "track",
568 "checklist",
569 "reminder",
570 "subtask",
571 "status",
572 "create",
573 "list",
574 "progress",
575 "organize",
576 "mark",
577 "done",
578 "complete",
579 "in progress",
580 "workflow",
581 "breakdown",
582 ],
583 inputs: &[
584 "operation (create/update_status/update/delete/list/get_hierarchy)",
585 "task_id (for update_status/update/delete)",
586 "name (for create, optional for update)",
587 "description (optional for create/update)",
588 "status (for update_status)",
589 "parent_id (optional, for subtasks)",
590 ],
591 side_effects: &[
592 "Creates/modifies/deletes task files in .gestura/tasks/",
593 "Persists task state across sessions",
594 ],
595 examples: &[
596 "task create --name 'Implement feature' --description 'Add new API endpoint'",
597 "task update --task_id abc123 --name 'Refine onboarding task'",
598 "task update_status --task_id abc123 --status inprogress",
599 "{\"operation\":\"update_status\",\"task_id\":\"abc123\",\"status\":\"completed\"}",
600 "task delete --task_id abc123",
601 "task list",
602 "task create --name 'Write tests' --parent_id abc123",
603 ],
604 },
605 ToolDefinition {
606 name: "screenshot",
607 summary: "Capture screenshots of the screen or specific regions",
608 description: "Capture screenshots of the full screen, a specific display, or a \
609 defined region. Use this tool when the user wants to take a picture of their \
610 screen, capture a UI state, snap a region, or save a visual record. Returns \
611 a file path or inline base64 image. Requires screen recording permission.",
612 keywords: &[
613 "screenshot",
614 "capture",
615 "screen",
616 "image",
617 "photo",
618 "snap",
619 "grab",
620 "display",
621 "picture",
622 "png",
623 "jpg",
624 "snapshot",
625 "screengrab",
626 "take a picture",
627 "show me",
628 ],
629 inputs: &[
630 "output_format (optional: png/jpg)",
631 "output_path (optional; default artifact path)",
632 "return (optional: mode=path|inline_base64 + inline bounds)",
633 "region (optional: x,y,width,height)",
634 "display (optional: display number)",
635 ],
636 side_effects: &[
637 "Captures screen content (privacy-sensitive)",
638 "Creates image file on disk",
639 "May prompt for OS screen recording permission",
640 ],
641 examples: &[
642 "{\"output_path\":\"./screen.png\"}",
643 "{}",
644 "{\"output_format\":\"jpg\"}",
645 "{\"output_path\":\"./region.png\",\"region\":{\"x\":0,\"y\":0,\"width\":800,\"height\":600}}",
646 ],
647 },
648 ToolDefinition {
649 name: "screen_record",
650 summary: "Record screen video with start/stop controls",
651 description: "Record the screen as a video file (MP4 or MOV). Start a recording \
652 session with optional region and display selection, then stop it to produce a \
653 file. Use this tool when the user wants to create a screencast, demonstrate \
654 a workflow, record a tutorial, or capture video of any on-screen activity.",
655 keywords: &[
656 "record",
657 "recording",
658 "video",
659 "screen",
660 "capture",
661 "screencast",
662 "demonstration",
663 "demo",
664 "tutorial",
665 "mp4",
666 "mov",
667 "film",
668 "show",
669 "create a video",
670 "make a video",
671 ],
672 inputs: &[
673 "operation (start/stop)",
674 "output_format (optional for start: mp4/mov)",
675 "output_path (optional for start; default artifact path)",
676 "recording_id (for stop)",
677 "region (optional: x,y,width,height)",
678 "display (optional: display number)",
679 ],
680 side_effects: &[
681 "Captures screen content (privacy-sensitive)",
682 "Creates video file on disk",
683 "May prompt for OS screen recording permission",
684 "Spawns background recording process",
685 ],
686 examples: &[
687 "{\"operation\":\"start\",\"output_path\":\"./recording.mp4\"}",
688 "{\"operation\":\"start\"}",
689 "{\"operation\":\"start\",\"output_format\":\"mov\"}",
690 "{\"operation\":\"stop\",\"recording_id\":\"<recording_id>\"}",
691 ],
692 },
693 ToolDefinition {
694 name: "gui_control",
695 summary: "Drive the application GUI for self-demonstrations",
696 description: "Control the Gestura application GUI programmatically: toggle view \
697 modes, open or close the file explorer, navigate to the chat panel, or open \
698 configuration screens. Use this tool when the agent needs to demonstrate its \
699 own interface or navigate UI panels as part of a self-demonstration workflow.",
700 keywords: &[
701 "gui",
702 "interface",
703 "view",
704 "mode",
705 "explorer",
706 "editor",
707 "chat",
708 "navigate",
709 "toggle",
710 "window",
711 "ui",
712 "panel",
713 "open",
714 "close",
715 "config",
716 "settings",
717 ],
718 inputs: &[
719 "action (toggle_view_mode/open_explorer/close_explorer/open_chat/close_chat/navigate_config)",
720 "target (optional argument depending on action)",
721 ],
722 side_effects: &["Changes the physical view in the user's GUI"],
723 examples: &[
724 "{\"action\":\"toggle_view_mode\"}",
725 "{\"action\":\"open_explorer\"}",
726 ],
727 },
728 ];
729 TOOLS
730}
731
732pub fn find_tool(name: &str) -> Option<&'static ToolDefinition> {
734 let name = name.trim().to_ascii_lowercase();
735 all_tools()
736 .iter()
737 .find(|t| t.name.eq_ignore_ascii_case(&name))
738}
739
740pub fn render_tools_overview() -> String {
742 let mut out = String::new();
743 out.push_str("**Built-in Tools:**\n\n");
744 for t in all_tools() {
745 out.push_str(&format!("• **{}** - {}\n", t.name, t.summary));
746 }
747 out.push_str("\nUse `/tools <name>` for details on a specific tool.");
748 out.push_str("\nUse `/capabilities` for full system status including MCP servers and devices.");
749 out
750}
751
752pub fn render_tool_detail(name: &str) -> Option<String> {
754 let t = find_tool(name)?;
755 let mut out = String::new();
756 out.push_str(&format!("**{}**\n\n", t.name));
757 out.push_str(&format!("{}\n\n", t.summary));
758
759 if !t.inputs.is_empty() {
760 out.push_str("**Inputs:**\n");
761 for i in t.inputs {
762 out.push_str(&format!("• {}\n", i));
763 }
764 out.push('\n');
765 }
766
767 if !t.side_effects.is_empty() {
768 out.push_str("**Side Effects:**\n");
769 for s in t.side_effects {
770 out.push_str(&format!("⚠ {}\n", s));
771 }
772 out.push('\n');
773 }
774
775 if !t.examples.is_empty() {
776 out.push_str("**Examples:**\n");
777 for e in t.examples {
778 out.push_str(&format!("```\n{}\n```\n", e));
779 }
780 }
781 Some(out)
782}
783
784pub fn looks_like_tools_question(input: &str) -> bool {
786 let s = input.trim().to_ascii_lowercase();
787 s == "tools"
788 || s == "tool"
789 || s == "/tools"
790 || s.contains("what tools")
791 || s.contains("available tools")
792 || s.contains("which tools")
793 || s.contains("list tools")
794 || s.contains("tool list")
795 || s.contains("show tools")
796 || s.contains("show me tools")
797}
798
799pub fn looks_like_capabilities_question(input: &str) -> bool {
801 let s = input.trim().to_ascii_lowercase();
802 s == "/capabilities"
803 || s == "capabilities"
804 || s.contains("what can you do")
805 || s.contains("what do you have access to")
806 || s.contains("have access to")
807 || s.contains("what are your capabilities")
808 || s.contains("show capabilities")
809 || s.contains("mcp servers")
810 || s.contains("mcp tools")
811 || s.contains("configured tools")
812 || s.contains("system status")
813 || s.contains("current config")
814 || s.contains("device settings")
815}
816
817#[cfg(test)]
818mod tests {
819 use super::*;
820
821 #[test]
822 fn registry_contains_expected_tools() {
823 assert!(find_tool("file").is_some());
824 assert!(find_tool("shell").is_some());
825 assert!(find_tool("git").is_some());
826 assert!(find_tool("code").is_some());
827 }
828
829 #[test]
830 fn task_tool_description_mentions_update_requirements() {
831 let task = find_tool("task").expect("task tool should exist");
832 assert!(task.summary.contains("update details"));
833 assert!(task.description.contains(
834 "For `update`, provide `task_id` plus at least one of `name` or `description`"
835 ));
836 assert!(task.examples.iter().any(|example| {
837 example.contains("task update --task_id abc123 --name 'Refine onboarding task'")
838 }));
839 assert!(
840 task.examples
841 .iter()
842 .any(|example| example.contains("task delete --task_id abc123"))
843 );
844 }
845
846 #[test]
847 fn code_tool_helpers_cover_legacy_and_split_names() {
848 assert!(is_code_tool_name("code"));
849 assert!(is_code_tool_name("code_edit_files"));
850 assert!(is_code_tool_name("code_read_files"));
851 assert!(!is_code_tool_name("shell"));
852 }
853
854 #[test]
855 fn looks_like_tools_question_matches_common_phrases() {
856 assert!(looks_like_tools_question("what tools do you have?"));
857 assert!(looks_like_tools_question("list tools"));
858 assert!(looks_like_tools_question("/tools"));
859 assert!(!looks_like_tools_question("tell me a joke"));
860 }
861}