luckyd-code 1.2.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.
- luckyd_code/__init__.py +54 -0
- luckyd_code/__main__.py +5 -0
- luckyd_code/_agent_loop.py +551 -0
- luckyd_code/_data_dir.py +73 -0
- luckyd_code/agent.py +38 -0
- luckyd_code/analytics/__init__.py +18 -0
- luckyd_code/analytics/reporter.py +195 -0
- luckyd_code/analytics/scanner.py +443 -0
- luckyd_code/analytics/smells.py +316 -0
- luckyd_code/analytics/trends.py +303 -0
- luckyd_code/api.py +473 -0
- luckyd_code/audit_daemon.py +845 -0
- luckyd_code/autonomous_fixer.py +473 -0
- luckyd_code/background.py +159 -0
- luckyd_code/backup.py +237 -0
- luckyd_code/brain/__init__.py +84 -0
- luckyd_code/brain/assembler.py +100 -0
- luckyd_code/brain/chunker.py +345 -0
- luckyd_code/brain/constants.py +73 -0
- luckyd_code/brain/embedder.py +163 -0
- luckyd_code/brain/graph.py +311 -0
- luckyd_code/brain/indexer.py +316 -0
- luckyd_code/brain/parser.py +140 -0
- luckyd_code/brain/retriever.py +234 -0
- luckyd_code/cli.py +894 -0
- luckyd_code/cli_commands/__init__.py +1 -0
- luckyd_code/cli_commands/audit.py +120 -0
- luckyd_code/cli_commands/background.py +83 -0
- luckyd_code/cli_commands/brain.py +87 -0
- luckyd_code/cli_commands/config.py +75 -0
- luckyd_code/cli_commands/dispatcher.py +695 -0
- luckyd_code/cli_commands/sessions.py +41 -0
- luckyd_code/cli_entry.py +147 -0
- luckyd_code/cli_utils.py +112 -0
- luckyd_code/config.py +205 -0
- luckyd_code/context.py +214 -0
- luckyd_code/cost_tracker.py +209 -0
- luckyd_code/error_reporter.py +508 -0
- luckyd_code/exceptions.py +39 -0
- luckyd_code/export.py +126 -0
- luckyd_code/feedback_analyzer.py +290 -0
- luckyd_code/file_watcher.py +258 -0
- luckyd_code/git/__init__.py +11 -0
- luckyd_code/git/auto_commit.py +157 -0
- luckyd_code/git/tools.py +85 -0
- luckyd_code/hooks.py +236 -0
- luckyd_code/indexer.py +280 -0
- luckyd_code/init.py +39 -0
- luckyd_code/keybindings.py +77 -0
- luckyd_code/log.py +55 -0
- luckyd_code/mcp/__init__.py +6 -0
- luckyd_code/mcp/client.py +184 -0
- luckyd_code/memory/__init__.py +19 -0
- luckyd_code/memory/manager.py +339 -0
- luckyd_code/metrics/__init__.py +5 -0
- luckyd_code/model_registry.py +131 -0
- luckyd_code/orchestrator.py +204 -0
- luckyd_code/permissions/__init__.py +1 -0
- luckyd_code/permissions/manager.py +103 -0
- luckyd_code/planner.py +361 -0
- luckyd_code/plugins.py +91 -0
- luckyd_code/py.typed +0 -0
- luckyd_code/retry.py +57 -0
- luckyd_code/router.py +417 -0
- luckyd_code/sandbox.py +156 -0
- luckyd_code/self_critique.py +2 -0
- luckyd_code/self_improve.py +274 -0
- luckyd_code/sessions.py +114 -0
- luckyd_code/settings.py +72 -0
- luckyd_code/skills/__init__.py +8 -0
- luckyd_code/skills/review.py +22 -0
- luckyd_code/skills/security.py +17 -0
- luckyd_code/tasks/__init__.py +1 -0
- luckyd_code/tasks/manager.py +102 -0
- luckyd_code/templates/icon-192.png +0 -0
- luckyd_code/templates/icon-512.png +0 -0
- luckyd_code/templates/index.html +1965 -0
- luckyd_code/templates/manifest.json +14 -0
- luckyd_code/templates/src/app.js +694 -0
- luckyd_code/templates/src/body.html +767 -0
- luckyd_code/templates/src/cdn.txt +2 -0
- luckyd_code/templates/src/style.css +474 -0
- luckyd_code/templates/sw.js +31 -0
- luckyd_code/templates/test.html +6 -0
- luckyd_code/themes.py +48 -0
- luckyd_code/tools/__init__.py +97 -0
- luckyd_code/tools/agent_tools.py +65 -0
- luckyd_code/tools/bash.py +360 -0
- luckyd_code/tools/brain_tools.py +137 -0
- luckyd_code/tools/browser.py +369 -0
- luckyd_code/tools/datetime_tool.py +34 -0
- luckyd_code/tools/dockerfile_gen.py +212 -0
- luckyd_code/tools/file_ops.py +381 -0
- luckyd_code/tools/game_gen.py +360 -0
- luckyd_code/tools/git_tools.py +130 -0
- luckyd_code/tools/git_worktree.py +63 -0
- luckyd_code/tools/path_validate.py +64 -0
- luckyd_code/tools/project_gen.py +187 -0
- luckyd_code/tools/readme_gen.py +227 -0
- luckyd_code/tools/registry.py +157 -0
- luckyd_code/tools/shell_detect.py +109 -0
- luckyd_code/tools/web.py +89 -0
- luckyd_code/tools/youtube.py +187 -0
- luckyd_code/tools_bridge.py +144 -0
- luckyd_code/undo.py +126 -0
- luckyd_code/update.py +60 -0
- luckyd_code/verify.py +360 -0
- luckyd_code/web_app.py +176 -0
- luckyd_code/web_routes/__init__.py +23 -0
- luckyd_code/web_routes/background.py +73 -0
- luckyd_code/web_routes/brain.py +109 -0
- luckyd_code/web_routes/cost.py +12 -0
- luckyd_code/web_routes/files.py +133 -0
- luckyd_code/web_routes/memories.py +94 -0
- luckyd_code/web_routes/misc.py +67 -0
- luckyd_code/web_routes/project.py +48 -0
- luckyd_code/web_routes/review.py +20 -0
- luckyd_code/web_routes/sessions.py +44 -0
- luckyd_code/web_routes/settings.py +43 -0
- luckyd_code/web_routes/static.py +70 -0
- luckyd_code/web_routes/update.py +19 -0
- luckyd_code/web_routes/ws.py +237 -0
- luckyd_code-1.2.2.dist-info/METADATA +297 -0
- luckyd_code-1.2.2.dist-info/RECORD +127 -0
- luckyd_code-1.2.2.dist-info/WHEEL +4 -0
- luckyd_code-1.2.2.dist-info/entry_points.txt +3 -0
- luckyd_code-1.2.2.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,695 @@
|
|
|
1
|
+
"""Command dispatcher for the CLI REPL.
|
|
2
|
+
|
|
3
|
+
All ``/command`` handlers live here so the Repl class stays focused on
|
|
4
|
+
chat-loop orchestration rather than CLI argument parsing.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import sys
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
from rich.markdown import Markdown
|
|
11
|
+
from rich.panel import Panel
|
|
12
|
+
|
|
13
|
+
from ..cli_utils import console
|
|
14
|
+
from .. import memory as memory_module
|
|
15
|
+
from .. import tasks, planner, init as project_init
|
|
16
|
+
from .. import update as updater
|
|
17
|
+
from ..skills import review as review_skill
|
|
18
|
+
from ..skills import security as security_skill
|
|
19
|
+
|
|
20
|
+
from .background import handle_background_command
|
|
21
|
+
from .sessions import handle_sessions_command
|
|
22
|
+
from .brain import handle_brain_command
|
|
23
|
+
from .config import handle_config_command
|
|
24
|
+
from .audit import handle_audit_command
|
|
25
|
+
from ..backup import create_backup, list_backups, restore_backup, format_backup_list
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def handle_command(repl, cmd: str):
|
|
29
|
+
"""Dispatch a /command to the appropriate handler."""
|
|
30
|
+
parts = cmd.split()
|
|
31
|
+
command = parts[0].lower()
|
|
32
|
+
args = parts[1:]
|
|
33
|
+
|
|
34
|
+
if command in ("/exit", "/quit"):
|
|
35
|
+
console.print("Goodbye!")
|
|
36
|
+
sys.exit(0)
|
|
37
|
+
|
|
38
|
+
elif command == "/stop":
|
|
39
|
+
stopped_bg = 0
|
|
40
|
+
if repl.background:
|
|
41
|
+
for task in repl.background.tasks.values():
|
|
42
|
+
if task.status in ("running", "pending"):
|
|
43
|
+
task.status = "stopped"
|
|
44
|
+
if not task.finished_at:
|
|
45
|
+
from datetime import datetime
|
|
46
|
+
task.finished_at = datetime.now().isoformat()
|
|
47
|
+
stopped_bg += 1
|
|
48
|
+
if stopped_bg:
|
|
49
|
+
console.print(f"[yellow]⏹ Stopped {stopped_bg} background task(s)[/yellow]")
|
|
50
|
+
else:
|
|
51
|
+
console.print("[dim]No background tasks running. Use Ctrl+C to stop a live response.[/dim]")
|
|
52
|
+
|
|
53
|
+
elif command == "/clear":
|
|
54
|
+
repl.context.reset()
|
|
55
|
+
repl._load_memory()
|
|
56
|
+
console.print("[cyan]Conversation reset.[/cyan]")
|
|
57
|
+
|
|
58
|
+
elif command == "/backup":
|
|
59
|
+
if args and args[0].lower() == "list":
|
|
60
|
+
backups = list_backups(cwd=repl.config.working_directory)
|
|
61
|
+
console.print(format_backup_list(backups))
|
|
62
|
+
elif args and args[0].lower() == "restore":
|
|
63
|
+
ref = args[1] if len(args) > 1 else "1"
|
|
64
|
+
console.print(f"[yellow]Restoring backup {ref}...[/yellow]")
|
|
65
|
+
result = restore_backup(ref, cwd=repl.config.working_directory)
|
|
66
|
+
if result["ok"]:
|
|
67
|
+
console.print(f"[green]{result['message']}[/green]")
|
|
68
|
+
else:
|
|
69
|
+
console.print(f"[red]Restore failed: {result['error']}[/red]")
|
|
70
|
+
else:
|
|
71
|
+
message = " ".join(args) if args else "manual backup"
|
|
72
|
+
console.print("[yellow]Creating backup...[/yellow]")
|
|
73
|
+
result = create_backup(message, cwd=repl.config.working_directory)
|
|
74
|
+
if result["ok"]:
|
|
75
|
+
console.print(f"[green]{result['message']}[/green]")
|
|
76
|
+
else:
|
|
77
|
+
console.print(f"[red]Backup failed: {result['error']}[/red]")
|
|
78
|
+
|
|
79
|
+
elif command == "/deps":
|
|
80
|
+
symbol = " ".join(args)
|
|
81
|
+
if not symbol:
|
|
82
|
+
console.print("[yellow]Usage: /deps <symbol_name>[/yellow]")
|
|
83
|
+
else:
|
|
84
|
+
from rich.table import Table
|
|
85
|
+
deps = repl.brain.find_dependents(symbol)
|
|
86
|
+
if deps:
|
|
87
|
+
table = Table(title=f"Nodes that depend on '{symbol}'")
|
|
88
|
+
table.add_column("Dependent", style="cyan")
|
|
89
|
+
table.add_column("File", style="green")
|
|
90
|
+
table.add_column("Relation", style="yellow")
|
|
91
|
+
for d in deps:
|
|
92
|
+
table.add_row(d["name"], d["file"], d["relation"])
|
|
93
|
+
console.print(table)
|
|
94
|
+
else:
|
|
95
|
+
console.print(f"[dim]No dependents found for '{symbol}' in the knowledge graph.[/dim]")
|
|
96
|
+
|
|
97
|
+
elif command == "/help":
|
|
98
|
+
from rich.table import Table
|
|
99
|
+
|
|
100
|
+
def _cmd_table(title: str, cmds: list[tuple[str, str]], style: str = "cyan") -> Table:
|
|
101
|
+
t = Table(title=title, title_style=f"bold {style}", box=None,
|
|
102
|
+
show_header=False, padding=(0, 2, 0, 0))
|
|
103
|
+
t.add_column("Cmd", style=style, no_wrap=True)
|
|
104
|
+
t.add_column("Desc", style="white")
|
|
105
|
+
for cmd, desc in cmds:
|
|
106
|
+
t.add_row(cmd, desc)
|
|
107
|
+
return t
|
|
108
|
+
|
|
109
|
+
# ── Commands ──────────────────────────────────────────────
|
|
110
|
+
console.print()
|
|
111
|
+
console.print("[bold]╭─ Commands[/bold]")
|
|
112
|
+
console.print("[bold]│[/bold]")
|
|
113
|
+
|
|
114
|
+
sections = [
|
|
115
|
+
("Chat", [
|
|
116
|
+
("/help", "Show this help"),
|
|
117
|
+
("/clear", "Reset conversation"),
|
|
118
|
+
("/stop", "Stop background tasks (Ctrl+C to stop live response)"),
|
|
119
|
+
("/compact", "Compact conversation history"),
|
|
120
|
+
("/undo", "Undo last file write/edit"),
|
|
121
|
+
("/model", "Show current model settings"),
|
|
122
|
+
("/tokens", "Show message count"),
|
|
123
|
+
("/stats", "Show message stats (counts, chars)"),
|
|
124
|
+
("/cost", "Show session & cumulative cost"),
|
|
125
|
+
("/cost reset", "Clear cumulative cost history"),
|
|
126
|
+
("/version", "Show version"),
|
|
127
|
+
("/exit", "Exit (or Ctrl+D)"),
|
|
128
|
+
]),
|
|
129
|
+
("Export & Backup", [
|
|
130
|
+
("/export md [file]", "Export conversation as Markdown"),
|
|
131
|
+
("/export html [file]", "Export conversation as HTML"),
|
|
132
|
+
("/backup", "Git snapshot (auto before /debug, /self-improve)"),
|
|
133
|
+
("/backup list", "List recent backups"),
|
|
134
|
+
("/backup restore [#]", "Restore project to a backup"),
|
|
135
|
+
]),
|
|
136
|
+
("Config", [
|
|
137
|
+
("/config list", "Show all settings"),
|
|
138
|
+
("/config get <key>", "Get a setting value"),
|
|
139
|
+
("/config set <key> <value>", "Set a setting value"),
|
|
140
|
+
]),
|
|
141
|
+
("Memory & Sessions", [
|
|
142
|
+
("/memory", "Show project memory (MEMORY.md)"),
|
|
143
|
+
("/memory save <name>", "Save conversation to memory"),
|
|
144
|
+
("/memory search <query>", "Search persistent memories"),
|
|
145
|
+
("/memory list", "List all memories"),
|
|
146
|
+
("/memory delete <name>", "Delete a memory"),
|
|
147
|
+
("/init", "Create MEMORY.md"),
|
|
148
|
+
("/sessions list", "List saved conversations"),
|
|
149
|
+
("/sessions save <name>", "Save conversation"),
|
|
150
|
+
("/sessions load <name>", "Load conversation"),
|
|
151
|
+
("/sessions delete <name>", "Delete session"),
|
|
152
|
+
]),
|
|
153
|
+
("Models & Routing", [
|
|
154
|
+
("/models", "List available models by tier"),
|
|
155
|
+
("/models set <id>", "Switch to a specific model"),
|
|
156
|
+
("/route <text>", "Show routing decision for input"),
|
|
157
|
+
]),
|
|
158
|
+
("Agents & Automation", [
|
|
159
|
+
("/debug", "Auto debug loop (test → fix → re-run)"),
|
|
160
|
+
("/orchestrate <task>", "Researcher → coder → reviewer pipeline"),
|
|
161
|
+
("/background start <task>", "Run task in background"),
|
|
162
|
+
("/background status [id]", "Check background task status"),
|
|
163
|
+
("/background result <id>", "View background task result"),
|
|
164
|
+
("/background list", "List all background tasks"),
|
|
165
|
+
]),
|
|
166
|
+
("Knowledge Graph", [
|
|
167
|
+
("/brain", "Show knowledge graph status"),
|
|
168
|
+
("/brain rebuild", "Re-index Python files into graph"),
|
|
169
|
+
("/brain stats", "Detailed graph statistics"),
|
|
170
|
+
("/index", "Re-index project structure"),
|
|
171
|
+
("/deps <symbol>", "Find dependents of a symbol"),
|
|
172
|
+
("/watch start", "Auto-reindex on file changes"),
|
|
173
|
+
("/watch stop", "Stop file watcher"),
|
|
174
|
+
("/watch status", "Show file watcher state"),
|
|
175
|
+
]),
|
|
176
|
+
("Tasks & Git", [
|
|
177
|
+
("/tasks", "List tasks"),
|
|
178
|
+
("/plan", "List saved plans"),
|
|
179
|
+
("/plan <task>", "Draft → approve → execute (interactive)"),
|
|
180
|
+
("/parallel <task>", "Decompose & run subtasks concurrently"),
|
|
181
|
+
("/branch <task>", "Create & checkout a task branch"),
|
|
182
|
+
("/finish [--pr]", "Commit all changes + optional PR"),
|
|
183
|
+
]),
|
|
184
|
+
("Skills & Tools", [
|
|
185
|
+
("/review", "Review git changes"),
|
|
186
|
+
("/security-review", "Run security analysis"),
|
|
187
|
+
("/sandbox status", "Check Docker sandbox availability"),
|
|
188
|
+
("/allow <tool>", "Allow a tool without confirmation"),
|
|
189
|
+
("/update", "Update DeepSeek Code"),
|
|
190
|
+
]),
|
|
191
|
+
]
|
|
192
|
+
|
|
193
|
+
for title, cmds in sections:
|
|
194
|
+
console.print(_cmd_table(title, cmds))
|
|
195
|
+
console.print()
|
|
196
|
+
|
|
197
|
+
# ── Tools ────────────────────────────────────────────────
|
|
198
|
+
console.print("[bold]╭─ Tools[/bold]")
|
|
199
|
+
console.print("[bold]│[/bold]")
|
|
200
|
+
|
|
201
|
+
builtin_tools = repl.registry.list_tools()
|
|
202
|
+
mcp_tools = repl.mcp.get_all_tools()
|
|
203
|
+
total_count = len(builtin_tools) + len(mcp_tools)
|
|
204
|
+
|
|
205
|
+
# Build name→description lookup
|
|
206
|
+
name_to_desc = {}
|
|
207
|
+
for t in builtin_tools:
|
|
208
|
+
name_to_desc[t["function"]["name"]] = t["function"]["description"]
|
|
209
|
+
|
|
210
|
+
tool_categories = {
|
|
211
|
+
"📁 Files": ["Read", "Write", "Edit", "Glob", "Grep"],
|
|
212
|
+
"💻 Shell": ["Bash"],
|
|
213
|
+
"🕐 Date/Time": ["DateTime"],
|
|
214
|
+
"🌐 Web": ["WebFetch", "WebSearch"],
|
|
215
|
+
"🔧 Git": ["GitStatus", "GitDiff", "GitLog", "GitCommit", "GitAdd",
|
|
216
|
+
"GitBranch", "GitPR", "GitPush", "GitWorktree"],
|
|
217
|
+
"🤖 Agents": ["SubAgent", "AgentHandoff"],
|
|
218
|
+
"🧠 Codebase": ["BrainSearch", "BrainStatus"],
|
|
219
|
+
"🌍 Browser": ["BrowserNavigate", "BrowserClick", "BrowserType",
|
|
220
|
+
"BrowserSnapshot", "BrowserScreenshot", "BrowserEvaluate",
|
|
221
|
+
"BrowserClose", "OpenInBrowser"],
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
for cat_name, tool_names in tool_categories.items():
|
|
225
|
+
visible = [n for n in tool_names if n in name_to_desc]
|
|
226
|
+
if not visible:
|
|
227
|
+
continue
|
|
228
|
+
rows = []
|
|
229
|
+
for name in visible:
|
|
230
|
+
desc = name_to_desc.get(name, "")
|
|
231
|
+
if len(desc) > 100:
|
|
232
|
+
desc = desc[:100] + "…"
|
|
233
|
+
rows.append((f"`{name}`", desc))
|
|
234
|
+
cat_table = _cmd_table(cat_name, rows, style="green")
|
|
235
|
+
console.print(cat_table)
|
|
236
|
+
console.print()
|
|
237
|
+
|
|
238
|
+
if mcp_tools:
|
|
239
|
+
mcp_rows = []
|
|
240
|
+
for t in mcp_tools:
|
|
241
|
+
name = t["function"]["name"]
|
|
242
|
+
desc = t["function"].get("description", "")[:80]
|
|
243
|
+
mcp_rows.append((f"`{name}`", desc))
|
|
244
|
+
console.print(_cmd_table(f"🔌 MCP Tools ({len(mcp_tools)})", mcp_rows, style="magenta"))
|
|
245
|
+
console.print()
|
|
246
|
+
|
|
247
|
+
console.print(f"[dim]Total: {total_count} tools[/dim]")
|
|
248
|
+
console.print("[dim]Enter to submit · Alt+Enter for newline[/dim]")
|
|
249
|
+
console.print()
|
|
250
|
+
|
|
251
|
+
elif command == "/model":
|
|
252
|
+
if args and args[0] == "list":
|
|
253
|
+
repl._handle_model_list()
|
|
254
|
+
return
|
|
255
|
+
provider_str = f" ({repl.config.provider})" if repl.config.provider != "deepseek" else ""
|
|
256
|
+
console.print(f"[cyan]Model:[/cyan] {repl.config.model}{provider_str}")
|
|
257
|
+
console.print(f"[cyan]Provider:[/cyan] {repl.config.provider}")
|
|
258
|
+
console.print(f"[cyan]Temperature:[/cyan] {repl.config.temperature}")
|
|
259
|
+
console.print(f"[cyan]Max tokens:[/cyan] {repl.config.max_tokens}")
|
|
260
|
+
console.print(f"[cyan]API Base:[/cyan] {repl.config.base_url}")
|
|
261
|
+
console.print(f"[cyan]Context messages:[/cyan] {repl.context.count_messages()}")
|
|
262
|
+
|
|
263
|
+
elif command == "/models":
|
|
264
|
+
from ..model_registry import format_model_list
|
|
265
|
+
if args and args[0] == "set":
|
|
266
|
+
new_model = " ".join(args[1:])
|
|
267
|
+
if new_model:
|
|
268
|
+
repl.config.model = new_model
|
|
269
|
+
repl.config.save()
|
|
270
|
+
console.print(f"[green]Model set to: {new_model}[/green]")
|
|
271
|
+
else:
|
|
272
|
+
console.print("[yellow]Usage: /models set <model_id>[/yellow]")
|
|
273
|
+
else:
|
|
274
|
+
console.print(format_model_list())
|
|
275
|
+
console.print(f"\n[cyan]Current model:[/cyan] {repl.config.model}")
|
|
276
|
+
console.print("[dim]Use /models set <model_id> to switch[/dim]")
|
|
277
|
+
|
|
278
|
+
elif command == "/route":
|
|
279
|
+
from ..router import show_current_routing
|
|
280
|
+
if args:
|
|
281
|
+
sample = " ".join(args)
|
|
282
|
+
info = show_current_routing(sample, 0, repl.config.model)
|
|
283
|
+
console.print(Panel(info, title=f"Routing for: {sample[:50]}", border_style="cyan"))
|
|
284
|
+
else:
|
|
285
|
+
console.print("[yellow]Usage: /route <text>[/yellow]")
|
|
286
|
+
console.print("[dim]Example: /route fix this bug in auth.py[/dim]")
|
|
287
|
+
|
|
288
|
+
elif command == "/tokens":
|
|
289
|
+
console.print(f"Messages in context: {repl.context.count_messages()}")
|
|
290
|
+
|
|
291
|
+
elif command == "/version":
|
|
292
|
+
console.print(f"DeepSeek Code v{updater.get_version()}")
|
|
293
|
+
|
|
294
|
+
elif command == "/compact":
|
|
295
|
+
console.print("[yellow]Compacting conversation...[/yellow]")
|
|
296
|
+
result = repl.context.compact(
|
|
297
|
+
repl.config, repl.config.model,
|
|
298
|
+
on_compact=lambda s, c: repl.memory_mgr.save_conversation_summary(s, c),
|
|
299
|
+
)
|
|
300
|
+
console.print(f"[green]{result}[/green]")
|
|
301
|
+
|
|
302
|
+
elif command == "/cost":
|
|
303
|
+
if args and args[0].lower() == "reset":
|
|
304
|
+
result = repl.cost_tracker.reset_cumulative()
|
|
305
|
+
console.print(f"[green]{result}[/green]")
|
|
306
|
+
else:
|
|
307
|
+
console.print(repl.cost_tracker.get_stats())
|
|
308
|
+
console.print("[dim]Use /cost reset to clear cumulative history[/dim]")
|
|
309
|
+
|
|
310
|
+
elif command == "/stats":
|
|
311
|
+
total = repl.context.count_messages()
|
|
312
|
+
user_msgs = sum(1 for m in repl.context.messages if m["role"] == "user")
|
|
313
|
+
asst_msgs = sum(1 for m in repl.context.messages if m["role"] == "assistant")
|
|
314
|
+
tool_msgs = sum(1 for m in repl.context.messages if m["role"] == "tool")
|
|
315
|
+
total_chars = sum(len(str(m.get("content", ""))) for m in repl.context.messages)
|
|
316
|
+
console.print(f"[cyan]Messages:[/cyan] {total} ({user_msgs} user, {asst_msgs} assistant, {tool_msgs} tool)")
|
|
317
|
+
console.print(f"[cyan]Total chars:[/cyan] {total_chars:,}")
|
|
318
|
+
console.print(f"[cyan]Model:[/cyan] {repl.config.model}")
|
|
319
|
+
console.print(f"[cyan]Temperature:[/cyan] {repl.config.temperature}")
|
|
320
|
+
|
|
321
|
+
elif command == "/config":
|
|
322
|
+
handle_config_command(repl, args)
|
|
323
|
+
|
|
324
|
+
elif command == "/export":
|
|
325
|
+
if not args:
|
|
326
|
+
console.print("[yellow]Usage: /export md|html [filepath][/yellow]")
|
|
327
|
+
return
|
|
328
|
+
fmt = args[0].lower()
|
|
329
|
+
filepath = None
|
|
330
|
+
if len(args) > 1:
|
|
331
|
+
filepath = " ".join(args[1:])
|
|
332
|
+
else:
|
|
333
|
+
from datetime import datetime
|
|
334
|
+
desktop = Path.home() / "OneDrive" / "Desktop"
|
|
335
|
+
ts = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
336
|
+
ext = "md" if fmt == "md" else "html"
|
|
337
|
+
filepath = str(desktop / f"deepseek_export_{ts}.{ext}")
|
|
338
|
+
if fmt == "md":
|
|
339
|
+
from ..export import export_markdown
|
|
340
|
+
result = export_markdown(repl.context.messages, filepath)
|
|
341
|
+
if filepath:
|
|
342
|
+
console.print(f"[green]Exported to {filepath}[/green]")
|
|
343
|
+
else:
|
|
344
|
+
console.print(result)
|
|
345
|
+
elif fmt == "html":
|
|
346
|
+
from ..export import export_html
|
|
347
|
+
result = export_html(repl.context.messages, filepath)
|
|
348
|
+
if filepath:
|
|
349
|
+
console.print(f"[green]Exported to {filepath}[/green]")
|
|
350
|
+
else:
|
|
351
|
+
console.print(result[:2000])
|
|
352
|
+
else:
|
|
353
|
+
console.print(f"[red]Unknown format: {fmt}. Use md or html.[/red]")
|
|
354
|
+
|
|
355
|
+
elif command == "/update":
|
|
356
|
+
console.print("[yellow]Checking for updates...[/yellow]")
|
|
357
|
+
result = updater.do_update()
|
|
358
|
+
console.print(f"[green]{result}[/green]")
|
|
359
|
+
|
|
360
|
+
elif command == "/memory":
|
|
361
|
+
if not args:
|
|
362
|
+
md = memory_module.load_claude_md()
|
|
363
|
+
if md:
|
|
364
|
+
console.print(Panel(md, title="Project Memory (MEMORY.md)", border_style="cyan"))
|
|
365
|
+
else:
|
|
366
|
+
console.print("[yellow]No MEMORY.md found[/yellow]")
|
|
367
|
+
return
|
|
368
|
+
|
|
369
|
+
sub = args[0].lower()
|
|
370
|
+
if sub == "save":
|
|
371
|
+
name = " ".join(args[1:]) if args[1:] else "unnamed"
|
|
372
|
+
recent_msgs = repl.context.messages[-4:]
|
|
373
|
+
summary_parts = []
|
|
374
|
+
for m in recent_msgs:
|
|
375
|
+
role = m.get("role", "?")
|
|
376
|
+
content = str(m.get("content", ""))[:300]
|
|
377
|
+
if content:
|
|
378
|
+
summary_parts.append(f"{role}: {content}")
|
|
379
|
+
summary = "\n\n".join(summary_parts) or "(empty conversation)"
|
|
380
|
+
repl.memory_mgr.save_memory(name, summary)
|
|
381
|
+
console.print(f"[green]Saved memory '{name}'[/green]")
|
|
382
|
+
|
|
383
|
+
elif sub == "search":
|
|
384
|
+
query = " ".join(args[1:]) if args[1:] else ""
|
|
385
|
+
if not query:
|
|
386
|
+
console.print("[yellow]Usage: /memory search <query>[/yellow]")
|
|
387
|
+
return
|
|
388
|
+
results = repl.memory_mgr.search_memories(query)
|
|
389
|
+
if results:
|
|
390
|
+
lines = ["[bold]Memory search results:[/bold]\n"]
|
|
391
|
+
for r in results:
|
|
392
|
+
lines.append(f"[cyan]{r['name']}[/cyan] (score: {r['score']})")
|
|
393
|
+
lines.append(f" {r['snippet'][:200]}")
|
|
394
|
+
console.print("\n".join(lines))
|
|
395
|
+
else:
|
|
396
|
+
console.print("[yellow]No memories found[/yellow]")
|
|
397
|
+
|
|
398
|
+
elif sub == "list":
|
|
399
|
+
memories = repl.memory_mgr.list_memories()
|
|
400
|
+
if memories:
|
|
401
|
+
lines = ["[bold]Memories:[/bold]\n"]
|
|
402
|
+
for m in memories:
|
|
403
|
+
lines.append(f" [cyan]{m['name']}[/cyan] ({m['type']})")
|
|
404
|
+
console.print("\n".join(lines))
|
|
405
|
+
else:
|
|
406
|
+
console.print("[yellow]No memories yet[/yellow]")
|
|
407
|
+
|
|
408
|
+
elif sub == "delete":
|
|
409
|
+
name = " ".join(args[1:]) if args[1:] else ""
|
|
410
|
+
if not name:
|
|
411
|
+
console.print("[yellow]Usage: /memory delete <name>[/yellow]")
|
|
412
|
+
return
|
|
413
|
+
ok = repl.memory_mgr.delete_memory(name)
|
|
414
|
+
if ok:
|
|
415
|
+
console.print(f"[green]Deleted memory '{name}'[/green]")
|
|
416
|
+
else:
|
|
417
|
+
console.print(f"[yellow]Memory '{name}' not found[/yellow]")
|
|
418
|
+
|
|
419
|
+
else:
|
|
420
|
+
console.print("[yellow]Usage: /memory [save|search|list|delete] [args][/yellow]")
|
|
421
|
+
|
|
422
|
+
elif command == "/index":
|
|
423
|
+
from ..indexer import index_project
|
|
424
|
+
with console.status("Indexing project...", spinner="dots"):
|
|
425
|
+
project_context = index_project()
|
|
426
|
+
if project_context:
|
|
427
|
+
new_content = f"<project-context>\n{project_context}\n</project-context>"
|
|
428
|
+
replaced = False
|
|
429
|
+
for i, m in enumerate(repl.context.messages):
|
|
430
|
+
content = str(m.get("content", ""))
|
|
431
|
+
if content.startswith("<project-context>") and content.endswith("</project-context>"):
|
|
432
|
+
repl.context.messages[i]["content"] = new_content
|
|
433
|
+
console.print(f"[green]Re-indexed project ({project_context.count(chr(10)) + 1} items)[/green]")
|
|
434
|
+
replaced = True
|
|
435
|
+
break
|
|
436
|
+
if not replaced:
|
|
437
|
+
repl.context.messages.insert(1, {
|
|
438
|
+
"role": "user",
|
|
439
|
+
"content": new_content,
|
|
440
|
+
})
|
|
441
|
+
console.print(f"[green]Indexed project ({project_context.count(chr(10)) + 1} items)[/green]")
|
|
442
|
+
else:
|
|
443
|
+
console.print("[yellow]No project files found to index[/yellow]")
|
|
444
|
+
|
|
445
|
+
elif command == "/init":
|
|
446
|
+
result = project_init.init_project()
|
|
447
|
+
console.print(f"[green]{result}[/green]")
|
|
448
|
+
|
|
449
|
+
elif command == "/tasks":
|
|
450
|
+
status = args[0] if args else None
|
|
451
|
+
result = tasks.list_tasks(status)
|
|
452
|
+
console.print(result)
|
|
453
|
+
|
|
454
|
+
elif command == "/plan":
|
|
455
|
+
if not args:
|
|
456
|
+
plans = planner.list_plans()
|
|
457
|
+
console.print(Markdown(f"**Saved plans:**\n\n{plans}"))
|
|
458
|
+
console.print("[dim]Usage: /plan <task description> — to create and execute a plan[/dim]")
|
|
459
|
+
return
|
|
460
|
+
|
|
461
|
+
task = " ".join(args)
|
|
462
|
+
approved_plan = planner.plan_and_approve(task, repl.config, repl.session)
|
|
463
|
+
if approved_plan is None:
|
|
464
|
+
return
|
|
465
|
+
|
|
466
|
+
console.print("\n[bold cyan]Executing approved plan...[/bold cyan]")
|
|
467
|
+
result = planner.execute_plan(approved_plan, task, repl.config)
|
|
468
|
+
console.print(result)
|
|
469
|
+
|
|
470
|
+
elif command == "/parallel":
|
|
471
|
+
if not args:
|
|
472
|
+
console.print("[yellow]Usage: /parallel <task description>[/yellow]")
|
|
473
|
+
console.print("[dim]Decomposes the task into parallel subtasks and runs them concurrently.[/dim]")
|
|
474
|
+
return
|
|
475
|
+
|
|
476
|
+
task = " ".join(args)
|
|
477
|
+
console.print(f"\n[bold cyan]Parallel execution:[/bold cyan] {task}")
|
|
478
|
+
|
|
479
|
+
from ..parallel_executor import ParallelExecutor
|
|
480
|
+
|
|
481
|
+
def _on_progress(role: str, status: str):
|
|
482
|
+
icons = {"queued": "○", "running": "◉", "done": "✓", "error": "✗", "timeout": "⏱"}
|
|
483
|
+
icon = icons.get(status, "·")
|
|
484
|
+
colors = {"queued": "dim", "running": "cyan", "done": "green", "error": "red", "timeout": "yellow"}
|
|
485
|
+
color = colors.get(status, "white")
|
|
486
|
+
console.print(f" [{color}]{icon} {role}[/{color}]")
|
|
487
|
+
|
|
488
|
+
executor = ParallelExecutor(repl.config, max_workers=4)
|
|
489
|
+
result = executor.run(task, on_progress=_on_progress)
|
|
490
|
+
console.print("\n" + result)
|
|
491
|
+
|
|
492
|
+
elif command == "/branch":
|
|
493
|
+
from ..git.workflow import start_task_branch, in_git_repo
|
|
494
|
+
|
|
495
|
+
if not args:
|
|
496
|
+
console.print("[yellow]Usage: /branch <task description>[/yellow]")
|
|
497
|
+
console.print("[dim]Creates and checks out a git branch named task/<slug>[/dim]")
|
|
498
|
+
return
|
|
499
|
+
|
|
500
|
+
if not in_git_repo(repl.config.working_directory):
|
|
501
|
+
console.print("[red]Not in a git repository.[/red]")
|
|
502
|
+
return
|
|
503
|
+
|
|
504
|
+
task = " ".join(args)
|
|
505
|
+
ok, result = start_task_branch(task, cwd=repl.config.working_directory)
|
|
506
|
+
if ok:
|
|
507
|
+
console.print(f"[green]Switched to branch:[/green] {result}")
|
|
508
|
+
console.print("[dim]Use /finish when done to commit and optionally open a PR.[/dim]")
|
|
509
|
+
else:
|
|
510
|
+
console.print(f"[red]Branch failed:[/red] {result}")
|
|
511
|
+
|
|
512
|
+
elif command == "/finish":
|
|
513
|
+
from ..git.workflow import finish_task, in_git_repo, current_branch
|
|
514
|
+
|
|
515
|
+
if not in_git_repo(repl.config.working_directory):
|
|
516
|
+
console.print("[red]Not in a git repository.[/red]")
|
|
517
|
+
return
|
|
518
|
+
|
|
519
|
+
branch = current_branch(repl.config.working_directory)
|
|
520
|
+
if not branch.startswith("task/"):
|
|
521
|
+
console.print(f"[yellow]Current branch '{branch}' is not a task branch — committing anyway.[/yellow]")
|
|
522
|
+
|
|
523
|
+
make_pr = "--pr" in args
|
|
524
|
+
task_args = [a for a in args if a != "--pr"]
|
|
525
|
+
task = " ".join(task_args) if task_args else branch.replace("task/", "").replace("-", " ")
|
|
526
|
+
|
|
527
|
+
console.print(f"[bold cyan]Finishing task:[/bold cyan] {task}")
|
|
528
|
+
result = finish_task(task, repl.config, make_pr=make_pr, cwd=repl.config.working_directory)
|
|
529
|
+
console.print(result)
|
|
530
|
+
if not make_pr:
|
|
531
|
+
console.print("[dim]Tip: /finish --pr to also open a GitHub PR.[/dim]")
|
|
532
|
+
|
|
533
|
+
elif command == "/review":
|
|
534
|
+
diff = review_skill.review_changes()
|
|
535
|
+
console.print(Markdown(f"**Code Review**\n\n{diff}"))
|
|
536
|
+
|
|
537
|
+
elif command == "/security-review":
|
|
538
|
+
analysis = security_skill.security_review()
|
|
539
|
+
console.print(Markdown(f"**Security Review**\n\n{analysis}"))
|
|
540
|
+
|
|
541
|
+
elif command == "/allow":
|
|
542
|
+
if args:
|
|
543
|
+
from ..permissions.manager import _save_to_allowlist
|
|
544
|
+
_save_to_allowlist(args[0])
|
|
545
|
+
console.print(f"[green]Allowed {args[0]}[/green]")
|
|
546
|
+
else:
|
|
547
|
+
console.print("[yellow]Usage: /allow <tool_name>[/yellow]")
|
|
548
|
+
|
|
549
|
+
elif command == "/debug":
|
|
550
|
+
DEBUG_PROMPT = (
|
|
551
|
+
"You are now in DEBUG MODE. Your single purpose is to make ALL tests pass.\n\n"
|
|
552
|
+
"FOLLOW THIS PROTOCOL STEP BY STEP. Do NOT skip steps.\n\n"
|
|
553
|
+
"STEP 1 — DISCOVER: Find how to run tests. Use Glob to find test files.\n"
|
|
554
|
+
"STEP 2 — RUN: Execute the test command via Bash. Show the full output.\n"
|
|
555
|
+
"STEP 3 — EVALUATE: If ALL tests pass, report success.\n"
|
|
556
|
+
"STEP 4 — ANALYZE: Read failing test output. Identify which tests fail.\n"
|
|
557
|
+
"STEP 5 — DIAGNOSE: Explain the root cause of each failure.\n"
|
|
558
|
+
"STEP 6 — FIX: Edit source files to fix issues.\n"
|
|
559
|
+
"STEP 7 — RE-RUN: Run the tests again.\n"
|
|
560
|
+
"STEP 8 — REPEAT: If any tests still fail, go back to STEP 4. Maximum 5 iterations.\n\n"
|
|
561
|
+
"CRITICAL RULES:\n"
|
|
562
|
+
"- Read files before editing them\n"
|
|
563
|
+
"- Make minimal, targeted fixes\n"
|
|
564
|
+
"- Show test output clearly at each iteration"
|
|
565
|
+
)
|
|
566
|
+
console.print("\n[bold cyan]Entering DEBUG MODE[/bold cyan]")
|
|
567
|
+
console.print("[dim]I will run tests, find failures, fix them, and repeat until all pass.[/dim]\n")
|
|
568
|
+
# Auto-backup before debug modifies any files
|
|
569
|
+
console.print("[yellow]Creating safety backup before debug...[/yellow]")
|
|
570
|
+
bk = create_backup("pre-debug", cwd=repl.config.working_directory)
|
|
571
|
+
if bk["ok"]:
|
|
572
|
+
console.print(f"[green]Backup ready: {bk['message']}[/green]")
|
|
573
|
+
console.print("[dim]Run /backup restore to undo any changes if something breaks.[/dim]\n")
|
|
574
|
+
else:
|
|
575
|
+
console.print(f"[yellow]Backup skipped: {bk['error']}[/yellow]\n")
|
|
576
|
+
repl.context.messages.insert(1, {
|
|
577
|
+
"role": "system",
|
|
578
|
+
"content": DEBUG_PROMPT,
|
|
579
|
+
})
|
|
580
|
+
repl.context.add_user_message(
|
|
581
|
+
"Run the debug protocol. Find how to run tests, execute them, "
|
|
582
|
+
"fix any failures iteratively, and make ALL tests pass. "
|
|
583
|
+
"Show each step clearly."
|
|
584
|
+
)
|
|
585
|
+
repl._chat_loop()
|
|
586
|
+
|
|
587
|
+
elif command == "/self-improve":
|
|
588
|
+
from ..self_improve import SELF_IMPROVE_PROMPT, get_improvement_prompt, ImprovementTracker
|
|
589
|
+
console.print("\n[bold cyan]SELF-IMPROVEMENT MODE[/bold cyan]")
|
|
590
|
+
console.print("[dim]The AI will analyze, propose, implement, and verify improvements to itself.[/dim]\n")
|
|
591
|
+
# Auto-backup before any source files are modified
|
|
592
|
+
console.print("[yellow]Creating safety backup before self-improve...[/yellow]")
|
|
593
|
+
bk = create_backup("pre-self-improve", cwd=repl.config.working_directory)
|
|
594
|
+
if bk["ok"]:
|
|
595
|
+
console.print(f"[green]Backup ready: {bk['message']}[/green]")
|
|
596
|
+
console.print("[dim]Run /backup restore to undo all changes if something breaks.[/dim]\n")
|
|
597
|
+
else:
|
|
598
|
+
console.print(f"[yellow]Backup skipped: {bk['error']}[/yellow]\n")
|
|
599
|
+
|
|
600
|
+
tracker = ImprovementTracker()
|
|
601
|
+
snap_msg = tracker.snapshot()
|
|
602
|
+
console.print(f"[dim]{snap_msg}[/dim]")
|
|
603
|
+
|
|
604
|
+
repl.context.messages.insert(1, {
|
|
605
|
+
"role": "system",
|
|
606
|
+
"content": SELF_IMPROVE_PROMPT,
|
|
607
|
+
})
|
|
608
|
+
area = args[0] if args else ""
|
|
609
|
+
task = get_improvement_prompt(area)
|
|
610
|
+
repl.context.add_user_message(task)
|
|
611
|
+
repl._chat_loop()
|
|
612
|
+
|
|
613
|
+
report = tracker.report(commit=False)
|
|
614
|
+
if report.files_changed:
|
|
615
|
+
console.print("\n[bold cyan]Changes Made:[/bold cyan]")
|
|
616
|
+
console.print(report.diff_summary)
|
|
617
|
+
else:
|
|
618
|
+
console.print("[yellow]No file changes were detected by git.[/yellow]")
|
|
619
|
+
|
|
620
|
+
elif command == "/orchestrate":
|
|
621
|
+
from ..orchestrator import Coordinator
|
|
622
|
+
if not args:
|
|
623
|
+
console.print("[yellow]Usage: /orchestrate <task description>[/yellow]")
|
|
624
|
+
console.print("[dim]Example: /orchestrate add error handling to web_app.py[/dim]")
|
|
625
|
+
return
|
|
626
|
+
task = " ".join(args)
|
|
627
|
+
console.print(f"[bold cyan]Orchestrating:[/bold cyan] {task}")
|
|
628
|
+
console.print("[dim]Running researcher -> coder -> reviewer pipeline...[/dim]")
|
|
629
|
+
coord = Coordinator(repl.config)
|
|
630
|
+
result = coord.orchestrate(task)
|
|
631
|
+
console.print(result)
|
|
632
|
+
|
|
633
|
+
elif command == "/background":
|
|
634
|
+
handle_background_command(repl, args)
|
|
635
|
+
|
|
636
|
+
elif command == "/watch":
|
|
637
|
+
if not args:
|
|
638
|
+
console.print("[yellow]Usage: /watch start|stop|status[/yellow]")
|
|
639
|
+
return
|
|
640
|
+
sub = args[0].lower()
|
|
641
|
+
if sub == "start":
|
|
642
|
+
if repl.file_watcher and repl.file_watcher.is_running:
|
|
643
|
+
console.print("[yellow]File watcher already running[/yellow]")
|
|
644
|
+
else:
|
|
645
|
+
from ..file_watcher import FileWatcher
|
|
646
|
+
repl.file_watcher = FileWatcher()
|
|
647
|
+
repl.file_watcher.start()
|
|
648
|
+
console.print("[green]File watcher started — auto-reindexing on changes[/green]")
|
|
649
|
+
elif sub == "stop":
|
|
650
|
+
if repl.file_watcher and repl.file_watcher.is_running:
|
|
651
|
+
repl.file_watcher.stop()
|
|
652
|
+
console.print("[yellow]File watcher stopped[/yellow]")
|
|
653
|
+
else:
|
|
654
|
+
console.print("[yellow]File watcher is not running[/yellow]")
|
|
655
|
+
elif sub == "status":
|
|
656
|
+
if repl.file_watcher:
|
|
657
|
+
console.print(f"[cyan]File watcher:[/cyan] {repl.file_watcher.status}")
|
|
658
|
+
else:
|
|
659
|
+
console.print("[yellow]File watcher not initialized[/yellow]")
|
|
660
|
+
else:
|
|
661
|
+
console.print(f"[red]Unknown: /watch {sub}[/red]")
|
|
662
|
+
|
|
663
|
+
elif command == "/sandbox":
|
|
664
|
+
from ..sandbox import get_sandbox, check_docker
|
|
665
|
+
sub = args[0].lower() if args else "status"
|
|
666
|
+
if sub == "status":
|
|
667
|
+
available, version = check_docker()
|
|
668
|
+
if available:
|
|
669
|
+
sb = get_sandbox()
|
|
670
|
+
image_ready = sb.ensure_image()
|
|
671
|
+
console.print(f"[green]Docker:[/green] {version}")
|
|
672
|
+
console.print(f"[cyan]Image:[/cyan] {sb.image} ({'ready' if image_ready else 'not pulled'})")
|
|
673
|
+
console.print("[green]Sandbox mode: ACTIVE[/green]")
|
|
674
|
+
else:
|
|
675
|
+
console.print("[yellow]Docker not found — commands run without sandboxing[/yellow]")
|
|
676
|
+
console.print("[dim]Install Docker to enable isolated command execution[/dim]")
|
|
677
|
+
else:
|
|
678
|
+
console.print("[yellow]Usage: /sandbox status[/yellow]")
|
|
679
|
+
|
|
680
|
+
elif command == "/undo":
|
|
681
|
+
from ..undo import undo_last
|
|
682
|
+
result = undo_last()
|
|
683
|
+
console.print(f"[cyan]{result}[/cyan]")
|
|
684
|
+
|
|
685
|
+
elif command == "/sessions":
|
|
686
|
+
handle_sessions_command(repl, args)
|
|
687
|
+
|
|
688
|
+
elif command == "/brain":
|
|
689
|
+
handle_brain_command(repl, args)
|
|
690
|
+
|
|
691
|
+
elif command == "/audit":
|
|
692
|
+
handle_audit_command(repl, args)
|
|
693
|
+
|
|
694
|
+
else:
|
|
695
|
+
console.print(f"[red]Unknown command: {command}. Type /help[/red]")
|