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.
- ata_coder/__init__.py +1 -0
- ata_coder/agent.py +874 -0
- ata_coder/agent_compact.py +190 -0
- ata_coder/agent_controller.py +218 -0
- ata_coder/agent_extension.py +69 -0
- ata_coder/agent_routing.py +105 -0
- ata_coder/agent_subsystems.py +72 -0
- ata_coder/agent_tools.py +318 -0
- ata_coder/agent_undo.py +63 -0
- ata_coder/anthropic_client.py +465 -0
- ata_coder/change_tracker.py +368 -0
- ata_coder/clawd_integration.py +574 -0
- ata_coder/commands/__init__.py +128 -0
- ata_coder/commands/_core.py +184 -0
- ata_coder/commands/_safety.py +95 -0
- ata_coder/commands/_settings.py +241 -0
- ata_coder/commands/_workflow.py +451 -0
- ata_coder/commands.py +974 -0
- ata_coder/config.py +257 -0
- ata_coder/core/__init__.py +35 -0
- ata_coder/core/events.py +73 -0
- ata_coder/core/queue.py +85 -0
- ata_coder/core/state.py +17 -0
- ata_coder/event_queue.py +5 -0
- ata_coder/extension.py +654 -0
- ata_coder/extensions/__init__.py +1 -0
- ata_coder/extensions/hello_skill.py +47 -0
- ata_coder/fool_proof.py +295 -0
- ata_coder/git_workflow.py +371 -0
- ata_coder/gui.py +511 -0
- ata_coder/llm_client.py +543 -0
- ata_coder/main.py +814 -0
- ata_coder/mcp_client.py +1095 -0
- ata_coder/memory.py +539 -0
- ata_coder/model_registry.py +134 -0
- ata_coder/model_router.py +105 -0
- ata_coder/permissions.py +274 -0
- ata_coder/privilege.py +464 -0
- ata_coder/project.py +273 -0
- ata_coder/prompt_template.py +423 -0
- ata_coder/prompts/auto-mode.md +7 -0
- ata_coder/prompts/coding-rules.md +40 -0
- ata_coder/prompts/execution-guardrails.md +14 -0
- ata_coder/prompts/memory-system.md +24 -0
- ata_coder/prompts/output-style.md +23 -0
- ata_coder/prompts/safety.md +17 -0
- ata_coder/prompts/slash-commands.md +24 -0
- ata_coder/prompts/sub-agents.md +38 -0
- ata_coder/prompts/system-reminders.md +17 -0
- ata_coder/prompts/system.md +105 -0
- ata_coder/prompts/tool-policy.md +46 -0
- ata_coder/repl_theme.py +99 -0
- ata_coder/repl_tracker.py +89 -0
- ata_coder/repl_ui.py +1214 -0
- ata_coder/safety_guard.py +434 -0
- ata_coder/self_correct.py +346 -0
- ata_coder/server.py +882 -0
- ata_coder/server_session.py +159 -0
- ata_coder/server_shell.py +129 -0
- ata_coder/session.py +431 -0
- ata_coder/settings.py +439 -0
- ata_coder/setup_wizard.py +136 -0
- ata_coder/skill_extension.py +92 -0
- ata_coder/skills/architect/SKILL.md +42 -0
- ata_coder/skills/code-reviewer/SKILL.md +37 -0
- ata_coder/skills/codecraft/SKILL.md +452 -0
- ata_coder/skills/debugger/SKILL.md +45 -0
- ata_coder/skills/doc-writer/SKILL.md +36 -0
- ata_coder/skills/general-coder/SKILL.md +76 -0
- ata_coder/skills/math-calculator/README.md +40 -0
- ata_coder/skills/math-calculator/SKILL.md +59 -0
- ata_coder/skills/math-calculator/handler.py +103 -0
- ata_coder/skills/math-calculator/prompts/system.md +8 -0
- ata_coder/skills/math-calculator/requirements.txt +2 -0
- ata_coder/skills/math-calculator/resources/constants.json +8 -0
- ata_coder/skills/math-calculator/tests/test_handler.py +53 -0
- ata_coder/skills/security-auditor/SKILL.md +40 -0
- ata_coder/skills/test-writer/SKILL.md +36 -0
- ata_coder/skills/weather-skill/README.md +45 -0
- ata_coder/skills/weather-skill/handler.py +76 -0
- ata_coder/skills/weather-skill/manifest.json +48 -0
- ata_coder/skills/weather-skill/prompts/system_prompt.txt +9 -0
- ata_coder/skills/weather-skill/prompts/user_prompt_template.txt +3 -0
- ata_coder/skills/weather-skill/requirements.txt +1 -0
- ata_coder/skills/weather-skill/resources/city_list.json +17 -0
- ata_coder/skills/weather-skill/resources/error_messages.json +7 -0
- ata_coder/skills/weather-skill/tests/test_handler.py +28 -0
- ata_coder/skills/weather-skill/weather_utils.py +50 -0
- ata_coder/skills.py +1014 -0
- ata_coder/sub_agent.py +273 -0
- ata_coder/sub_agent_manager.py +203 -0
- ata_coder/system_prompt_builder.py +146 -0
- ata_coder/task_planner.py +391 -0
- ata_coder/terminal.py +318 -0
- ata_coder/test_runner.py +219 -0
- ata_coder/thread_supervisor.py +195 -0
- ata_coder/tool_defs.py +335 -0
- ata_coder/tools/__init__.py +11 -0
- ata_coder/tools/definitions.py +335 -0
- ata_coder/tools/executor.py +1036 -0
- ata_coder/tools/result.py +26 -0
- ata_coder/tools/subagent.py +332 -0
- ata_coder/tools/web.py +361 -0
- ata_coder/tools.py +1576 -0
- ata_coder/types.py +92 -0
- ata_coder/utils.py +113 -0
- ata_coder/web/css/style.css +180 -0
- ata_coder/web/index.html +84 -0
- ata_coder/web/js/app.js +489 -0
- ata_coder/web/package-lock.json +25 -0
- ata_coder/web/package.json +10 -0
- ata_coder/web/tsconfig.json +13 -0
- ata_coder-2.4.2.dist-info/METADATA +799 -0
- ata_coder-2.4.2.dist-info/RECORD +118 -0
- ata_coder-2.4.2.dist-info/WHEEL +5 -0
- ata_coder-2.4.2.dist-info/entry_points.txt +2 -0
- ata_coder-2.4.2.dist-info/licenses/LICENSE +21 -0
- 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
|
+
|