codeaois 0.2.6__tar.gz → 0.3.1__tar.gz

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 (47) hide show
  1. {codeaois-0.2.6 → codeaois-0.3.1}/PKG-INFO +16 -1
  2. {codeaois-0.2.6 → codeaois-0.3.1}/README.md +15 -0
  3. codeaois-0.3.1/codeaois/agents/coder_agent.py +46 -0
  4. codeaois-0.3.1/codeaois/agents/researcher_agent.py +59 -0
  5. codeaois-0.3.1/codeaois/agents/scaffolder_agent.py +63 -0
  6. codeaois-0.3.1/codeaois/agents/vision_agent.py +45 -0
  7. codeaois-0.3.1/codeaois/brain/embeddings.py +112 -0
  8. codeaois-0.3.1/codeaois/brain/scanner.py +75 -0
  9. {codeaois-0.2.6 → codeaois-0.3.1}/codeaois/cli/main.py +260 -133
  10. codeaois-0.3.1/codeaois/core/_auth.py +43 -0
  11. {codeaois-0.2.6 → codeaois-0.3.1}/codeaois/core/context.py +13 -1
  12. codeaois-0.3.1/codeaois/core/memory.py +162 -0
  13. codeaois-0.3.1/codeaois/core/orchestrator.py +81 -0
  14. codeaois-0.3.1/codeaois/core/planner.py +134 -0
  15. codeaois-0.3.1/codeaois/models/llm_interface.py +135 -0
  16. {codeaois-0.2.6 → codeaois-0.3.1}/codeaois.egg-info/PKG-INFO +16 -1
  17. {codeaois-0.2.6 → codeaois-0.3.1}/codeaois.egg-info/SOURCES.txt +3 -0
  18. {codeaois-0.2.6 → codeaois-0.3.1}/pyproject.toml +1 -1
  19. {codeaois-0.2.6 → codeaois-0.3.1}/setup.py +16 -3
  20. codeaois-0.2.6/codeaois/agents/coder_agent.py +0 -34
  21. codeaois-0.2.6/codeaois/brain/embeddings.py +0 -27
  22. codeaois-0.2.6/codeaois/brain/scanner.py +0 -49
  23. codeaois-0.2.6/codeaois/core/_auth.py +0 -17
  24. codeaois-0.2.6/codeaois/core/memory.py +0 -80
  25. codeaois-0.2.6/codeaois/core/orchestrator.py +0 -23
  26. codeaois-0.2.6/codeaois/core/planner.py +0 -50
  27. codeaois-0.2.6/codeaois/models/llm_interface.py +0 -70
  28. {codeaois-0.2.6 → codeaois-0.3.1}/codeaois/__init__.py +0 -0
  29. {codeaois-0.2.6 → codeaois-0.3.1}/codeaois/agents/data_science_agent.py +0 -0
  30. {codeaois-0.2.6 → codeaois-0.3.1}/codeaois/agents/git_agent.py +0 -0
  31. {codeaois-0.2.6 → codeaois-0.3.1}/codeaois/agents/pip_agent.py +0 -0
  32. {codeaois-0.2.6 → codeaois-0.3.1}/codeaois/agents/terminal_agent.py +0 -0
  33. {codeaois-0.2.6 → codeaois-0.3.1}/codeaois/agents/tester_agent.py +0 -0
  34. {codeaois-0.2.6 → codeaois-0.3.1}/codeaois/app.py +0 -0
  35. {codeaois-0.2.6 → codeaois-0.3.1}/codeaois/brain/__init__.py +0 -0
  36. {codeaois-0.2.6 → codeaois-0.3.1}/codeaois/brain/memory.py +0 -0
  37. {codeaois-0.2.6 → codeaois-0.3.1}/codeaois/cli/__init__.py +0 -0
  38. {codeaois-0.2.6 → codeaois-0.3.1}/codeaois/config/settings.py +0 -0
  39. {codeaois-0.2.6 → codeaois-0.3.1}/codeaois/core/marketplace.py +0 -0
  40. {codeaois-0.2.6 → codeaois-0.3.1}/codeaois/core/router.py +0 -0
  41. {codeaois-0.2.6 → codeaois-0.3.1}/codeaois/utils/file_writer.py +0 -0
  42. {codeaois-0.2.6 → codeaois-0.3.1}/codeaois/utils/logger.py +0 -0
  43. {codeaois-0.2.6 → codeaois-0.3.1}/codeaois.egg-info/dependency_links.txt +0 -0
  44. {codeaois-0.2.6 → codeaois-0.3.1}/codeaois.egg-info/entry_points.txt +0 -0
  45. {codeaois-0.2.6 → codeaois-0.3.1}/codeaois.egg-info/requires.txt +0 -0
  46. {codeaois-0.2.6 → codeaois-0.3.1}/codeaois.egg-info/top_level.txt +0 -0
  47. {codeaois-0.2.6 → codeaois-0.3.1}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codeaois
