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.
Files changed (127) hide show
  1. luckyd_code/__init__.py +54 -0
  2. luckyd_code/__main__.py +5 -0
  3. luckyd_code/_agent_loop.py +551 -0
  4. luckyd_code/_data_dir.py +73 -0
  5. luckyd_code/agent.py +38 -0
  6. luckyd_code/analytics/__init__.py +18 -0
  7. luckyd_code/analytics/reporter.py +195 -0
  8. luckyd_code/analytics/scanner.py +443 -0
  9. luckyd_code/analytics/smells.py +316 -0
  10. luckyd_code/analytics/trends.py +303 -0
  11. luckyd_code/api.py +473 -0
  12. luckyd_code/audit_daemon.py +845 -0
  13. luckyd_code/autonomous_fixer.py +473 -0
  14. luckyd_code/background.py +159 -0
  15. luckyd_code/backup.py +237 -0
  16. luckyd_code/brain/__init__.py +84 -0
  17. luckyd_code/brain/assembler.py +100 -0
  18. luckyd_code/brain/chunker.py +345 -0
  19. luckyd_code/brain/constants.py +73 -0
  20. luckyd_code/brain/embedder.py +163 -0
  21. luckyd_code/brain/graph.py +311 -0
  22. luckyd_code/brain/indexer.py +316 -0
  23. luckyd_code/brain/parser.py +140 -0
  24. luckyd_code/brain/retriever.py +234 -0
  25. luckyd_code/cli.py +894 -0
  26. luckyd_code/cli_commands/__init__.py +1 -0
  27. luckyd_code/cli_commands/audit.py +120 -0
  28. luckyd_code/cli_commands/background.py +83 -0
  29. luckyd_code/cli_commands/brain.py +87 -0
  30. luckyd_code/cli_commands/config.py +75 -0
  31. luckyd_code/cli_commands/dispatcher.py +695 -0
  32. luckyd_code/cli_commands/sessions.py +41 -0
  33. luckyd_code/cli_entry.py +147 -0
  34. luckyd_code/cli_utils.py +112 -0
  35. luckyd_code/config.py +205 -0
  36. luckyd_code/context.py +214 -0
  37. luckyd_code/cost_tracker.py +209 -0
  38. luckyd_code/error_reporter.py +508 -0
  39. luckyd_code/exceptions.py +39 -0
  40. luckyd_code/export.py +126 -0
  41. luckyd_code/feedback_analyzer.py +290 -0
  42. luckyd_code/file_watcher.py +258 -0
  43. luckyd_code/git/__init__.py +11 -0
  44. luckyd_code/git/auto_commit.py +157 -0
  45. luckyd_code/git/tools.py +85 -0
  46. luckyd_code/hooks.py +236 -0
  47. luckyd_code/indexer.py +280 -0
  48. luckyd_code/init.py +39 -0
  49. luckyd_code/keybindings.py +77 -0
  50. luckyd_code/log.py +55 -0
  51. luckyd_code/mcp/__init__.py +6 -0
  52. luckyd_code/mcp/client.py +184 -0
  53. luckyd_code/memory/__init__.py +19 -0
  54. luckyd_code/memory/manager.py +339 -0
  55. luckyd_code/metrics/__init__.py +5 -0
  56. luckyd_code/model_registry.py +131 -0
  57. luckyd_code/orchestrator.py +204 -0
  58. luckyd_code/permissions/__init__.py +1 -0
  59. luckyd_code/permissions/manager.py +103 -0
  60. luckyd_code/planner.py +361 -0
  61. luckyd_code/plugins.py +91 -0
  62. luckyd_code/py.typed +0 -0
  63. luckyd_code/retry.py +57 -0
  64. luckyd_code/router.py +417 -0
  65. luckyd_code/sandbox.py +156 -0
  66. luckyd_code/self_critique.py +2 -0
  67. luckyd_code/self_improve.py +274 -0
  68. luckyd_code/sessions.py +114 -0
  69. luckyd_code/settings.py +72 -0
  70. luckyd_code/skills/__init__.py +8 -0
  71. luckyd_code/skills/review.py +22 -0
  72. luckyd_code/skills/security.py +17 -0
  73. luckyd_code/tasks/__init__.py +1 -0
  74. luckyd_code/tasks/manager.py +102 -0
  75. luckyd_code/templates/icon-192.png +0 -0
  76. luckyd_code/templates/icon-512.png +0 -0
  77. luckyd_code/templates/index.html +1965 -0
  78. luckyd_code/templates/manifest.json +14 -0
  79. luckyd_code/templates/src/app.js +694 -0
  80. luckyd_code/templates/src/body.html +767 -0
  81. luckyd_code/templates/src/cdn.txt +2 -0
  82. luckyd_code/templates/src/style.css +474 -0
  83. luckyd_code/templates/sw.js +31 -0
  84. luckyd_code/templates/test.html +6 -0
  85. luckyd_code/themes.py +48 -0
  86. luckyd_code/tools/__init__.py +97 -0
  87. luckyd_code/tools/agent_tools.py +65 -0
  88. luckyd_code/tools/bash.py +360 -0
  89. luckyd_code/tools/brain_tools.py +137 -0
  90. luckyd_code/tools/browser.py +369 -0
  91. luckyd_code/tools/datetime_tool.py +34 -0
  92. luckyd_code/tools/dockerfile_gen.py +212 -0
  93. luckyd_code/tools/file_ops.py +381 -0
  94. luckyd_code/tools/game_gen.py +360 -0
  95. luckyd_code/tools/git_tools.py +130 -0
  96. luckyd_code/tools/git_worktree.py +63 -0
  97. luckyd_code/tools/path_validate.py +64 -0
  98. luckyd_code/tools/project_gen.py +187 -0
  99. luckyd_code/tools/readme_gen.py +227 -0
  100. luckyd_code/tools/registry.py +157 -0
  101. luckyd_code/tools/shell_detect.py +109 -0
  102. luckyd_code/tools/web.py +89 -0
  103. luckyd_code/tools/youtube.py +187 -0
  104. luckyd_code/tools_bridge.py +144 -0
  105. luckyd_code/undo.py +126 -0
  106. luckyd_code/update.py +60 -0
  107. luckyd_code/verify.py +360 -0
  108. luckyd_code/web_app.py +176 -0
  109. luckyd_code/web_routes/__init__.py +23 -0
  110. luckyd_code/web_routes/background.py +73 -0
  111. luckyd_code/web_routes/brain.py +109 -0
  112. luckyd_code/web_routes/cost.py +12 -0
  113. luckyd_code/web_routes/files.py +133 -0
  114. luckyd_code/web_routes/memories.py +94 -0
  115. luckyd_code/web_routes/misc.py +67 -0
  116. luckyd_code/web_routes/project.py +48 -0
  117. luckyd_code/web_routes/review.py +20 -0
  118. luckyd_code/web_routes/sessions.py +44 -0
  119. luckyd_code/web_routes/settings.py +43 -0
  120. luckyd_code/web_routes/static.py +70 -0
  121. luckyd_code/web_routes/update.py +19 -0
  122. luckyd_code/web_routes/ws.py +237 -0
  123. luckyd_code-1.2.2.dist-info/METADATA +297 -0
  124. luckyd_code-1.2.2.dist-info/RECORD +127 -0
  125. luckyd_code-1.2.2.dist-info/WHEEL +4 -0
  126. luckyd_code-1.2.2.dist-info/entry_points.txt +3 -0
  127. 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]")