ata-coder 2.4.2__py3-none-any.whl

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 (118) hide show
  1. ata_coder/__init__.py +1 -0
  2. ata_coder/agent.py +874 -0
  3. ata_coder/agent_compact.py +190 -0
  4. ata_coder/agent_controller.py +218 -0
  5. ata_coder/agent_extension.py +69 -0
  6. ata_coder/agent_routing.py +105 -0
  7. ata_coder/agent_subsystems.py +72 -0
  8. ata_coder/agent_tools.py +318 -0
  9. ata_coder/agent_undo.py +63 -0
  10. ata_coder/anthropic_client.py +465 -0
  11. ata_coder/change_tracker.py +368 -0
  12. ata_coder/clawd_integration.py +574 -0
  13. ata_coder/commands/__init__.py +128 -0
  14. ata_coder/commands/_core.py +184 -0
  15. ata_coder/commands/_safety.py +95 -0
  16. ata_coder/commands/_settings.py +241 -0
  17. ata_coder/commands/_workflow.py +451 -0
  18. ata_coder/commands.py +974 -0
  19. ata_coder/config.py +257 -0
  20. ata_coder/core/__init__.py +35 -0
  21. ata_coder/core/events.py +73 -0
  22. ata_coder/core/queue.py +85 -0
  23. ata_coder/core/state.py +17 -0
  24. ata_coder/event_queue.py +5 -0
  25. ata_coder/extension.py +654 -0
  26. ata_coder/extensions/__init__.py +1 -0
  27. ata_coder/extensions/hello_skill.py +47 -0
  28. ata_coder/fool_proof.py +295 -0
  29. ata_coder/git_workflow.py +371 -0
  30. ata_coder/gui.py +511 -0
  31. ata_coder/llm_client.py +543 -0
  32. ata_coder/main.py +814 -0
  33. ata_coder/mcp_client.py +1095 -0
  34. ata_coder/memory.py +539 -0
  35. ata_coder/model_registry.py +134 -0
  36. ata_coder/model_router.py +105 -0
  37. ata_coder/permissions.py +274 -0
  38. ata_coder/privilege.py +464 -0
  39. ata_coder/project.py +273 -0
  40. ata_coder/prompt_template.py +423 -0
  41. ata_coder/prompts/auto-mode.md +7 -0
  42. ata_coder/prompts/coding-rules.md +40 -0
  43. ata_coder/prompts/execution-guardrails.md +14 -0
  44. ata_coder/prompts/memory-system.md +24 -0
  45. ata_coder/prompts/output-style.md +23 -0
  46. ata_coder/prompts/safety.md +17 -0
  47. ata_coder/prompts/slash-commands.md +24 -0
  48. ata_coder/prompts/sub-agents.md +38 -0
  49. ata_coder/prompts/system-reminders.md +17 -0
  50. ata_coder/prompts/system.md +105 -0
  51. ata_coder/prompts/tool-policy.md +46 -0
  52. ata_coder/repl_theme.py +99 -0
  53. ata_coder/repl_tracker.py +89 -0
  54. ata_coder/repl_ui.py +1214 -0
  55. ata_coder/safety_guard.py +434 -0
  56. ata_coder/self_correct.py +346 -0
  57. ata_coder/server.py +882 -0
  58. ata_coder/server_session.py +159 -0
  59. ata_coder/server_shell.py +129 -0
  60. ata_coder/session.py +431 -0
  61. ata_coder/settings.py +439 -0
  62. ata_coder/setup_wizard.py +136 -0
  63. ata_coder/skill_extension.py +92 -0
  64. ata_coder/skills/architect/SKILL.md +42 -0
  65. ata_coder/skills/code-reviewer/SKILL.md +37 -0
  66. ata_coder/skills/codecraft/SKILL.md +452 -0
  67. ata_coder/skills/debugger/SKILL.md +45 -0
  68. ata_coder/skills/doc-writer/SKILL.md +36 -0
  69. ata_coder/skills/general-coder/SKILL.md +76 -0
  70. ata_coder/skills/math-calculator/README.md +40 -0
  71. ata_coder/skills/math-calculator/SKILL.md +59 -0
  72. ata_coder/skills/math-calculator/handler.py +103 -0
  73. ata_coder/skills/math-calculator/prompts/system.md +8 -0
  74. ata_coder/skills/math-calculator/requirements.txt +2 -0
  75. ata_coder/skills/math-calculator/resources/constants.json +8 -0
  76. ata_coder/skills/math-calculator/tests/test_handler.py +53 -0
  77. ata_coder/skills/security-auditor/SKILL.md +40 -0
  78. ata_coder/skills/test-writer/SKILL.md +36 -0
  79. ata_coder/skills/weather-skill/README.md +45 -0
  80. ata_coder/skills/weather-skill/handler.py +76 -0
  81. ata_coder/skills/weather-skill/manifest.json +48 -0
  82. ata_coder/skills/weather-skill/prompts/system_prompt.txt +9 -0
  83. ata_coder/skills/weather-skill/prompts/user_prompt_template.txt +3 -0
  84. ata_coder/skills/weather-skill/requirements.txt +1 -0
  85. ata_coder/skills/weather-skill/resources/city_list.json +17 -0
  86. ata_coder/skills/weather-skill/resources/error_messages.json +7 -0
  87. ata_coder/skills/weather-skill/tests/test_handler.py +28 -0
  88. ata_coder/skills/weather-skill/weather_utils.py +50 -0
  89. ata_coder/skills.py +1014 -0
  90. ata_coder/sub_agent.py +273 -0
  91. ata_coder/sub_agent_manager.py +203 -0
  92. ata_coder/system_prompt_builder.py +146 -0
  93. ata_coder/task_planner.py +391 -0
  94. ata_coder/terminal.py +318 -0
  95. ata_coder/test_runner.py +219 -0
  96. ata_coder/thread_supervisor.py +195 -0
  97. ata_coder/tool_defs.py +335 -0
  98. ata_coder/tools/__init__.py +11 -0
  99. ata_coder/tools/definitions.py +335 -0
  100. ata_coder/tools/executor.py +1036 -0
  101. ata_coder/tools/result.py +26 -0
  102. ata_coder/tools/subagent.py +332 -0
  103. ata_coder/tools/web.py +361 -0
  104. ata_coder/tools.py +1576 -0
  105. ata_coder/types.py +92 -0
  106. ata_coder/utils.py +113 -0
  107. ata_coder/web/css/style.css +180 -0
  108. ata_coder/web/index.html +84 -0
  109. ata_coder/web/js/app.js +489 -0
  110. ata_coder/web/package-lock.json +25 -0
  111. ata_coder/web/package.json +10 -0
  112. ata_coder/web/tsconfig.json +13 -0
  113. ata_coder-2.4.2.dist-info/METADATA +799 -0
  114. ata_coder-2.4.2.dist-info/RECORD +118 -0
  115. ata_coder-2.4.2.dist-info/WHEEL +5 -0
  116. ata_coder-2.4.2.dist-info/entry_points.txt +2 -0
  117. ata_coder-2.4.2.dist-info/licenses/LICENSE +21 -0
  118. ata_coder-2.4.2.dist-info/top_level.txt +1 -0
