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.
Files changed (51) hide show
  1. app/__init__.py +3 -0
  2. app/cli/__init__.py +0 -0
  3. app/cli/banner.py +35 -0
  4. app/cli/commands.py +78 -0
  5. app/cli/interactive.py +293 -0
  6. app/cli/main.py +128 -0
  7. app/cli/session.py +69 -0
  8. app/editing/__init__.py +0 -0
  9. app/editing/code_modifier.py +99 -0
  10. app/editing/diff_generator.py +46 -0
  11. app/editing/editing_service.py +246 -0
  12. app/editing/file_manager.py +137 -0
  13. app/editing/models.py +44 -0
  14. app/editing/validator.py +146 -0
  15. app/graph/__init__.py +0 -0
  16. app/graph/evaluator.py +20 -0
  17. app/graph/nodes.py +510 -0
  18. app/graph/planner.py +371 -0
  19. app/graph/planner_schema.py +51 -0
  20. app/graph/state.py +33 -0
  21. app/graph/workflow.py +80 -0
  22. app/memory/__init__.py +0 -0
  23. app/memory/database.py +14 -0
  24. app/memory/memory_manager.py +75 -0
  25. app/memory/schema.py +24 -0
  26. app/models/__init__.py +0 -0
  27. app/models/model_router.py +36 -0
  28. app/models/ollama_client.py +35 -0
  29. app/resources/__init__.py +0 -0
  30. app/resources/system_monitor.py +14 -0
  31. app/scripts/__init__.py +1 -0
  32. app/scripts/export_graph.py +5 -0
  33. app/setup/__init__.py +1 -0
  34. app/setup/checks.py +91 -0
  35. app/setup/installer.py +127 -0
  36. app/setup/models.py +8 -0
  37. app/tools/__init__.py +0 -0
  38. app/tools/code_explainer.py +46 -0
  39. app/tools/code_scanner.py +50 -0
  40. app/tools/debug_assistant.py +31 -0
  41. app/tools/deployment_generator.py +63 -0
  42. app/tools/docker_compose_generator.py +70 -0
  43. app/tools/file_discovery.py +175 -0
  44. app/tools/file_reader.py +16 -0
  45. app/tools/project_analyzer.py +69 -0
  46. app/tools/requirements_generator.py +98 -0
  47. edgemind-1.0.1.dist-info/METADATA +843 -0
  48. edgemind-1.0.1.dist-info/RECORD +51 -0
  49. edgemind-1.0.1.dist-info/WHEEL +5 -0
  50. edgemind-1.0.1.dist-info/entry_points.txt +2 -0
  51. edgemind-1.0.1.dist-info/top_level.txt +1 -0
app/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ """EdgeMind Package Header"""
2
+
3
+ __version__ = "1.0.1"
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()
File without changes