3
- Version: 0.2.6
3
+ Version: 0.3.1
4
4
  Summary: Advanced AI Developer OS
5
5
  Author: Nikhil Nagar
6
6
  Requires-Python: >=3.8
@@ -14,6 +14,21 @@ Dynamic: requires-python
14
14
 
15
15
  # CodeAOIS
16
16
 
17
+ # 💻 CodeAOIS (v0.3.0) - Advanced Developer OS
18
+ An elite, autonomous AI operating system for your terminal. CodeAOIS doesn't just chat; it reads your codebase, runs its own code, and fixes its own bugs.
19
+
20
+ ## 🔥 Core Pillars
21
+ 1. **Global Cloud Identity:** Secure, OTP-based login via private SMTP. Your history, developer profile, and token usage follow you to any machine via Supabase.
22
+ 2. **Autonomous Execution Loop:** The OS executes generated Python scripts locally. If it hits a `Traceback` error, it autonomously feeds the crash data back to the LLM and self-corrects the code.
23
+ 3. **Local Codebase Brain (RAG):** Type `/index` to calculate embeddings for your entire local project. CodeAOIS instantly retrieves semantic context so you never have to copy-paste your code again.
24
+ 4. **LLM Supervisor Engine:** An intelligent routing system that determines if you want to chat, execute a terminal command, or write a new script, routing tasks to specialized agents seamlessly.
25
+
26
+ ## ⚡ Quick Start
27
+ ```bash
28
+ pip install -r requirements.txt
29
+ codeaois
30
+ ```
31
+
17
32
  ### Advanced AI Developer Operating System
18
33
 
19
34
  CodeAOIS is an experimental **AI-powered developer operating system** that allows developers to interact with an AI coding system directly from the terminal.
@@ -1,5 +1,20 @@
1
1
  # CodeAOIS
2
2
 
3
+ # 💻 CodeAOIS (v0.3.0) - Advanced Developer OS
4
+ An elite, autonomous AI operating system for your terminal. CodeAOIS doesn't just chat; it reads your codebase, runs its own code, and fixes its own bugs.
5
+
6
+ ## 🔥 Core Pillars
7
+ 1. **Global Cloud Identity:** Secure, OTP-based login via private SMTP. Your history, developer profile, and token usage follow you to any machine via Supabase.
8
+ 2. **Autonomous Execution Loop:** The OS executes generated Python scripts locally. If it hits a `Traceback` error, it autonomously feeds the crash data back to the LLM and self-corrects the code.
9
+ 3. **Local Codebase Brain (RAG):** Type `/index` to calculate embeddings for your entire local project. CodeAOIS instantly retrieves semantic context so you never have to copy-paste your code again.
10
+ 4. **LLM Supervisor Engine:** An intelligent routing system that determines if you want to chat, execute a terminal command, or write a new script, routing tasks to specialized agents seamlessly.
11
+
12
+ ## ⚡ Quick Start
13
+ ```bash
14
+ pip install -r requirements.txt
15
+ codeaois
16
+ ```
17
+
3
18
  ### Advanced AI Developer Operating System