@@ -0,0 +1,184 @@
1
+ """Command handlers — auto-split from commands.py."""
2
+
3
+ from __future__ import annotations
4
+ from typing import Any
5
+
6
+
7
+ def register_commands(r: Any) -> None:
8
+ """Register this group's commands on the registry."""
9
+ # ── Basic ────────────────────────────────────────────────────────────
10
+ # ── Basic ──────────────────────────────────────────────────────────
11
+
12
+ @r.register("/help", "Show help", "basic")
13
+ def cmd_help(arg: str, ctx: dict) -> bool:
14
+ ctx["ui"].show_help()
15
+ return True
16
+
17
+ @r.register("/quit", "Exit", "basic")
18
+ @r.register("/exit", "Exit", "basic")
19
+ @r.register("/q", "Exit", "basic")
20
+ def cmd_quit(arg: str, ctx: dict) -> bool:
21
+ agent = ctx.get("agent")
22
+ sid = getattr(agent, "_current_session_id", "") if agent else ""
23
+ if sid and "-" in sid:
24
+ parts = sid.split("-")
25
+ if len(parts) >= 2:
26
+ print(f"\nResume this session with:\n ata --resume {parts[-1]}")
27
+ return False
28
+ print("Goodbye!")
29
+ return False
30
+
31
+ @r.register("/clear", "Clear conversation", "basic")
32
+ def cmd_clear(arg: str, ctx: dict) -> bool:
33
+ ctx["agent"].reset()
34
+ print("Conversation cleared.")
35
+ return True
36
+
37
+ @r.register("/context", "Show conversation window", "basic")
38
+ def cmd_context(arg: str, ctx: dict) -> bool:
39
+ agent = ctx["agent"]
40
+ ctx["ui"].show_context(
41
+ total_messages=len(agent._state.messages),
42
+ tool_calls=agent._state.tool_call_count,
43
+ skill=agent.skills.active_skill.name if agent.skills and agent.skills.active_skill else "default",
44
+ model=ctx["config"].llm.model,
45
+ estimated_tokens=agent.get_token_estimate(),
46
+ max_tokens=ctx["config"].agent.max_context_tokens,
47
+ )
48
+ return True
49
+
50
+ @r.register("/compact", "Compact conversation", "basic")
51
+ def cmd_compact(arg: str, ctx: dict) -> bool:
52
+ print(ctx["agent"].compact())
53
+ return True
54
+
55
+ @r.register("/cost", "Estimate cost", "basic")
56
+ def cmd_cost(arg: str, ctx: dict) -> bool:
57
+ from .model_registry import estimate_cost
58
+ agent = ctx["agent"]
59
+ tokens = agent.get_token_estimate()
60
+ model = ctx["config"].llm.model
61
+ cost = estimate_cost(tokens, model)
62
+ print(f"Estimated: ~${cost:.4f} (~{tokens:,} tokens, {model})")
63
+ return True
64
+
65
+ @r.register("/summary", "Conversation summary", "basic")
66
+ def cmd_summary(arg: str, ctx: dict) -> bool:
67
+ print(ctx["agent"].get_conversation_summary())
68
+ return True
69
+
70
+
71
+ # ── Skills ────────────────────────────────────────────────────────────
72
+ # ── Skills ─────────────────────────────────────────────────────────
73
+
74
+ @r.register("/skills", "List skills", "skill")
75
+ def cmd_skills(arg: str, ctx: dict) -> bool:
76
+ sm = ctx.get("skill_mgr")
77
+ if not sm:
78
+ print("Skills not loaded.")
79
+ return True
80
+ for s in sm.list_skills():
81
+ marker = " [active]" if sm.active_skill and sm.active_skill.name == s.name else ""
82
+ print(f" {s.name}{marker}: {s.description[:80]}")
83
+ return True
84
+
85
+ @r.register("/skill", "Switch skill", "skill")
86
+ def cmd_skill(arg: str, ctx: dict) -> bool:
87
+ sm = ctx.get("skill_mgr")
88
+ if not sm:
89
+ print("Skills not loaded.")
90
+ return True
91
+ if arg:
92
+ s = sm.activate(arg)
93
+ print(f"Skill: {s.name}" if s else f"Not found: {arg}")
94
+ else:
95
+ a = sm.active_skill
96
+ print(f"Active: {a.name} - {a.description}" if a else "No active skill.")
97
+ return True
98
+
99
+ @r.register("/skill-auto", "Toggle skill auto-detect", "skill")
100
+ def cmd_skill_auto(arg: str, ctx: dict) -> bool:
101
+ if arg.lower() in ("off", "false", "0"):
102
+ ctx["auto_skill_state"]["value"] = False
103
+ print("Auto-skill: off")
104
+ else:
105
+ ctx["auto_skill_state"]["value"] = True
106
+ print("Auto-skill: on")
107
+ return True
108
+
109
+
110
+ # ── Memory ────────────────────────────────────────────────────────────
111
+ # ── Memory ─────────────────────────────────────────────────────────
112
+
113
+ @r.register("/remember", "Save a memory", "memory")
114
+ def cmd_remember(arg: str, ctx: dict) -> bool:
115
+ store = ctx.get("memory_store")
116
+ if not store:
117
+ print("Memory not loaded.")
118
+ return True
119
+ parts = arg.split("|", 1)
120
+ if len(parts) < 2:
121
+ print("Usage: /remember type/name description | content")
122
+ return True
123
+ header = parts[0].strip()
124
+ content = parts[1].strip()
125
+ header_parts = header.split(maxsplit=1)
126
+ type_name = header_parts[0]
127
+ description = header_parts[1] if len(header_parts) > 1 else ""
128
+
129
+ if "/" in type_name:
130
+ mem_type, name = type_name.split("/", 1)
131
+ else:
132
+ mem_type, name = "reference", type_name
133
+
134
+ from .memory import Memory
135
+ store.add(Memory(name=name, description=description, content=content, metadata={"type": mem_type}))
136
+ print(f"Saved: [{mem_type}] {name}")
137
+ return True
138
+
139
+ @r.register("/recall", "Search memories", "memory")
140
+ def cmd_recall(arg: str, ctx: dict) -> bool:
141
+ store = ctx.get("memory_store")
142
+ if not store:
143
+ print("Memory not loaded.")
144
+ return True
145
+ if not arg:
146
+ print("Usage: /recall <query>")
147
+ return True
148
+ results = store.search(arg)
149
+ if not results:
150
+ print("No matches.")
151
+ return True
152
+ for m in results[:5]:
153
+ print(f"\n[{m.memory_type}] {m.description}\n{m.content[:300]}")
154
+ return True
155
+
156
+ @r.register("/memories", "List memories", "memory")
157
+ def cmd_memories(arg: str, ctx: dict) -> bool:
158
+ store = ctx.get("memory_store")
159
+ if not store:
160
+ print("Memory not loaded.")
161
+ return True
162
+ memories = store.list_all(arg if arg else None)
163
+ if not memories:
164
+ print("No memories.")
165
+ return True
166
+ for m in memories:
167
+ print(f" [{m.memory_type}] {m.name} - {m.description} ({str(m.updated)[:10]})")
168
+ return True
169
+
170
+ @r.register("/forget", "Delete a memory", "memory")
171
+ def cmd_forget(arg: str, ctx: dict) -> bool:
172
+ store = ctx.get("memory_store")
173
+ if not store:
174
+ print("Memory not loaded.")
175
+ return True
176
+ if not arg:
177
+ print("Usage: /forget <name>")
178
+ return True
179
+ ok = store.delete(arg)
180
+ print(f"Deleted: {arg}" if ok else f"Not found: {arg}")
181
+ return True
182
+
183
+
184
+
@@ -0,0 +1,95 @@
1
+ """Command handlers — auto-split from commands.py."""
2
+
3
+ from __future__ import annotations
4
+ from typing import Any
5
+
6
+
7
+ def register_commands(r: Any) -> None:
8
+ """Register this group's commands on the registry."""
9
+ # ── Safety ────────────────────────────────────────────────────────────
10
+ # ── Safety ─────────────────────────────────────────────────────────
11
+
12
+ @r.register("/undo", "Undo changes", "safety")
13
+ def cmd_undo(arg: str, ctx: dict) -> bool:
14
+ agent = ctx["agent"]
15
+ if arg.lower() == "all":
16
+ print(agent.undo_all())
17
+ else:
18
+ try:
19
+ n = int(arg) if arg else 1
20
+ except ValueError:
21
+ n = 1
22
+ print(agent.undo(n))
23
+ return True
24
+
25
+ @r.register("/redo", "Re-apply reverted change", "safety")
26
+ def cmd_redo(arg: str, ctx: dict) -> bool:
27
+ try:
28
+ n = int(arg) if arg else 1
29
+ except ValueError:
30
+ print("Usage: /redo <change-id>")
31
+ return True
32
+ print(ctx["agent"].restore_change(n))
33
+ return True
34
+
35
+ @r.register("/changes", "List file changes", "safety")
36
+ def cmd_changes(arg: str, ctx: dict) -> bool:
37
+ print(ctx["agent"].list_changes())
38
+ return True
39
+
40
+ @r.register("/diff-changes", "Show change diffs", "safety")
41
+ def cmd_diff_changes(arg: str, ctx: dict) -> bool:
42
+ try:
43
+ n = int(arg) if arg else 3
44
+ except ValueError:
45
+ n = 3
46
+ print(ctx["agent"].show_change_diff(n))
47
+ return True
48
+
49
+ @r.register("/dry-run", "Toggle dry-run mode", "safety")
50
+ def cmd_dry_run(arg: str, ctx: dict) -> bool:
51
+ enable = None if not arg else arg.lower() in ("on", "true", "1", "yes")
52
+ print(ctx["agent"].toggle_dry_run(enable))
53
+ return True
54
+
55
+ @r.register("/stats", "Safety stats", "safety")
56
+ def cmd_stats(arg: str, ctx: dict) -> bool:
57
+ a = ctx["agent"]
58
+ if a.fool_proof:
59
+ s = a.fool_proof.stats
60
+ print(f"Blocks: {s['blocks']} Confirmations: {s['confirmations']} "
61
+ f"Changes: {s['tracker_changes']} active "
62
+ f"Dry-run: {'ON' if a.change_tracker and a.change_tracker.dry_run else 'OFF'}")
63
+ return True
64
+
65
+
66
+ # ── Dangerous mode ────────────────────────────────────────────────────────────
67
+ # ── Dangerous mode ─────────────────────────────────────────────────
68
+
69
+ @r.register("/dangerous", "Dangerous mode", "danger")
70
+ def cmd_dangerous(arg: str, ctx: dict) -> bool:
71
+ pm = ctx["agent"].privilege_mgr
72
+ if not pm:
73
+ print("Not available.")
74
+ return True
75
+ al = arg.lower()
76
+ if al in ("on", "enable", "1", "yes"):
77
+ print(pm.enable_dangerous_mode("user-command", timeout_minutes=15))
78
+ elif al in ("off", "disable", "0", "no"):
79
+ print(pm.disable_dangerous_mode())
80
+ elif al == "audit":
81
+ print(pm.get_audit_log())
82
+ elif al == "elevate":
83
+ print(pm.get_elevation_instructions())
84
+ else:
85
+ print(pm.status())
86
+ return True
87
+
88
+ @r.register("/elevate", "Elevation guide", "danger")
89
+ def cmd_elevate(arg: str, ctx: dict) -> bool:
90
+ pm = ctx["agent"].privilege_mgr
91
+ print(pm.get_elevation_instructions() if pm else "Not available.")
92
+ return True
93
+
94
+
95
+
@@ -0,0 +1,241 @@
1
+ """Command handlers — auto-split from commands.py."""
2
+
3
+ from __future__ import annotations
4
+ from typing import Any
5
+
6
+
7
+ def register_commands(r: Any) -> None:
8
+ """Register this group's commands on the registry."""
9
+ # ── Think ────────────────────────────────────────────────────────────
10
+ # ── Think ──────────────────────────────────────────────────────────
11
+
12
+ @r.register("/think", "Thinking mode", "settings")
13
+ def cmd_think(arg: str, ctx: dict) -> bool:
14
+ cfg = ctx["config"]
15
+ strengths = ["off", "low", "medium", "high", "xhigh", "max"]
16
+ if not arg:
17
+ current = cfg.llm.thinking_strength or "off"
18
+ print(f"Thinking: {current} ({' | '.join(strengths)})")
19
+ elif arg.lower() == "off":
20
+ cfg.llm.thinking_strength = ""
21
+ print("Thinking: OFF")
22
+ elif arg.lower() in strengths:
23
+ cfg.llm.thinking_strength = arg.lower()
24
+ print(f"Thinking: {arg.upper()}")
25
+ else:
26
+ print(f"Invalid. Choose: {' | '.join(strengths)}")
27
+ return True
28
+
29
+
30
+ # ── Settings ────────────────────────────────────────────────────────────
31
+ # ── Settings ───────────────────────────────────────────────────────
32
+
33
+ @r.register("/model", "Change model", "settings")
34
+ def cmd_model(arg: str, ctx: dict) -> bool:
35
+ agent = ctx["agent"]
36
+ if not arg:
37
+ print(f"Model: {agent.llm.config.model}")
38
+ return True
39
+ agent.llm.set_model(arg)
40
+ agent.llm.register_tools(agent._all_tools)
41
+ print(f"Model: {arg}")
42
+ return True
43
+
44
+ @r.register("/effort", "Set effort: low/medium/high/xhigh/max", "settings")
45
+ def cmd_effort(arg: str, ctx: dict) -> bool:
46
+ valid = {"low", "medium", "high", "xhigh", "max"}
47
+ if not arg or arg.lower() not in valid:
48
+ current = getattr(ctx.get("config", None), "effort", "medium")
49
+ print(f"Effort: {current} (low / medium / high / xhigh / max)")
50
+ print(" low = haiku, 4K tokens, thinking disabled")
51
+ print(" medium = default, 16K tokens, no thinking")
52
+ print(" high = default, 32K tokens, reasoning_effort=high")
53
+ print(" xhigh = opus, 48K tokens, reasoning_effort=xhigh")
54
+ print(" max = opus, 64K tokens, reasoning_effort=max")
55
+ return True
56
+ level = arg.lower()
57
+ ctx["config"].effort = level
58
+ agent = ctx["agent"]
59
+ if level == "low":
60
+ agent.llm.config.max_tokens = 4096
61
+ agent.llm.config.thinking_strength = "off"
62
+ elif level == "medium":
63
+ agent.llm.config.max_tokens = 16384
64
+ agent.llm.config.thinking_strength = ""
65
+ elif level == "high":
66
+ agent.llm.config.max_tokens = 32768
67
+ agent.llm.config.thinking_strength = "high"
68
+ elif level == "xhigh":
69
+ agent.llm.config.max_tokens = 49152
70
+ agent.llm.config.thinking_strength = "xhigh"
71
+ elif level == "max":
72
+ agent.llm.config.max_tokens = 65536
73
+ agent.llm.config.thinking_strength = "max"
74
+ print(f"Effort: {level}")
75
+ return True
76
+
77
+ @r.register("/models", "List models from API", "settings")
78
+ def cmd_models(arg: str, ctx: dict) -> bool:
79
+ from .model_registry import fetch_available_models
80
+ cfg = ctx["config"]
81
+ models = fetch_available_models(cfg.llm.base_url, cfg.llm.api_key)
82
+ if not models:
83
+ print("Failed to fetch models.")
84
+ return True
85
+ current = cfg.llm.model
86
+ print(f"\n{len(models)} model(s) (current: {current}):")
87
+ for mid in sorted(models):
88
+ print(f" {mid}{' << current' if mid == current else ''}")
89
+ return True
90
+
91
+ @r.register("/workspace", "Change workspace", "settings")
92
+ def cmd_workspace(arg: str, ctx: dict) -> bool:
93
+ from pathlib import Path
94
+ import os
95
+
96
+ cfg = ctx["config"]
97
+ agent = ctx["agent"]
98
+
99
+ if not arg:
100
+ print(f"Workspace: {cfg.agent.workspace_dir}")
101
+ return True
102
+
103
+ new_path = os.path.abspath(os.path.expanduser(arg))
104
+ if not os.path.isdir(new_path):
105
+ print(f"Not found: {arg}")
106
+ return True
107
+
108
+ cfg.agent.workspace_dir = new_path
109
+ agent.tools.workspace = Path(new_path)
110
+ agent.tools.config.workspace_dir = new_path
111
+ print(f"Workspace: {new_path}")
112
+ return True
113
+
114
+ @r.register("/permissions", "Permission rules", "settings")
115
+ def cmd_permissions(arg: str, ctx: dict) -> bool:
116
+ ps = ctx.get("permission_store")
117
+ print(ps.describe() if ps else "Not loaded.")
118
+ return True
119
+
120
+ @r.register("/mcp", "MCP status", "settings")
121
+ def cmd_mcp(arg: str, ctx: dict) -> bool:
122
+ mcp = ctx.get("mcp_client")
123
+ if not mcp:
124
+ print("MCP not configured.")
125
+ return True
126
+ for name in mcp.connected_servers:
127
+ count = sum(
128
+ 1 for t in mcp.get_tools()
129
+ if t["function"]["name"].startswith(f"mcp__{name}__")
130
+ )
131
+ print(f" {name}: {count} tools")
132
+ return True
133
+
134
+ @r.register("/mcp-tools", "List MCP tools", "settings")
135
+ def cmd_mcp_tools(arg: str, ctx: dict) -> bool:
136
+ mcp = ctx.get("mcp_client")
137
+ if not mcp:
138
+ print("MCP not configured.")
139
+ return True
140
+ for t in mcp.get_tools():
141
+ fn = t["function"]
142
+ print(f" {fn['name']}: {fn['description'][:100]}")
143
+ return True
144
+
145
+ @r.register("/mcp search", "Search MCP tools/resources", "settings")
146
+ def cmd_mcp_search(arg: str, ctx: dict) -> bool:
147
+ """Search MCP tools and resources by keyword. Usage: /mcp search <keyword>"""
148
+ mcp = ctx.get("mcp_client")
149
+ if not mcp:
150
+ print("MCP not configured.")
151
+ return True
152
+ if not arg:
153
+ print("Usage: /mcp search <keyword>")
154
+ print(" Searches tool names, descriptions, and resource URIs.")
155
+ return True
156
+
157
+ # Search tools
158
+ tools = mcp.search_tools(arg, limit=15)
159
+ if tools:
160
+ print(f"\n Tools matching '{arg}' ({len(tools)}):")
161
+ for t in tools:
162
+ name = t.get("name", "?")
163
+ desc = (t.get("description") or "")[:80]
164
+ server = t.get("_mcp_server", "?")
165
+ print(f" \033[1m{name}\033[0m @{server}")
166
+ if desc:
167
+ print(f" {desc}")
168
+
169
+ # Search resources
170
+ resources = mcp.search_resources(arg, limit=15)
171
+ if resources:
172
+ print(f"\n Resources matching '{arg}' ({len(resources)}):")
173
+ for r in resources:
174
+ uri = r.get("uri", "?")
175
+ name = r.get("name", "")
176
+ server = r.get("_mcp_server", "?")
177
+ label = f"{name} ({uri})" if name else uri
178
+ print(f" \033[1m{label}\033[0m @{server}")
179
+
180
+ if not tools and not resources:
181
+ print(f" No tools or resources found matching '{arg}'.")
182
+ return True
183
+
184
+ @r.register("/mcp resources", "List/search MCP resources", "settings")
185
+ def cmd_mcp_resources(arg: str, ctx: dict) -> bool:
186
+ """List or search MCP resources. Usage: /mcp resources [keyword]"""
187
+ mcp = ctx.get("mcp_client")
188
+ if not mcp:
189
+ print("MCP not configured.")
190
+ return True
191
+
192
+ if arg:
193
+ resources = mcp.search_resources(arg)
194
+ label = f"matching '{arg}'"
195
+ else:
196
+ resources = mcp.get_all_resources()
197
+ label = "available"
198
+
199
+ if not resources:
200
+ print(f" No resources {label}.")
201
+ return True
202
+
203
+ print(f"\n MCP resources {label} ({len(resources)}):")
204
+ for r in resources:
205
+ uri = r.get("uri", "?")
206
+ name = r.get("name", "")
207
+ desc = (r.get("description") or "")[:80]
208
+ server = r.get("_mcp_server", "?")
209
+ display = name or uri
210
+ print(f" \033[1m{display}\033[0m @{server}")
211
+ if desc:
212
+ print(f" {desc}")
213
+ if name:
214
+ print(f" uri: {uri}")
215
+ return True
216
+
217
+ @r.register("/templates", "List templates", "settings")
218
+ def cmd_templates(arg: str, ctx: dict) -> bool:
219
+ tm = ctx.get("template_mgr")
220
+ if not tm:
221
+ print("Not loaded.")
222
+ return True
223
+ for t in tm.list_templates():
224
+ print(f" {t}")
225
+ return True
226
+
227
+ @r.register("/template", "Render template", "settings")
228
+ def cmd_template(arg: str, ctx: dict) -> bool:
229
+ tm = ctx.get("template_mgr")
230
+ if not tm:
231
+ print("Not loaded.")
232
+ return True
233
+ if not arg:
234
+ print("Usage: /template <name>")
235
+ return True
236
+ r = tm.render(arg)
237
+ print(r if r else f"Not found: {arg}")
238
+ return True
239
+
240
+
241
+