edgemind 1.0.1__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.
- app/__init__.py +3 -0
- app/cli/__init__.py +0 -0
- app/cli/banner.py +35 -0
- app/cli/commands.py +78 -0
- app/cli/interactive.py +293 -0
- app/cli/main.py +128 -0
- app/cli/session.py +69 -0
- app/editing/__init__.py +0 -0
- app/editing/code_modifier.py +99 -0
- app/editing/diff_generator.py +46 -0
- app/editing/editing_service.py +246 -0
- app/editing/file_manager.py +137 -0
- app/editing/models.py +44 -0
- app/editing/validator.py +146 -0
- app/graph/__init__.py +0 -0
- app/graph/evaluator.py +20 -0
- app/graph/nodes.py +510 -0
- app/graph/planner.py +371 -0
- app/graph/planner_schema.py +51 -0
- app/graph/state.py +33 -0
- app/graph/workflow.py +80 -0
- app/memory/__init__.py +0 -0
- app/memory/database.py +14 -0
- app/memory/memory_manager.py +75 -0
- app/memory/schema.py +24 -0
- app/models/__init__.py +0 -0
- app/models/model_router.py +36 -0
- app/models/ollama_client.py +35 -0
- app/resources/__init__.py +0 -0
- app/resources/system_monitor.py +14 -0
- app/scripts/__init__.py +1 -0
- app/scripts/export_graph.py +5 -0
- app/setup/__init__.py +1 -0
- app/setup/checks.py +91 -0
- app/setup/installer.py +127 -0
- app/setup/models.py +8 -0
- app/tools/__init__.py +0 -0
- app/tools/code_explainer.py +46 -0
- app/tools/code_scanner.py +50 -0
- app/tools/debug_assistant.py +31 -0
- app/tools/deployment_generator.py +63 -0
- app/tools/docker_compose_generator.py +70 -0
- app/tools/file_discovery.py +175 -0
- app/tools/file_reader.py +16 -0
- app/tools/project_analyzer.py +69 -0
- app/tools/requirements_generator.py +98 -0
- edgemind-1.0.1.dist-info/METADATA +843 -0
- edgemind-1.0.1.dist-info/RECORD +51 -0
- edgemind-1.0.1.dist-info/WHEEL +5 -0
- edgemind-1.0.1.dist-info/entry_points.txt +2 -0
- edgemind-1.0.1.dist-info/top_level.txt +1 -0
app/__init__.py
ADDED
app/cli/__init__.py
ADDED
|
File without changes
|
app/cli/banner.py
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""
|
|
2
|
+
EdgeMind CLI Banner
|
|
3
|
+
Displays the startup screen for EdgeMind.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from app.resources.system_monitor import get_system_resources
|
|
8
|
+
|
|
9
|
+
def print_banner() -> None:
|
|
10
|
+
"""
|
|
11
|
+
Display the EdgeMind startup banner.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
resources = get_system_resources()
|
|
15
|
+
|
|
16
|
+
print("\n")
|
|
17
|
+
print("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
|
|
18
|
+
print(" EdgeMind v1.0")
|
|
19
|
+
print(" Resource-Aware Agentic Coding Assistant")
|
|
20
|
+
print("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
|
|
21
|
+
|
|
22
|
+
print(f"\nProject : {Path.cwd().name}")
|
|
23
|
+
print("Memory : Enabled")
|
|
24
|
+
print("Current File : None")
|
|
25
|
+
print("Session : Active")
|
|
26
|
+
print("Planner : Ready")
|
|
27
|
+
print("LangGraph : Ready")
|
|
28
|
+
|
|
29
|
+
print(f"CPU Usage : {resources['cpu_percent']} %")
|
|
30
|
+
print(f"Available RAM : {resources['ram_available_gb']:.2f} GB")
|
|
31
|
+
|
|
32
|
+
print("\nType 'help' to view commands.")
|
|
33
|
+
print("Type 'exit' to quit.")
|
|
34
|
+
|
|
35
|
+
print("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n")
|
app/cli/commands.py
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"""
|
|
2
|
+
EdgeMind CLI Commands
|
|
3
|
+
Handles built-in commands.
|
|
4
|
+
No LangGraph logic should exist here.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from app.memory.memory_manager import search_memory
|
|
10
|
+
from app.resources.system_monitor import get_system_resources
|
|
11
|
+
from app.setup.installer import run_setup
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def show_help():
|
|
15
|
+
|
|
16
|
+
print("\nAvailable Commands\n")
|
|
17
|
+
|
|
18
|
+
print("help")
|
|
19
|
+
print("status")
|
|
20
|
+
print("memory")
|
|
21
|
+
print("clear")
|
|
22
|
+
print("setup")
|
|
23
|
+
print("exit")
|
|
24
|
+
|
|
25
|
+
print("\nExamples\n")
|
|
26
|
+
|
|
27
|
+
print("Analyze this project")
|
|
28
|
+
print("Explain app/models/model_router.py")
|
|
29
|
+
print("Debug tests/sample_error.txt")
|
|
30
|
+
print("Generate Dockerfile")
|
|
31
|
+
print("Modify tests/sample_code.py")
|
|
32
|
+
print()
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def show_status(session):
|
|
36
|
+
|
|
37
|
+
resources = get_system_resources()
|
|
38
|
+
rows = search_memory(session.project_path)
|
|
39
|
+
|
|
40
|
+
print("\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
|
|
41
|
+
print("EdgeMind Status")
|
|
42
|
+
print("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
|
|
43
|
+
|
|
44
|
+
print(f"\nProject : {session.project_name}")
|
|
45
|
+
print(f"Current File : {session.active_file}")
|
|
46
|
+
print(f"Last Query : {session.last_query}")
|
|
47
|
+
print(f"Conversation : {len(session.conversation_history)} turns")
|
|
48
|
+
print(f"Selected Model : {session.selected_model}")
|
|
49
|
+
print(f"Memory Entries : {len(rows)}")
|
|
50
|
+
print(f"CPU Usage : {resources['cpu_percent']} %")
|
|
51
|
+
print(f"Available RAM : {resources['ram_available_gb']:.2f} GB")
|
|
52
|
+
print("\nSQLite : Connected")
|
|
53
|
+
|
|
54
|
+
print("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n")
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def show_memory(session):
|
|
58
|
+
|
|
59
|
+
rows = search_memory(session.project_path)
|
|
60
|
+
print()
|
|
61
|
+
|
|
62
|
+
if not rows:
|
|
63
|
+
print("No memory available.\n")
|
|
64
|
+
return
|
|
65
|
+
|
|
66
|
+
print("Recent Memory\n")
|
|
67
|
+
for query, task, result, success in rows[-10:]:
|
|
68
|
+
print("--------------------------------")
|
|
69
|
+
print(f"Query : {query}")
|
|
70
|
+
print(f"Task : {task}")
|
|
71
|
+
print(f"Success : {bool(success)}")
|
|
72
|
+
print("--------------------------------")
|
|
73
|
+
|
|
74
|
+
print()
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def clear_terminal():
|
|
78
|
+
print("\033c", end="")
|
app/cli/interactive.py
ADDED
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
"""
|
|
2
|
+
EdgeMind V2 Interactive CLI Shell
|
|
3
|
+
|
|
4
|
+
Provides a polished, Claude-Code style interactive coding assistant interface.
|
|
5
|
+
Autonomous file discovery, structured step indicators, Change Review summaries,
|
|
6
|
+
and robust session context management.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import re
|
|
10
|
+
import shutil
|
|
11
|
+
import string
|
|
12
|
+
import time
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
|
|
15
|
+
from app.cli.banner import print_banner
|
|
16
|
+
from app.cli.commands import (
|
|
17
|
+
clear_terminal,
|
|
18
|
+
show_help,
|
|
19
|
+
show_memory,
|
|
20
|
+
show_status,
|
|
21
|
+
)
|
|
22
|
+
from app.cli.session import SessionState
|
|
23
|
+
from app.graph.workflow import workflow
|
|
24
|
+
from app.tools.file_discovery import resolve_best_file
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def format_change_review(state: dict, result_data: dict) -> str:
|
|
28
|
+
"""
|
|
29
|
+
Generate Claude-Code style output report:
|
|
30
|
+
- Step execution checkmarks (✓ Source found, ✓ Plan created, etc.)
|
|
31
|
+
- Summary of Created / Modified / Preserved files
|
|
32
|
+
- Validation & Verification status
|
|
33
|
+
- Formatted unified diff
|
|
34
|
+
"""
|
|
35
|
+
output = []
|
|
36
|
+
output.append("\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
|
|
37
|
+
output.append("EdgeMind V2 Execution Summary")
|
|
38
|
+
output.append("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n")
|
|
39
|
+
|
|
40
|
+
# Step Checkmarks
|
|
41
|
+
plan = result_data.get("plan", [])
|
|
42
|
+
output.append("Workflow Steps:")
|
|
43
|
+
if plan:
|
|
44
|
+
output.append(f" ✓ Plan created ({len(plan)} tasks)")
|
|
45
|
+
for idx, task in enumerate(plan, 1):
|
|
46
|
+
tool = task.get("tool", "")
|
|
47
|
+
op = task.get("operation", "")
|
|
48
|
+
target = task.get("target_file") or task.get("source_file") or ""
|
|
49
|
+
output.append(f" ✓ Step {idx}: [{op.upper()}] {tool} {target}".strip())
|
|
50
|
+
else:
|
|
51
|
+
output.append(" ✓ Direct execution completed")
|
|
52
|
+
|
|
53
|
+
# Files Overview
|
|
54
|
+
source_file = result_data.get("source_file") or result_data.get("file_path") or ""
|
|
55
|
+
modified_file = result_data.get("modified_file") or ""
|
|
56
|
+
operation = result_data.get("operation") or "modify"
|
|
57
|
+
|
|
58
|
+
created = "None"
|
|
59
|
+
modified = "None"
|
|
60
|
+
preserved = "None"
|
|
61
|
+
|
|
62
|
+
if operation == "create" and modified_file:
|
|
63
|
+
created = f"{modified_file} (NEW FILE)"
|
|
64
|
+
if source_file and source_file != modified_file:
|
|
65
|
+
preserved = f"{source_file} (UNTOUCHED)"
|
|
66
|
+
elif operation == "modify" and modified_file:
|
|
67
|
+
modified = f"{modified_file} (MODIFIED FILE)"
|
|
68
|
+
elif source_file:
|
|
69
|
+
preserved = f"{source_file}"
|
|
70
|
+
|
|
71
|
+
output.append("\nFiles Status:")
|
|
72
|
+
output.append(f" Created : {created}")
|
|
73
|
+
output.append(f" Modified : {modified}")
|
|
74
|
+
output.append(f" Preserved: {preserved}")
|
|
75
|
+
|
|
76
|
+
# Validation & Verification
|
|
77
|
+
review = result_data.get("review_status") or {}
|
|
78
|
+
review_details = review.get("details", [])
|
|
79
|
+
output.append("\nVerification & Review:")
|
|
80
|
+
if review_details:
|
|
81
|
+
for detail in review_details:
|
|
82
|
+
output.append(f" {detail}")
|
|
83
|
+
else:
|
|
84
|
+
success = result_data.get("execution_success", False)
|
|
85
|
+
output.append(f" {'✓ Task executed successfully' if success else '✗ Execution failed'}")
|
|
86
|
+
|
|
87
|
+
# Result / Diff Content
|
|
88
|
+
res_str = result_data.get("result", "")
|
|
89
|
+
output.append("\nResult Output:")
|
|
90
|
+
output.append("-------------------------------------------------------------")
|
|
91
|
+
output.append(res_str if res_str else "(No text output)")
|
|
92
|
+
output.append("-------------------------------------------------------------\n")
|
|
93
|
+
|
|
94
|
+
return "\n".join(output)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def update_session_context(session: SessionState, query: str):
|
|
98
|
+
"""
|
|
99
|
+
Update active session working context from user prompt.
|
|
100
|
+
Support natural language pronouns ("it", "that", "this file", "what changed?").
|
|
101
|
+
"""
|
|
102
|
+
session.project_path = str(Path.cwd())
|
|
103
|
+
|
|
104
|
+
# Direct filename search in query
|
|
105
|
+
best_file = resolve_best_file(query, session.project_path, active_file=session.active_file)
|
|
106
|
+
|
|
107
|
+
if best_file:
|
|
108
|
+
session.active_file = best_file
|
|
109
|
+
session.active_directory = str(Path(best_file).parent)
|
|
110
|
+
elif session.active_file:
|
|
111
|
+
# Check pronoun references
|
|
112
|
+
words = set(re.findall(r"\b\w+\b", query.lower()))
|
|
113
|
+
pronouns = {"it", "that", "file", "code", "optimize", "fix", "convert", "again", "changed", "what"}
|
|
114
|
+
if not words.isdisjoint(pronouns):
|
|
115
|
+
pass # keep session.active_file intact
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def create_state(session: SessionState, query: str) -> dict:
|
|
119
|
+
return {
|
|
120
|
+
"user_query": query,
|
|
121
|
+
"project_path": session.project_path,
|
|
122
|
+
"file_path": session.active_file or "",
|
|
123
|
+
"source_file": session.active_file or None,
|
|
124
|
+
"target_file": None,
|
|
125
|
+
"modified_file": None,
|
|
126
|
+
"source_language": None,
|
|
127
|
+
"target_language": None,
|
|
128
|
+
"plan": [],
|
|
129
|
+
"current_step": 0,
|
|
130
|
+
"current_task": "",
|
|
131
|
+
"task_instruction": "",
|
|
132
|
+
"operation": None,
|
|
133
|
+
"selected_model": "",
|
|
134
|
+
"retry_count": 0,
|
|
135
|
+
"max_retry": 2,
|
|
136
|
+
"result": "",
|
|
137
|
+
"execution_success": False,
|
|
138
|
+
"memory_context": "",
|
|
139
|
+
"edit_response": None,
|
|
140
|
+
"discovered_files": [],
|
|
141
|
+
"review_status": None,
|
|
142
|
+
"change_summary": None,
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def run():
|
|
147
|
+
from app.setup.checks import (
|
|
148
|
+
check_disk,
|
|
149
|
+
check_ollama,
|
|
150
|
+
check_ram,
|
|
151
|
+
missing_models,
|
|
152
|
+
start_ollama,
|
|
153
|
+
)
|
|
154
|
+
from app.setup.installer import run_setup
|
|
155
|
+
|
|
156
|
+
# 1. Ollama installation check
|
|
157
|
+
if not shutil.which("ollama"):
|
|
158
|
+
print("\nError: Ollama binary not found. Please install Ollama (https://ollama.com) and ensure it is in your PATH.")
|
|
159
|
+
return
|
|
160
|
+
|
|
161
|
+
# 2. Ollama running state check
|
|
162
|
+
if not check_ollama():
|
|
163
|
+
print("\nOllama is not running.")
|
|
164
|
+
answer = input("Start it now? (Y/N): ").lower()
|
|
165
|
+
if answer == "y":
|
|
166
|
+
if not start_ollama():
|
|
167
|
+
print("Unable to start Ollama automatically. Please run 'ollama serve' in a separate terminal.")
|
|
168
|
+
return
|
|
169
|
+
|
|
170
|
+
print("Starting Ollama", end="", flush=True)
|
|
171
|
+
for _ in range(10):
|
|
172
|
+
time.sleep(1)
|
|
173
|
+
print(".", end="", flush=True)
|
|
174
|
+
if check_ollama():
|
|
175
|
+
print(" Running!")
|
|
176
|
+
break
|
|
177
|
+
else:
|
|
178
|
+
print("\nOllama is taking too long to start. Please check 'ollama serve' output.")
|
|
179
|
+
return
|
|
180
|
+
else:
|
|
181
|
+
print("Ollama must be running to execute EdgeMind V2. Exiting.")
|
|
182
|
+
return
|
|
183
|
+
|
|
184
|
+
# 3. Missing models check
|
|
185
|
+
missing = missing_models()
|
|
186
|
+
if missing:
|
|
187
|
+
print("\nMissing required Ollama models:")
|
|
188
|
+
for model in missing:
|
|
189
|
+
print(f" - {model}")
|
|
190
|
+
|
|
191
|
+
print("\nSystem Requirements Check:")
|
|
192
|
+
print(f" - Available RAM: {'✓ OK' if check_ram() else '✗ Low RAM (requires >= 4GB)'}")
|
|
193
|
+
print(f" - Free Disk Space: {'✓ OK' if check_disk() else '✗ Low Disk Space (requires >= 5GB)'}")
|
|
194
|
+
|
|
195
|
+
answer = input("\nDownload them now? (Y/N): ").lower()
|
|
196
|
+
if answer == "y":
|
|
197
|
+
import ollama
|
|
198
|
+
|
|
199
|
+
for model in missing:
|
|
200
|
+
print(f"Downloading {model}...")
|
|
201
|
+
try:
|
|
202
|
+
ollama.pull(model)
|
|
203
|
+
print(f"✓ Successfully pulled {model}")
|
|
204
|
+
except Exception as e:
|
|
205
|
+
print(f"✗ Failed to pull {model}: {e}")
|
|
206
|
+
return
|
|
207
|
+
else:
|
|
208
|
+
print("Required models are missing. Exiting.")
|
|
209
|
+
return
|
|
210
|
+
|
|
211
|
+
print_banner()
|
|
212
|
+
session = SessionState()
|
|
213
|
+
|
|
214
|
+
while True:
|
|
215
|
+
try:
|
|
216
|
+
query = input("EdgeMind > ").strip()
|
|
217
|
+
except (KeyboardInterrupt, EOFError):
|
|
218
|
+
print("\nGoodbye.\n")
|
|
219
|
+
break
|
|
220
|
+
|
|
221
|
+
if not query:
|
|
222
|
+
continue
|
|
223
|
+
|
|
224
|
+
command = query.lower()
|
|
225
|
+
if command == "exit":
|
|
226
|
+
print("\nGoodbye.\n")
|
|
227
|
+
break
|
|
228
|
+
|
|
229
|
+
if command == "help":
|
|
230
|
+
show_help()
|
|
231
|
+
continue
|
|
232
|
+
|
|
233
|
+
if command == "status":
|
|
234
|
+
show_status(session)
|
|
235
|
+
continue
|
|
236
|
+
|
|
237
|
+
if command == "memory":
|
|
238
|
+
show_memory(session)
|
|
239
|
+
continue
|
|
240
|
+
|
|
241
|
+
if command == "clear":
|
|
242
|
+
session.clear()
|
|
243
|
+
clear_terminal()
|
|
244
|
+
print_banner()
|
|
245
|
+
continue
|
|
246
|
+
|
|
247
|
+
if command == "setup":
|
|
248
|
+
run_setup()
|
|
249
|
+
continue
|
|
250
|
+
|
|
251
|
+
if command in {"what changed", "what changed?", "show diff"}:
|
|
252
|
+
if session.last_result:
|
|
253
|
+
print("\nLast Execution Output / Diff:\n")
|
|
254
|
+
print(session.last_result)
|
|
255
|
+
else:
|
|
256
|
+
print("\nNo recent changes in session.\n")
|
|
257
|
+
continue
|
|
258
|
+
|
|
259
|
+
update_session_context(session, query)
|
|
260
|
+
|
|
261
|
+
print("\nThinking...\n")
|
|
262
|
+
state = create_state(session, query)
|
|
263
|
+
|
|
264
|
+
try:
|
|
265
|
+
result_data = workflow.invoke(state)
|
|
266
|
+
|
|
267
|
+
formatted_report = format_change_review(state, result_data)
|
|
268
|
+
print(formatted_report)
|
|
269
|
+
|
|
270
|
+
# Update session context
|
|
271
|
+
active_file = (
|
|
272
|
+
result_data.get("modified_file")
|
|
273
|
+
or result_data.get("source_file")
|
|
274
|
+
or result_data.get("file_path")
|
|
275
|
+
or session.active_file
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
session.remember(
|
|
279
|
+
query=query,
|
|
280
|
+
result=result_data.get("result", ""),
|
|
281
|
+
file_path=active_file,
|
|
282
|
+
plan=result_data.get("plan"),
|
|
283
|
+
model=result_data.get("selected_model"),
|
|
284
|
+
last_edited_file=result_data.get("modified_file"),
|
|
285
|
+
)
|
|
286
|
+
print(f"\n✓ Completed task using {session.selected_model or 'Ollama'}\n")
|
|
287
|
+
|
|
288
|
+
except Exception as e:
|
|
289
|
+
print(f"\nExecution failed: {e}\n")
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
if __name__ == "__main__":
|
|
293
|
+
run()
|
app/cli/main.py
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
from app.tools.project_analyzer import analyze_project
|
|
5
|
+
from app.tools.code_explainer import explain_code
|
|
6
|
+
from app.tools.debug_assistant import debug_error
|
|
7
|
+
from app.tools.deployment_generator import save_dockerfile
|
|
8
|
+
from app.tools.requirements_generator import save_requirements
|
|
9
|
+
from app.tools.docker_compose_generator import save_docker_compose
|
|
10
|
+
|
|
11
|
+
app = typer.Typer(
|
|
12
|
+
name="EdgeMind",
|
|
13
|
+
help="Resource-Aware Agentic Coding Assistant"
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _project_path(project_path: str) -> str:
|
|
18
|
+
"""Validate and normalize a project directory argument."""
|
|
19
|
+
|
|
20
|
+
path = Path(project_path).expanduser()
|
|
21
|
+
if not path.is_dir():
|
|
22
|
+
raise typer.BadParameter("Project path must be an existing directory.")
|
|
23
|
+
return str(path.resolve())
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _file_path(file_path: str) -> Path:
|
|
27
|
+
"""Validate and normalize a source or error-log file argument."""
|
|
28
|
+
|
|
29
|
+
path = Path(file_path).expanduser()
|
|
30
|
+
if not path.is_file():
|
|
31
|
+
raise typer.BadParameter("File path must be an existing file.")
|
|
32
|
+
return path.resolve()
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@app.command()
|
|
36
|
+
def analyze(project_path: str = ".") -> None:
|
|
37
|
+
"""
|
|
38
|
+
Analyze an entire project.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
report = analyze_project(_project_path(project_path))
|
|
42
|
+
|
|
43
|
+
print("\n========================")
|
|
44
|
+
print("PROJECT ANALYSIS REPORT")
|
|
45
|
+
print("========================\n")
|
|
46
|
+
|
|
47
|
+
print("Project Info:")
|
|
48
|
+
print(report["project_info"])
|
|
49
|
+
|
|
50
|
+
print("\nResources:")
|
|
51
|
+
print(report["resources"])
|
|
52
|
+
|
|
53
|
+
print("\nSelected Model:")
|
|
54
|
+
print(report["selected_model"])
|
|
55
|
+
|
|
56
|
+
print("\nAI Analysis:\n")
|
|
57
|
+
print(report["analysis"])
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@app.command()
|
|
61
|
+
def explain(file_path: str) -> None:
|
|
62
|
+
"""
|
|
63
|
+
Explain a source code file.
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
result = explain_code(str(_file_path(file_path)))
|
|
67
|
+
|
|
68
|
+
print("\n========================")
|
|
69
|
+
print("CODE EXPLANATION")
|
|
70
|
+
print("========================\n")
|
|
71
|
+
|
|
72
|
+
print(result)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@app.command()
|
|
76
|
+
def debug(error_file: str) -> None:
|
|
77
|
+
"""
|
|
78
|
+
Analyze an error log or traceback.
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
error_text = _file_path(error_file).read_text(encoding="utf-8")
|
|
82
|
+
|
|
83
|
+
result = debug_error(error_text)
|
|
84
|
+
|
|
85
|
+
print("\n========================")
|
|
86
|
+
print("DEBUG ANALYSIS")
|
|
87
|
+
print("========================\n")
|
|
88
|
+
|
|
89
|
+
print(result)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@app.command()
|
|
93
|
+
def generate_docker(project_path: str = ".") -> None:
|
|
94
|
+
|
|
95
|
+
result = save_dockerfile(_project_path(project_path))
|
|
96
|
+
print(result)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
@app.command()
|
|
100
|
+
def generate_requirements(project_path: str = ".") -> None:
|
|
101
|
+
|
|
102
|
+
packages = save_requirements(_project_path(project_path))
|
|
103
|
+
|
|
104
|
+
print(
|
|
105
|
+
f"Generated requirements.txt "
|
|
106
|
+
f"with {len(packages)} packages."
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
@app.command()
|
|
111
|
+
def generate_compose(project_path: str = ".") -> None:
|
|
112
|
+
|
|
113
|
+
result = save_docker_compose(_project_path(project_path))
|
|
114
|
+
print(result)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
@app.command()
|
|
118
|
+
def interactive():
|
|
119
|
+
"""
|
|
120
|
+
Launch EdgeMind interactive shell.
|
|
121
|
+
"""
|
|
122
|
+
|
|
123
|
+
from app.cli.interactive import run
|
|
124
|
+
|
|
125
|
+
run()
|
|
126
|
+
|
|
127
|
+
if __name__ == "__main__":
|
|
128
|
+
app()
|
app/cli/session.py
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""
|
|
2
|
+
EdgeMind CLI Session State V2
|
|
3
|
+
|
|
4
|
+
Maintains interactive shell context between user turns.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from dataclasses import dataclass, field
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass
|
|
12
|
+
class SessionState:
|
|
13
|
+
# Project info
|
|
14
|
+
project_path: str = field(default_factory=lambda: str(Path.cwd()))
|
|
15
|
+
project_name: str = field(default_factory=lambda: Path.cwd().name)
|
|
16
|
+
|
|
17
|
+
# Context
|
|
18
|
+
active_file: str | None = None
|
|
19
|
+
active_directory: str | None = None
|
|
20
|
+
selected_model: str | None = None
|
|
21
|
+
|
|
22
|
+
# History & previous turns
|
|
23
|
+
last_query: str = ""
|
|
24
|
+
last_result: str = ""
|
|
25
|
+
last_plan: list[dict] = field(default_factory=list)
|
|
26
|
+
last_edited_file: str | None = None
|
|
27
|
+
last_created_file: str | None = None
|
|
28
|
+
|
|
29
|
+
# Conversation log
|
|
30
|
+
memory_enabled: bool = True
|
|
31
|
+
conversation_history: list[tuple[str, str]] = field(default_factory=list)
|
|
32
|
+
|
|
33
|
+
def remember(
|
|
34
|
+
self,
|
|
35
|
+
query: str,
|
|
36
|
+
result: str,
|
|
37
|
+
file_path: str | None = None,
|
|
38
|
+
plan: list | None = None,
|
|
39
|
+
model: str | None = None,
|
|
40
|
+
last_edited_file: str | None = None,
|
|
41
|
+
last_created_file: str | None = None,
|
|
42
|
+
):
|
|
43
|
+
self.last_query = query
|
|
44
|
+
self.last_result = result
|
|
45
|
+
if plan is not None:
|
|
46
|
+
self.last_plan = plan
|
|
47
|
+
if model is not None:
|
|
48
|
+
self.selected_model = model
|
|
49
|
+
if last_edited_file is not None:
|
|
50
|
+
self.last_edited_file = last_edited_file
|
|
51
|
+
if last_created_file is not None:
|
|
52
|
+
self.last_created_file = last_created_file
|
|
53
|
+
|
|
54
|
+
if file_path:
|
|
55
|
+
self.active_file = file_path
|
|
56
|
+
self.active_directory = str(Path(file_path).parent)
|
|
57
|
+
|
|
58
|
+
self.conversation_history.append((query, result))
|
|
59
|
+
|
|
60
|
+
def clear(self):
|
|
61
|
+
self.active_file = None
|
|
62
|
+
self.active_directory = None
|
|
63
|
+
self.selected_model = None
|
|
64
|
+
self.last_query = ""
|
|
65
|
+
self.last_result = ""
|
|
66
|
+
self.last_plan.clear()
|
|
67
|
+
self.last_edited_file = None
|
|
68
|
+
self.last_created_file = None
|
|
69
|
+
self.conversation_history.clear()
|
app/editing/__init__.py
ADDED
|
File without changes
|