4
19
 
5
20
  CodeAOIS is an experimental **AI-powered developer operating system** that allows developers to interact with an AI coding system directly from the terminal.
@@ -0,0 +1,46 @@
1
+ from codeaois.models.llm_interface import call_openrouter
2
+ from rich.console import Console
3
+ import time
4
+
5
+ console = Console()
6
+
7
+ def generate_code(user_input, full_context=""):
8
+ """Multi-Agent Swarm that plans, writes, and reviews code."""
9
+
10
+ # --- AGENT 1: THE ARCHITECT ---
11
+ console.print(f"[dim]🧠 [Architect] Drafting system blueprint...[/dim]")
12
+ architect_sys = "You are a Senior Software Architect. Break the user's request down into a strict, bulleted, step-by-step implementation plan. Identify the best programming language if not specified. Do NOT write code. Only write the logical plan."
13
+ plan = call_openrouter(architect_sys, f"REQUEST: {user_input}\nCONTEXT: {full_context}", intent="chat", history=[])
14
+
15
+ # Let the free API cool down!
16
+ time.sleep(8)
17
+
18
+ # --- AGENT 2: THE DEVELOPER ---
19
+ console.print(f"[dim]💻 [Developer] Writing code based on blueprint...[/dim]")
20
+ dev_sys = "You are an Elite Polyglot Software Engineer. Write the complete, functional code based EXACTLY on the Architect's plan. Return ONLY the final code inside a markdown block. Do not add explanations."
21
+ dev_prompt = f"ARCHITECT PLAN:\n{plan}\n\nWrite the code."
22
+ raw_code = call_openrouter(dev_sys, dev_prompt, intent="code", history=[])
23
+
24
+ # Let the free API cool down again!
25
+ time.sleep(8)
26
+
27
+ # --- AGENT 3: THE QA TESTER ---
28
+ console.print(f"[dim]🔎 [QA Tester] Auditing code for bugs and syntax errors...[/dim]")
29
+ qa_sys = "You are a strict QA Code Reviewer. Review the provided code for logic errors, missing dependencies, or syntax issues. Fix any issues found. Return ONLY the final, perfect code inside a markdown block. No explanations."
30
+ qa_prompt = f"ORIGINAL REQUEST: {user_input}\n\nDRAFT CODE:\n{raw_code}\n\nReview and return the final bulletproof code."
31
+ final_code = call_openrouter(qa_sys, qa_prompt, intent="code", history=[])
32
+
33
+ console.print("[bold green]✓ Swarm consensus reached![/bold green]")
34
+
35
+ return final_code
36
+
37
+ def generate_lite_code(user_input, full_context=""):
38
+ """Single-shot coder that bypasses the Swarm to prevent API rate limits."""
39
+ console.print(f"[dim]⚡ [Fast Coder] Writing code in a single shot...[/dim]")
40
+
41
+ dev_sys = """You are an Elite Software Engineer. Write the complete, functional code requested by the user.
42
+ You MUST output the target filename at the very top of your response (e.g., `### Filename: clock.html`).
43
+ Then, provide ONLY the final code inside a markdown block. Do NOT add any conversational text or apologies."""
44
+
45
+ # We use a single API call instead of 3, keeping you perfectly under the free rate limits!
46
+ return call_openrouter(dev_sys, f"REQUEST: {user_input}\nCONTEXT: {full_context}", intent="code", history=[])
@@ -0,0 +1,59 @@
1
+ import requests
2
+ from bs4 import BeautifulSoup
3
+ from ddgs import DDGS
4
+ from codeaois.models.llm_interface import call_openrouter
5
+ from rich.console import Console
6
+
7
+ console = Console()
8
+
9
+ def search_and_scrape(query, num_results=2):
10
+ """Searches the web and scrapes the text from the top results."""
11
+ results_text = ""
12
+ try:
13
+ with DDGS() as ddgs:
14
+ results = list(ddgs.text(query, max_results=num_results))
15
+ for res in results:
16
+ url = res.get('href')
17
+ title = res.get('title')
18
+ results_text += f"\n--- Source: {title} ({url}) ---\n"
19
+
20
+ try:
21
+ # Stealth headers to bypass basic bot-blockers
22
+ headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'}
23
+ page = requests.get(url, headers=headers, timeout=5)
24
+ soup = BeautifulSoup(page.content, 'html.parser')
25
+
26
+ # Grab all the paragraph text
27
+ paragraphs = soup.find_all('p')
28
+ text = " ".join([p.get_text() for p in paragraphs])
29
+
30
+ # Limit to 2500 characters per site to save your tokens
31
+ results_text += text[:2500] + "...\n"
32
+ except Exception as e:
33
+ results_text += f"[Failed to scrape content from {url}]\n"
34
+ except Exception as e:
35
+ console.print(f"\n[bold red]✗ Search Engine Crash:[/bold red] {e}")
36
+ return "WEB SEARCH FAILED. Tell the user you cannot search the internet due to an API error."
37
+
38
+ return results_text
39
+
40
+ def generate_researcher_code(user_input, full_context=""):
41
+ """Orchestrates the search, scrape, and synthesis process."""
42
+
43
+ # 1. Ask the AI to figure out exactly what to search for
44
+ query_prompt = f"Extract the core web search query from this prompt. Reply with ONLY the search string, no quotes.\nPrompt: {user_input}"
45
+ search_query = call_openrouter("You are a search query extractor.", query_prompt, intent="chat", history=[])
46
+ search_query = search_query.strip().strip('"').strip("'")
47
+
48
+ console.print(f"[dim]🌐 Searching live web for: [bold white]{search_query}[/bold white][/dim]")
49
+
50
+ # 2. Execute the live web search and scrape the pages
51
+ web_context = search_and_scrape(search_query)
52
+
53
+ # 3. Feed the live data back to the AI to answer the user
54
+ console.print(f"[dim]📚 Reading scraped documentation...[/dim]")
55
+
56
+ sys_prompt = "You are an expert AI Researcher with live internet access. Use the provided Web Research to answer the user accurately."
57
+ final_prompt = f"LATEST WEB RESEARCH:\n{web_context}\n\nUSER REQUEST: {user_input}\nLOCAL CONTEXT:\n{full_context}"
58
+
59
+ return call_openrouter(sys_prompt, final_prompt, intent="chat", history=[])
@@ -0,0 +1,63 @@
1
+ import os
2
+ import json
3
+ import re
4
+ from rich.console import Console
5
+ from codeaois.models.llm_interface import call_openrouter
6
+
7
+ console = Console()
8
+
9
+ def generate_scaffolder_code(user_input, full_context=""):
10
+ """Forces the AI to design a multi-file project architecture and writes it to disk."""
11
+ console.print("[dim]🏗️ [Scaffolder] Architecting multi-file project structure...[/dim]")
12
+
13
+ sys_prompt = """You are an Expert Software Architect. The user wants to generate a complete, multi-file project.
14
+ You MUST respond ONLY with a valid JSON object representing the file structure and code.
15
+ Do not add conversational text. Only JSON.
16
+
17
+ Format EXACTLY like this:
18
+ {
19
+ "project_name": "my_new_app",
20
+ "files": [
21
+ {"path": "main.py", "content": "print('hello world')"},
22
+ {"path": "src/utils.py", "content": "def add(a,b): return a+b"},
23
+ {"path": "requirements.txt", "content": "requests==2.31.0"}
24
+ ]
25
+ }
26
+ """
27
+
28
+ # Send request to your active AI model
29
+ response = call_openrouter(sys_prompt, user_input, intent="code", history=[])
30
+
31
+ try:
32
+ # Extract the JSON even if the AI wraps it in markdown blocks
33
+ json_str = response.strip()
34
+ match = re.search(r"```(?:json)?\s*(\{.*\})\s*```", response, re.DOTALL)
35
+ if match:
36
+ json_str = match.group(1)
37
+ else:
38
+ # Fallback: Find the first { and last }
39
+ start = response.find('{')
40
+ end = response.rfind('}') + 1
41
+ json_str = response[start:end]
42
+
43
+ data = json.loads(json_str)
44
+ project_name = data.get("project_name", "new_project")
45
+ files = data.get("files", [])
46
+
47
+ if not files:
48
+ return "❌ AI didn't return any valid files to generate."
49
+
50
+ console.print(f"\n[bold cyan]📦 Building Project Directory: {project_name}[/bold cyan]")
51
+
52
+ # Build the folders and files!
53
+ for f in files:
54
+ file_path = os.path.join(project_name, f["path"])
55
+ os.makedirs(os.path.dirname(file_path), exist_ok=True) # Create folders if they don't exist
56
+ with open(file_path, "w", encoding="utf-8") as out_file:
57
+ out_file.write(f["content"])
58
+ console.print(f"[green] + Created: {f['path']}[/green]")
59
+
60
+ return f"### 🏗️ Scaffolding Complete!\nSuccessfully generated `{project_name}` with {len(files)} files and folders."
61
+
62
+ except Exception as e:
63
+ return f"❌ **Scaffolder JSON Parsing Error:** The AI failed to output valid JSON. Try a smarter coding model.\n*Error: {e}*"
@@ -0,0 +1,45 @@
1
+ import os
2
+ import re
3
+ from playwright.sync_api import sync_playwright
4
+ from rich.console import Console
5
+ from codeaois.models.llm_interface import call_openrouter
6
+
7
+ console = Console()
8
+
9
+ def generate_vision_code(user_input, full_context=""):
10
+ """Opens local HTML files in a headless browser and uses Vision AI to critique the UI."""
11
+
12
+ # Look for an HTML file mentioned in the user's prompt
13
+ match = re.search(r'([a-zA-Z0-9_\-\.]+\.html)', user_input)
14
+ if not match:
15
+ return "❌ Please specify an HTML file to test (e.g., 'look at clock.html')."
16
+
17
+ file_name = match.group(1)
18
+ file_path = os.path.abspath(file_name)
19
+
20
+ if not os.path.exists(file_path):
21
+ return f"❌ Could not find {file_name} in the current directory."
22
+
23
+ screenshot_path = "debug_screenshot.png"
24
+
25
+ console.print(f"[dim]👁️ [Vision Engine] Launching headless browser to view {file_name}...[/dim]")
26
+
27
+ try:
28
+ # Spin up the hidden browser and take a picture!
29
+ with sync_playwright() as p:
30
+ browser = p.chromium.launch(headless=True)
31
+ page = browser.new_page()
32
+ page.goto(f"file://{file_path}")
33
+ page.screenshot(path=screenshot_path)
34
+ browser.close()
35
+ except Exception as e:
36
+ return f"❌ Headless Browser Error: {e}"
37
+
38
+ console.print(f"[dim]📸 [Vision Engine] Screenshot captured! Sending to Vision AI...[/dim]")
39
+
40
+ sys_prompt = "You are an Expert UI/UX Frontend Reviewer. Look at the provided screenshot of the user's web page. Critique the design, layout, alignment, and colors. Point out any visible CSS bugs or ugly elements, and provide the exact CSS code to fix them."
41
+
42
+ # Send the screenshot to your custom AI model! (Requires the updated llm_interface.py from earlier)
43
+ review = call_openrouter(sys_prompt, user_input, intent="chat", history=[], image_path=screenshot_path)
44
+
45
+ return f"{review}"
@@ -0,0 +1,112 @@
1
+ import os
2
+ import json
3
+ import re
4
+ from pathlib import Path
5
+
6
+ class CodebaseMemory:
7
+ def __init__(self, project_path="."):
8
+ self.project_path = Path(project_path)
9
+ self.db_dir = self.project_path / ".codeaois_db"
10
+ self.db_file = self.db_dir / "local_index.json"
11
+
12
+ # Folders and files we DO NOT want the AI to read
13
+ self.ignore_dirs = {".git", "__pycache__", "venv", "env", "node_modules", ".codeaois_db", "dist", "build"}
14
+ self.ignore_exts = {".pyc", ".png", ".jpg", ".jpeg", ".gif", ".exe", ".dll", ".so", ".pdf", ".zip"}
15
+
16
+ def _chunk_text(self, text, max_chars=1500):
17
+ """Chops large code files into digestible blocks for the AI."""
18
+ lines = text.split('\n')
19
+ chunks = []
20
+ current_chunk = ""
21
+
22
+ for line in lines:
23
+ if len(current_chunk) + len(line) > max_chars:
24
+ chunks.append(current_chunk)
25
+ current_chunk = line + "\n"
26
+ else:
27
+ current_chunk += line + "\n"
28
+
29
+ if current_chunk:
30
+ chunks.append(current_chunk)
31
+
32
+ return chunks
33
+
34
+ def index_project(self):
35
+ """Scans the project, chunks the code, and saves it to the local brain."""
36
+ self.db_dir.mkdir(exist_ok=True)
37
+
38
+ chunks_indexed = 0
39
+ index_data = []
40
+
41
+ for root, dirs, files in os.walk(self.project_path):
42
+ # Skip ignored directories and hidden folders
43
+ dirs[:] = [d for d in dirs if d not in self.ignore_dirs and not d.startswith('.')]
44
+
45
+ for file in files:
46
+ # Skip ignored extensions and hidden files
47
+ if any(file.endswith(ext) for ext in self.ignore_exts) or file.startswith('.'):
48
+ continue
49
+
50
+ file_path = Path(root) / file
51
+
52
+ try:
53
+ with open(file_path, "r", encoding="utf-8") as f:
54
+ content = f.read()
55
+
56
+ # Break the file into chunks
57
+ chunks = self._chunk_text(content)
58
+
59
+ for i, chunk in enumerate(chunks):
60
+ index_data.append({
61
+ "file": str(file_path.relative_to(self.project_path)),
62
+ "chunk_id": i,
63
+ "content": chunk
64
+ })
65
+ chunks_indexed += 1
66
+ except Exception:
67
+ # Silently skip binary files or files with weird encodings
68
+ continue
69
+
70
+ # Save the brain to the hidden database folder
71
+ with open(self.db_file, "w", encoding="utf-8") as f:
72
+ json.dump(index_data, f, indent=4)
73
+
74
+ return chunks_indexed
75
+
76
+ def search(self, query, top_k=3):
77
+ """Searches the local JSON brain for chunks matching the user's prompt."""
78
+ if not self.db_file.exists():
79
+ return "" # Brain is empty, run /index first
80
+
81
+ try:
82
+ with open(self.db_file, "r", encoding="utf-8") as f:
83
+ index_data = json.load(f)
84
+ except Exception:
85
+ return ""
86
+
87
+ # Simple keyword scoring engine
88
+ query_words = set(re.findall(r'\w+', query.lower()))
89
+ # Ignore common filler words
90
+ stop_words = {"a", "the", "is", "in", "to", "and", "how", "what", "where", "write", "code"}
91
+ search_words = query_words - stop_words
92
+
93
+ if not search_words:
94
+ return ""
95
+
96
+ scored_chunks = []
97
+ for item in index_data:
98
+ chunk_text = item["content"].lower()
99
+ # Give a point for every matching keyword
100
+ score = sum(1 for word in search_words if word in chunk_text)
101
+ if score > 0:
102
+ scored_chunks.append((score, item))
103
+
104
+ # Sort from highest score to lowest
105
+ scored_chunks.sort(key=lambda x: x[0], reverse=True)
106
+
107
+ # Grab the top results and format them
108
+ results = []
109
+ for score, item in scored_chunks[:top_k]:
110
+ results.append(f"--- Auto-Retrieved Context: {item['file']} ---\n{item['content']}")
111
+
112
+ return "\n\n".join(results)
@@ -0,0 +1,75 @@
1
+ # codeaois/brain/scanner.py
2
+ import os
3
+ import pathspec
4
+
5
+ import os
6
+
7
+ # --- THE CONTEXT SHIELD ---
8
+ IGNORE_DIRS = {".git", "node_modules", "venv", "env", "__pycache__", ".codeaois_db", "build", "dist", ".idea", ".vscode"}
9
+ IGNORE_FILES = {".env", ".DS_Store", "package-lock.json", "yarn.lock"}
10
+ IGNORE_EXTS = {".pyc", ".png", ".jpg", ".jpeg", ".exe", ".dll", ".so", ".zip", ".tar.gz", ".pdf", ".mp4"}
11
+
12
+ def scan_project_structure(root_dir="."):
13
+ """Scans the directory tree, but shields the AI from massive junk folders and binary files."""
14
+ tree_str = ""
15
+
16
+ for root, dirs, files in os.walk(root_dir):
17
+ # IN-PLACE FILTERING: This physically stops the OS from even looking inside node_modules or .git!
18
+ dirs[:] = [d for d in dirs if d not in IGNORE_DIRS and not d.startswith('.')]
19
+
20
+ level = root.replace(root_dir, '').count(os.sep)
21
+ indent = ' ' * 4 * (level)
22
+ folder_name = os.path.basename(root)
23
+
24
+ if folder_name:
25
+ tree_str += f"{indent}📂 {folder_name}/\n"
26
+
27
+ subindent = ' ' * 4 * (level + 1)
28
+ for f in files:
29
+ # Block secret files and heavy images/binaries
30
+ if f in IGNORE_FILES or any(f.endswith(ext) for ext in IGNORE_EXTS):
31
+ continue
32
+ tree_str += f"{subindent}📄 {f}\n"
33
+
34
+ return tree_str if tree_str else "📂 (Empty Project)"
35
+ class CodeScanner:
36
+ def __init__(self, root_dir="."):
37
+ self.root_dir = root_dir
38
+ # Standard directories and files to ignore globally for RAG Embeddings
39
+ self.default_ignores = [
40
+ ".git", "venv", "env", "__pycache__", "node_modules",
41
+ ".pytest_cache", "dist", "build", "*.egg-info",
42
+ "*.pyc", "*.png", "*.jpg", "*.pdf", ".codeaois_db"
43
+ ]
44
+ self.ignore_spec = self._load_gitignore()
45
+
46
+ def _load_gitignore(self):
47
+ """Loads .gitignore if it exists and combines it with defaults."""
48
+ ignore_patterns = list(self.default_ignores)
49
+ gitignore_path = os.path.join(self.root_dir, ".gitignore")
50
+
51
+ if os.path.exists(gitignore_path):
52
+ with open(gitignore_path, "r") as f:
53
+ ignore_patterns.extend(f.read().splitlines())
54
+
55
+ return pathspec.PathSpec.from_lines('gitwildmatch', ignore_patterns)
56
+
57
+ def scan_project(self):
58
+ """Yields (filepath, content) for all valid code files."""
59
+ for dirpath, dirnames, filenames in os.walk(self.root_dir):
60
+ # Filter out ignored directories in-place
61
+ dirnames[:] = [d for d in dirnames if not self.ignore_spec.match_file(os.path.join(dirpath, d))]
62
+
63
+ for filename in filenames:
64
+ filepath = os.path.join(dirpath, filename)
65
+ rel_path = os.path.relpath(filepath, self.root_dir)
66
+
67
+ if self.ignore_spec.match_file(rel_path):
68
+ continue
69
+
70
+ try:
71
+ with open(filepath, "r", encoding="utf-8") as f:
72
+ yield rel_path, f.read()
73
+ except UnicodeDecodeError:
74
+ # Skip binary files that slipped through
75
+ pass