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 @@
|
|
|
1
|
+
"""CLI slash-command handlers."""
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"""Handler for /audit subcommands.
|
|
2
|
+
|
|
3
|
+
Subcommands
|
|
4
|
+
-----------
|
|
5
|
+
/audit run — run one synchronous audit cycle immediately
|
|
6
|
+
/audit status — print daemon status summary
|
|
7
|
+
/audit metrics — dump full metrics history as JSONL
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import json
|
|
13
|
+
|
|
14
|
+
from ..cli_utils import console
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def handle_audit_command(repl, args: list[str]) -> None:
|
|
18
|
+
"""Dispatch /audit <subcommand> [options]."""
|
|
19
|
+
sub = args[0].lower() if args else "help"
|
|
20
|
+
|
|
21
|
+
if sub == "run":
|
|
22
|
+
_audit_run(repl)
|
|
23
|
+
elif sub == "status":
|
|
24
|
+
_audit_status(repl)
|
|
25
|
+
elif sub == "metrics":
|
|
26
|
+
_audit_metrics(repl)
|
|
27
|
+
else:
|
|
28
|
+
console.print(
|
|
29
|
+
"[yellow]Usage:[/yellow] /audit run | status | metrics\n"
|
|
30
|
+
" [dim]run[/dim] — trigger one audit cycle right now\n"
|
|
31
|
+
" [dim]status[/dim] — show daemon state and latest metrics\n"
|
|
32
|
+
" [dim]metrics[/dim] — dump full JSONL metrics history"
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
# ------------------------------------------------------------------ #
|
|
37
|
+
# /audit run
|
|
38
|
+
# ------------------------------------------------------------------ #
|
|
39
|
+
|
|
40
|
+
def _audit_run(repl) -> None:
|
|
41
|
+
"""Run one synchronous audit cycle in the foreground."""
|
|
42
|
+
from pathlib import Path
|
|
43
|
+
from ..audit_daemon import AuditDaemon
|
|
44
|
+
|
|
45
|
+
project_root = str(repl.config.working_directory or Path.cwd())
|
|
46
|
+
|
|
47
|
+
# Re-use a running daemon instance if available, otherwise create a
|
|
48
|
+
# temporary one. The temporary instance will respect the lock file and
|
|
49
|
+
# bail out if the background daemon is already running.
|
|
50
|
+
daemon: AuditDaemon = getattr(repl, "_audit_daemon", None) or AuditDaemon(
|
|
51
|
+
repl.config, project_root=project_root
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
console.print("[dim]Running audit cycle...[/dim]")
|
|
55
|
+
try:
|
|
56
|
+
summary = daemon.audit()
|
|
57
|
+
except Exception as exc:
|
|
58
|
+
console.print(f"[red]Audit failed: {exc}[/red]")
|
|
59
|
+
return
|
|
60
|
+
|
|
61
|
+
if summary.get("skipped"):
|
|
62
|
+
console.print(f"[yellow]Audit skipped:[/yellow] {summary['skip_reason']}")
|
|
63
|
+
return
|
|
64
|
+
|
|
65
|
+
metrics = summary.get("metrics", {})
|
|
66
|
+
attempted = summary.get("improvements_attempted", 0)
|
|
67
|
+
committed = summary.get("improvements_committed", 0)
|
|
68
|
+
regressions = summary.get("regressions", [])
|
|
69
|
+
|
|
70
|
+
console.print("[bold green]Audit complete[/bold green]")
|
|
71
|
+
if metrics:
|
|
72
|
+
for name, value in sorted(metrics.items()):
|
|
73
|
+
console.print(f" [cyan]{name:<28}[/cyan] {value:.4f}")
|
|
74
|
+
if regressions:
|
|
75
|
+
for r in regressions:
|
|
76
|
+
console.print(f" [red]⚠ regression:[/red] {r}")
|
|
77
|
+
if attempted:
|
|
78
|
+
status = "committed" if committed else "rolled back"
|
|
79
|
+
console.print(f" improvement: {status} ({attempted} attempted, {committed} committed)")
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
# ------------------------------------------------------------------ #
|
|
83
|
+
# /audit status
|
|
84
|
+
# ------------------------------------------------------------------ #
|
|
85
|
+
|
|
86
|
+
def _audit_status(repl) -> None:
|
|
87
|
+
"""Print human-readable daemon status."""
|
|
88
|
+
from pathlib import Path
|
|
89
|
+
from ..audit_daemon import AuditDaemon
|
|
90
|
+
|
|
91
|
+
project_root = str(repl.config.working_directory or Path.cwd())
|
|
92
|
+
daemon: AuditDaemon = getattr(repl, "_audit_daemon", None) or AuditDaemon(
|
|
93
|
+
repl.config, project_root=project_root
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
console.print(daemon.status())
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
# ------------------------------------------------------------------ #
|
|
100
|
+
# /audit metrics
|
|
101
|
+
# ------------------------------------------------------------------ #
|
|
102
|
+
|
|
103
|
+
def _audit_metrics(repl) -> None:
|
|
104
|
+
"""Dump full metrics history as pretty-printed JSON."""
|
|
105
|
+
from pathlib import Path
|
|
106
|
+
from ..audit_daemon import AuditDaemon
|
|
107
|
+
|
|
108
|
+
project_root = str(repl.config.working_directory or Path.cwd())
|
|
109
|
+
daemon: AuditDaemon = getattr(repl, "_audit_daemon", None) or AuditDaemon(
|
|
110
|
+
repl.config, project_root=project_root
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
raw = daemon.metrics_json()
|
|
114
|
+
rows = json.loads(raw)
|
|
115
|
+
if not rows:
|
|
116
|
+
console.print("[yellow]No metrics recorded yet. Run /audit run to collect a baseline.[/yellow]")
|
|
117
|
+
return
|
|
118
|
+
|
|
119
|
+
console.print(f"[dim]{len(rows)} metric rows — most recent last[/dim]")
|
|
120
|
+
console.print(raw)
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"""Handle /background commands."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def handle_background_command(repl, args):
|
|
5
|
+
"""Handle /background start|status|result|list commands."""
|
|
6
|
+
from ..cli_utils import console
|
|
7
|
+
|
|
8
|
+
if not args:
|
|
9
|
+
console.print("[yellow]Usage: /background start|status|result|list[/yellow]")
|
|
10
|
+
return
|
|
11
|
+
|
|
12
|
+
sub = args[0].lower()
|
|
13
|
+
|
|
14
|
+
if sub == "start":
|
|
15
|
+
task = " ".join(args[1:])
|
|
16
|
+
if not task:
|
|
17
|
+
console.print("[yellow]Usage: /background start <task description>[/yellow]")
|
|
18
|
+
return
|
|
19
|
+
task_id = repl.background.start_task(task)
|
|
20
|
+
console.print(f"[green]Background task started:[/green] {task_id}")
|
|
21
|
+
console.print(f"[dim]Check status: /background status {task_id}[/dim]")
|
|
22
|
+
console.print(f"[dim]View result: /background result {task_id}[/dim]")
|
|
23
|
+
|
|
24
|
+
elif sub == "status":
|
|
25
|
+
if len(args) > 1:
|
|
26
|
+
statuses = repl.background.get_status(args[1])
|
|
27
|
+
else:
|
|
28
|
+
statuses = repl.background.get_status()
|
|
29
|
+
|
|
30
|
+
if not statuses:
|
|
31
|
+
console.print("[yellow]No background tasks[/yellow]")
|
|
32
|
+
return
|
|
33
|
+
|
|
34
|
+
for s in statuses:
|
|
35
|
+
status_color = {
|
|
36
|
+
"pending": "yellow",
|
|
37
|
+
"running": "cyan",
|
|
38
|
+
"done": "green",
|
|
39
|
+
"error": "red",
|
|
40
|
+
}.get(s["status"], "dim")
|
|
41
|
+
|
|
42
|
+
console.print(f" [{status_color}]{s['id']}[/{status_color}] {s['status']}")
|
|
43
|
+
console.print(f" Task: {s['description']}")
|
|
44
|
+
if s["result_preview"]:
|
|
45
|
+
console.print(f" Preview: {s['result_preview']}")
|
|
46
|
+
|
|
47
|
+
elif sub == "result":
|
|
48
|
+
if len(args) < 2:
|
|
49
|
+
console.print("[yellow]Usage: /background result <task_id>[/yellow]")
|
|
50
|
+
return
|
|
51
|
+
result = repl.background.get_result(args[1])
|
|
52
|
+
if result:
|
|
53
|
+
console.print(result)
|
|
54
|
+
else:
|
|
55
|
+
statuses = repl.background.get_status(args[1])
|
|
56
|
+
if statuses and statuses[0]:
|
|
57
|
+
s = statuses[0]
|
|
58
|
+
if s["status"] == "running":
|
|
59
|
+
console.print("[yellow]Task still running...[/yellow]")
|
|
60
|
+
elif s["status"] == "error":
|
|
61
|
+
console.print(f"[red]Task failed: {s['error']}[/red]")
|
|
62
|
+
else:
|
|
63
|
+
console.print("[yellow]No result yet[/yellow]")
|
|
64
|
+
else:
|
|
65
|
+
console.print("[red]Task not found[/red]")
|
|
66
|
+
|
|
67
|
+
elif sub == "list":
|
|
68
|
+
statuses = repl.background.get_status()
|
|
69
|
+
if not statuses:
|
|
70
|
+
console.print("[yellow]No background tasks[/yellow]")
|
|
71
|
+
return
|
|
72
|
+
console.print("[bold]Background Tasks:[/bold]")
|
|
73
|
+
for s in statuses:
|
|
74
|
+
status_color = {
|
|
75
|
+
"pending": "yellow",
|
|
76
|
+
"running": "cyan",
|
|
77
|
+
"done": "green",
|
|
78
|
+
"error": "red",
|
|
79
|
+
}.get(s["status"], "dim")
|
|
80
|
+
console.print(f" [{status_color}]{s['id']}[/{status_color}] ({s['status']}) {s['description']}")
|
|
81
|
+
|
|
82
|
+
else:
|
|
83
|
+
console.print(f"[red]Unknown: /background {sub}[/red]")
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"""Handle /brain commands."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import time as _time
|
|
5
|
+
|
|
6
|
+
from ..brain import Retriever, VectorIndexer, rebuild_project
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def handle_brain_command(repl, args):
|
|
10
|
+
"""Handle /brain rebuild|stats|status commands."""
|
|
11
|
+
from ..cli_utils import console
|
|
12
|
+
|
|
13
|
+
sub = args[0].lower() if args else "status"
|
|
14
|
+
|
|
15
|
+
if sub == "rebuild":
|
|
16
|
+
with console.status("Rebuilding codebase index...", spinner="dots"):
|
|
17
|
+
result = rebuild_project(os.getcwd())
|
|
18
|
+
repl.brain.load()
|
|
19
|
+
repl._rag_retriever = None
|
|
20
|
+
|
|
21
|
+
rag_info = f"{result.get('chunks', 0)} chunks, {result.get('files', 0)} files" if result.get('chunks') else "no supported files found"
|
|
22
|
+
console.print(
|
|
23
|
+
f"[green]Brain rebuilt:[/green] {rag_info} | "
|
|
24
|
+
f"{result.get('node_count', 0)} symbols, "
|
|
25
|
+
f"{result.get('files_parsed', 0)} files"
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
brain_summary = repl.brain.summarize()
|
|
29
|
+
for i, m in enumerate(repl.context.messages):
|
|
30
|
+
if isinstance(m.get("content"), str) and m["content"].startswith("<knowledge-graph>"):
|
|
31
|
+
repl.context.messages[i]["content"] = brain_summary
|
|
32
|
+
break
|
|
33
|
+
elif isinstance(m.get("content"), str) and m["content"].startswith("<rag-context>"):
|
|
34
|
+
repl.context.messages.pop(i)
|
|
35
|
+
break
|
|
36
|
+
else:
|
|
37
|
+
repl.context.messages.insert(1, {
|
|
38
|
+
"role": "user",
|
|
39
|
+
"content": brain_summary,
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
elif sub == "stats":
|
|
43
|
+
has_rag = True
|
|
44
|
+
try:
|
|
45
|
+
r = Retriever()
|
|
46
|
+
info = r.stats()
|
|
47
|
+
except Exception:
|
|
48
|
+
has_rag = False
|
|
49
|
+
|
|
50
|
+
if not repl.brain.nodes and not has_rag:
|
|
51
|
+
console.print("[yellow]Brain is empty. Run `/brain rebuild` to index your codebase.[/yellow]")
|
|
52
|
+
return
|
|
53
|
+
|
|
54
|
+
from ..tools.brain_tools import BrainStatusTool
|
|
55
|
+
status = BrainStatusTool()
|
|
56
|
+
console.print(status.run())
|
|
57
|
+
|
|
58
|
+
else: # "status" or no args
|
|
59
|
+
rag_available = False
|
|
60
|
+
try:
|
|
61
|
+
idx = VectorIndexer()
|
|
62
|
+
rag_available = idx.load()
|
|
63
|
+
except Exception:
|
|
64
|
+
pass
|
|
65
|
+
|
|
66
|
+
if not repl.brain.nodes and not rag_available:
|
|
67
|
+
console.print("[yellow]Brain is empty. Run `/brain rebuild` to index your codebase.[/yellow]")
|
|
68
|
+
return
|
|
69
|
+
|
|
70
|
+
if rag_available:
|
|
71
|
+
r = Retriever()
|
|
72
|
+
info = r.stats()
|
|
73
|
+
vec = info.get("vector", {})
|
|
74
|
+
console.print(
|
|
75
|
+
f"[cyan]Brain:[/cyan] {vec.get('chunks', 0)} chunks, "
|
|
76
|
+
f"{vec.get('files', 0)} files | "
|
|
77
|
+
f"{repl.brain.stats.get('node_count', 0)} symbols"
|
|
78
|
+
)
|
|
79
|
+
else:
|
|
80
|
+
console.print(f"[cyan]Brain:[/cyan] {repl.brain.stats.get('node_count', 0)} symbols, "
|
|
81
|
+
f"{repl.brain.stats.get('edge_count', 0)} relations, "
|
|
82
|
+
f"{repl.brain.stats.get('files_parsed', 0)} files")
|
|
83
|
+
if repl.brain.stats.get("last_built"):
|
|
84
|
+
last = _time.strftime("%Y-%m-%d %H:%M:%S",
|
|
85
|
+
_time.localtime(repl.brain.stats["last_built"]))
|
|
86
|
+
console.print(f"[cyan]Last built:[/cyan] {last}")
|
|
87
|
+
console.print("[dim]Full stats: /brain stats | Rebuild: /brain rebuild[/dim]")
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"""Handle /config commands."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
from .. import settings as cfg
|
|
6
|
+
from ..api import test_connection
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def handle_config_command(repl, args):
|
|
10
|
+
"""Handle /config list|get|set commands."""
|
|
11
|
+
from ..cli_utils import console
|
|
12
|
+
|
|
13
|
+
if not args:
|
|
14
|
+
console.print("[yellow]Usage: /config list|get|set[/yellow]")
|
|
15
|
+
return
|
|
16
|
+
|
|
17
|
+
sub = args[0].lower()
|
|
18
|
+
|
|
19
|
+
if sub == "list":
|
|
20
|
+
console.print("[bold]Provider:[/bold]")
|
|
21
|
+
console.print(f" [cyan]provider[/cyan] = {repl.config.provider}")
|
|
22
|
+
console.print(f" [cyan]model[/cyan] = {repl.config.model}")
|
|
23
|
+
console.print(f" [cyan]base_url[/cyan] = {repl.config.base_url}")
|
|
24
|
+
console.print("[bold]Settings:[/bold]")
|
|
25
|
+
settings = cfg.load_settings()
|
|
26
|
+
for k, v in settings.items():
|
|
27
|
+
console.print(f" [cyan]{k}[/cyan] = {v}")
|
|
28
|
+
|
|
29
|
+
elif sub == "get":
|
|
30
|
+
if len(args) < 2:
|
|
31
|
+
console.print("[yellow]Usage: /config get <key>[/yellow]")
|
|
32
|
+
return
|
|
33
|
+
settings = cfg.load_settings()
|
|
34
|
+
val = settings.get(args[1], "[dim]not set[/dim]")
|
|
35
|
+
console.print(f"[cyan]{args[1]}[/cyan] = {val}")
|
|
36
|
+
|
|
37
|
+
elif sub == "set":
|
|
38
|
+
if len(args) < 3:
|
|
39
|
+
console.print("[yellow]Usage: /config set <key> <value>[/yellow]")
|
|
40
|
+
return
|
|
41
|
+
|
|
42
|
+
if args[1] == "provider":
|
|
43
|
+
if args[2] != "deepseek":
|
|
44
|
+
console.print("[red]Provider must be 'deepseek'[/red]")
|
|
45
|
+
return
|
|
46
|
+
repl.config.provider = args[2]
|
|
47
|
+
repl.config.base_url = "https://api.deepseek.com/v1"
|
|
48
|
+
env_key = os.environ.get("DEEPSEEK_API_KEY")
|
|
49
|
+
if env_key:
|
|
50
|
+
repl.config.api_key = env_key
|
|
51
|
+
repl.config.save()
|
|
52
|
+
console.print(f"[green]Switched provider to {args[2]}[/green]")
|
|
53
|
+
console.print(f"[green] base_url: {repl.config.base_url}[/green]")
|
|
54
|
+
with console.status("Testing new API connection...", spinner="dots"):
|
|
55
|
+
ok, msg = test_connection(repl.config.api_key, repl.config.base_url)
|
|
56
|
+
if ok:
|
|
57
|
+
console.print("[green]New connection OK[/green]")
|
|
58
|
+
else:
|
|
59
|
+
console.print(f"[red]New connection failed: {msg}[/red]")
|
|
60
|
+
return
|
|
61
|
+
|
|
62
|
+
if args[1] == "shell":
|
|
63
|
+
valid = ("auto", "git_bash", "wsl", "cmd")
|
|
64
|
+
if args[2] not in valid:
|
|
65
|
+
console.print(f"[red]Shell must be one of: {', '.join(valid)}[/red]")
|
|
66
|
+
return
|
|
67
|
+
from ..tools.bash import reset_shell_cache
|
|
68
|
+
reset_shell_cache()
|
|
69
|
+
console.print(f"[green]Shell set to {args[2]}. Will take effect on next Bash command.[/green]")
|
|
70
|
+
|
|
71
|
+
cfg.save_setting(args[1], args[2])
|
|
72
|
+
console.print(f"[green]Set {args[1]} = {args[2]}[/green]")
|
|
73
|
+
|
|
74
|
+
else:
|
|
75
|
+
console.print(f"[red]Unknown: /config {sub}[/red]")
|