myra-linux 0.1.0__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 (80) hide show
  1. Aria_office_manage.py +1985 -0
  2. Main_normal.py +261 -0
  3. aria.py +2872 -0
  4. aria_modules/__init__.py +1 -0
  5. aria_modules/aria_activation.py +335 -0
  6. aria_modules/aria_advanced_features.py +1269 -0
  7. aria_modules/aria_advanced_tools.py +1099 -0
  8. aria_modules/assets/about/cofounder.png +0 -0
  9. aria_modules/assets/about/developer.png +0 -0
  10. aria_modules/assets/fonts/OFL-Poppins.txt +93 -0
  11. aria_modules/assets/fonts/Poppins-Bold.ttf +0 -0
  12. aria_modules/assets/fonts/Poppins-ExtraBold.ttf +0 -0
  13. aria_modules/assets/fonts/Poppins-Medium.ttf +0 -0
  14. aria_modules/assets/fonts/Poppins-Regular.ttf +0 -0
  15. aria_modules/assets/fonts/Poppins-SemiBold.ttf +0 -0
  16. aria_modules/assets/myra_girl.png +0 -0
  17. aria_modules/assets/myra_logo.png +0 -0
  18. aria_modules/assets/splash_bg.jpg +0 -0
  19. aria_modules/assets/ui_reference.png +0 -0
  20. aria_modules/cloud_database.py +10 -0
  21. aria_modules/game_install.py +1056 -0
  22. aria_modules/gemini_rest.py +102 -0
  23. aria_modules/haarcascade_frontalface_default.xml +33314 -0
  24. aria_modules/license_client.py +250 -0
  25. aria_modules/minibar_aria.py +355 -0
  26. aria_modules/tools.py +2295 -0
  27. aria_ui.py +818 -0
  28. camera_vision_module.py +1040 -0
  29. linux_extra.py +240 -0
  30. myra/__init__.py +10 -0
  31. myra/__main__.py +3 -0
  32. myra/cli.py +141 -0
  33. myra/config.py +22 -0
  34. myra_actions.py +327 -0
  35. myra_apps.py +846 -0
  36. myra_autostart.py +170 -0
  37. myra_coder_integration.py +641 -0
  38. myra_edge_glow.py +274 -0
  39. myra_float_orb.py +471 -0
  40. myra_guardian.py +181 -0
  41. myra_lang.py +133 -0
  42. myra_linux-0.1.0.dist-info/METADATA +99 -0
  43. myra_linux-0.1.0.dist-info/RECORD +80 -0
  44. myra_linux-0.1.0.dist-info/WHEEL +5 -0
  45. myra_linux-0.1.0.dist-info/entry_points.txt +3 -0
  46. myra_linux-0.1.0.dist-info/licenses/LICENSE +36 -0
  47. myra_linux-0.1.0.dist-info/top_level.txt +31 -0
  48. myra_linux_boot.py +207 -0
  49. myra_linux_tools.py +314 -0
  50. myra_pages.py +1246 -0
  51. myra_prefs.py +71 -0
  52. myra_quick_features.py +748 -0
  53. myra_slot.py +39 -0
  54. myra_splash.py +301 -0
  55. myra_spotify.py +316 -0
  56. myra_studio.py +916 -0
  57. myra_ui.py +1057 -0
  58. normal_prompt.py +41 -0
  59. prompt.py +252 -0
  60. screen_share_module.py +447 -0
  61. website_builder/__init__.py +5 -0
  62. website_builder/agents/__init__.py +1 -0
  63. website_builder/agents/content_agent.py +99 -0
  64. website_builder/agents/generator_agent.py +249 -0
  65. website_builder/agents/planner_agent.py +175 -0
  66. website_builder/config.py +48 -0
  67. website_builder/database/__init__.py +1 -0
  68. website_builder/database/projects_db.py +47 -0
  69. website_builder/deploy/__init__.py +1 -0
  70. website_builder/deploy/github_service.py +67 -0
  71. website_builder/deploy/qr_display.py +130 -0
  72. website_builder/deploy/vercel_service.py +71 -0
  73. website_builder/main.py +25 -0
  74. website_builder/preview/__init__.py +1 -0
  75. website_builder/preview/preview_server.py +47 -0
  76. website_builder/project/__init__.py +1 -0
  77. website_builder/project/project_manager.py +32 -0
  78. website_builder/webforge_integration.py +220 -0
  79. website_builder/webforge_manager.py +209 -0
  80. whatsapp_file_sender.py +661 -0
@@ -0,0 +1,249 @@
1
+ # -*- coding: utf-8 -*-
2
+ """MYRA WebForge — Generator Agent (HTML/CSS/JS)"""
3
+
4
+ import json, re
5
+ from aria_modules.gemini_rest import FallbackModel
6
+
7
+ _HAS_GENAI = False
8
+ try:
9
+ try:
10
+ import google.genai as genai
11
+ except Exception:
12
+ import google.generativeai as genai
13
+ from website_builder.config import GEMINI_API_KEY
14
+ try:
15
+ if hasattr(genai, 'configure'):
16
+ genai.configure(api_key=GEMINI_API_KEY)
17
+ except Exception:
18
+ pass
19
+ _HAS_GENAI = True
20
+ except Exception:
21
+ _HAS_GENAI = False
22
+
23
+ THEME_CSS_VARS = {
24
+ "dark": {"bg":"#0a0a0a","bg2":"#111827","text":"#f9fafb","accent":"#3b82f6","accent2":"#06b6d4","card":"#1f2937","border":"rgba(255,255,255,0.1)"},
25
+ "light": {"bg":"#ffffff","bg2":"#f8fafc","text":"#0f172a","accent":"#2563eb","accent2":"#0891b2","card":"#f1f5f9","border":"rgba(0,0,0,0.08)"},
26
+ "glassmorphism": {"bg":"#0f0c29","bg2":"#302b63","text":"#ffffff","accent":"#a78bfa","accent2":"#38bdf8","card":"rgba(255,255,255,0.08)","border":"rgba(255,255,255,0.18)"},
27
+ "gaming": {"bg":"#050505","bg2":"#0d0d0d","text":"#ffffff","accent":"#ff0055","accent2":"#ff6600","card":"#111111","border":"rgba(255,0,85,0.3)"},
28
+ "futuristic_neon":{"bg":"#020014","bg2":"#0a0028","text":"#e0e0ff","accent":"#00ffcc","accent2":"#ff00ff","card":"#0d0030","border":"rgba(0,255,204,0.25)"},
29
+ "ai_startup": {"bg":"#050b18","bg2":"#0c1526","text":"#f0f4ff","accent":"#6366f1","accent2":"#06b6d4","card":"#0f1d38","border":"rgba(99,102,241,0.25)"},
30
+ "portfolio": {"bg":"#0d1117","bg2":"#161b22","text":"#e6edf3","accent":"#58a6ff","accent2":"#3fb950","card":"#21262d","border":"rgba(88,166,255,0.2)"},
31
+ "corporate": {"bg":"#ffffff","bg2":"#f5f7fa","text":"#1a202c","accent":"#1a56db","accent2":"#047857","card":"#edf2f7","border":"rgba(0,0,0,0.1)"},
32
+ "ecommerce": {"bg":"#fafafa","bg2":"#f0f0f0","text":"#1a1a1a","accent":"#ff6900","accent2":"#ffd700","card":"#ffffff","border":"rgba(0,0,0,0.1)"},
33
+ }
34
+
35
+
36
+ class GeneratorAgent:
37
+ def __init__(self):
38
+ if True: # REST fallback model — SDK pe depend nahi
39
+ try:
40
+ self._model = FallbackModel() # Gemini REST + model fallback chain
41
+ except Exception:
42
+ self._model = None
43
+ else:
44
+ self._model = None
45
+
46
+ def _theme(self, theme: str) -> dict:
47
+ return THEME_CSS_VARS.get(theme, THEME_CSS_VARS["dark"])
48
+
49
+ def generate_html(self, plan: dict, content: dict) -> str:
50
+ if not self._model:
51
+ return self._fallback_html(plan, content)
52
+
53
+ tv = self._theme(plan.get("theme", "dark"))
54
+ prompt = f"""
55
+ You are a world-class web developer. Generate a COMPLETE, BEAUTIFUL, PRODUCTION-READY single-page website.
56
+
57
+ Plan: {json.dumps(plan, indent=2)}
58
+ Content: {json.dumps(content, indent=2)}
59
+ Theme Variables: {json.dumps(tv, indent=2)}
60
+
61
+ REQUIREMENTS:
62
+ 1. Return ONLY complete index.html — no explanation, no markdown fences.
63
+ 2. Inline all CSS in <style> tag, inline all JS in <script> tag.
64
+ 3. Mobile-responsive with CSS Grid/Flexbox.
65
+ 4. Smooth scroll + Intersection Observer animations.
66
+ 5. Navigation bar with all section links + hamburger for mobile.
67
+ 6. Sections: {', '.join(plan.get('sections', ['hero','about','services','contact']))}.
68
+ 7. Footer with copyright.
69
+ 8. Use theme CSS variables exactly as provided.
70
+ 9. Glass cards if glassmorphism theme. Particle/glow effects if gaming/neon theme.
71
+ 10. SEO meta tags: title, description, og:title, og:description.
72
+ 11. Modern premium look — NOT generic.
73
+ 12. Smooth hover effects on all buttons and cards.
74
+
75
+ Generate the complete HTML now:
76
+ """
77
+ try:
78
+ resp = self._model.generate_content(
79
+ prompt,
80
+ generation_config={"temperature": 0.7, "max_output_tokens": 8192}
81
+ )
82
+ html = resp.text.strip()
83
+ html = re.sub(r"^```(?:html)?\s*", "", html)
84
+ html = re.sub(r"\s*```$", "", html)
85
+ return html
86
+ except Exception as e:
87
+ print(f"[Generator] HTML failed: {e}")
88
+ return self._fallback_html(plan, content)
89
+
90
+ def generate_css(self, plan: dict) -> str:
91
+ tv = self._theme(plan.get("theme", "dark"))
92
+ return f"""/* MYRA WebForge — {plan.get('title','Website')} */
93
+ :root {{
94
+ --bg: {tv['bg']}; --bg2: {tv['bg2']}; --text: {tv['text']};
95
+ --accent: {tv['accent']}; --accent2: {tv['accent2']};
96
+ --card: {tv['card']}; --border: {tv['border']};
97
+ }}
98
+ * {{ margin:0; padding:0; box-sizing:border-box; }}
99
+ body {{ background:var(--bg); color:var(--text); font-family:'{plan.get('font_family','Segoe UI')}',sans-serif; }}
100
+ a {{ color:var(--accent); text-decoration:none; }}
101
+ """
102
+
103
+ def generate_js(self, plan: dict) -> str:
104
+ return """// MYRA WebForge Script
105
+ document.addEventListener('DOMContentLoaded', () => {
106
+ document.querySelectorAll('a[href^="#"]').forEach(a => {
107
+ a.addEventListener('click', e => {
108
+ e.preventDefault();
109
+ const el = document.querySelector(a.getAttribute('href'));
110
+ if (el) el.scrollIntoView({ behavior: 'smooth' });
111
+ });
112
+ });
113
+ const obs = new IntersectionObserver(entries => {
114
+ entries.forEach(e => { if (e.isIntersecting) e.target.classList.add('visible'); });
115
+ }, { threshold: 0.1 });
116
+ document.querySelectorAll('.animate').forEach(el => obs.observe(el));
117
+ const tog = document.querySelector('.nav-toggle');
118
+ const nav = document.querySelector('.nav-menu');
119
+ if (tog && nav) tog.addEventListener('click', () => nav.classList.toggle('open'));
120
+ });
121
+ """
122
+
123
+ def generate_readme(self, plan: dict, live_url: str = "") -> str:
124
+ return f"""# {plan.get('title', plan.get('project_name','Website'))}
125
+ > {plan.get('tagline','Built with MYRA WebForge AI')}
126
+
127
+ ## 🚀 Live URL
128
+ {live_url if live_url else '_Deploying..._'}
129
+
130
+ ## About
131
+ - **Type:** {plan.get('website_type','website')}
132
+ - **Theme:** {plan.get('theme','dark')}
133
+ - **Stack:** {plan.get('tech_stack','HTML, CSS, JS')}
134
+
135
+ _Generated by MYRA WebForge AI_
136
+ """
137
+
138
+ def _fallback_html(self, plan: dict, content: dict) -> str:
139
+ tv = self._theme(plan.get("theme", "dark"))
140
+ title = plan.get("title", "My Website")
141
+ tagline = plan.get("tagline", "Built with MYRA WebForge")
142
+ hero = content.get("hero", {})
143
+ heading = hero.get("heading", title)
144
+ sub = hero.get("subheading", tagline)
145
+ cta = hero.get("cta_primary", "Get Started")
146
+ svcs = content.get("services", {}).get("items", [])
147
+ svc_html = ""
148
+ for s in svcs:
149
+ svc_html += f'<div class="card"><div class="card-icon">{s.get("icon","⚡")}</div><h3>{s.get("title","")}</h3><p>{s.get("description","")}</p></div>'
150
+ contact = content.get("contact", {})
151
+ footer = content.get("footer", {})
152
+ seo = content.get("seo", {})
153
+ return f"""<!DOCTYPE html>
154
+ <html lang="en">
155
+ <head>
156
+ <meta charset="UTF-8">
157
+ <meta name="viewport" content="width=device-width,initial-scale=1.0">
158
+ <title>{seo.get('meta_title', title)}</title>
159
+ <meta name="description" content="{seo.get('meta_description', tagline)}">
160
+ <meta property="og:title" content="{seo.get('og_title', title)}">
161
+ <meta property="og:description" content="{seo.get('og_description', tagline)}">
162
+ <style>
163
+ :root{{--bg:{tv['bg']};--bg2:{tv['bg2']};--text:{tv['text']};--accent:{tv['accent']};--accent2:{tv['accent2']};--card:{tv['card']};--border:{tv['border']};}}
164
+ *{{margin:0;padding:0;box-sizing:border-box;}}
165
+ body{{background:var(--bg);color:var(--text);font-family:'{plan.get('font_family','Segoe UI')}',sans-serif;}}
166
+ nav{{position:fixed;top:0;width:100%;background:var(--bg2);border-bottom:1px solid var(--border);padding:1rem 2rem;display:flex;justify-content:space-between;align-items:center;z-index:100;}}
167
+ nav .brand{{font-size:1.4rem;font-weight:bold;color:var(--accent);}}
168
+ nav ul{{display:flex;gap:2rem;list-style:none;}}
169
+ nav a{{color:var(--text);opacity:0.8;transition:opacity 0.3s;}}
170
+ nav a:hover{{opacity:1;color:var(--accent);}}
171
+ section{{padding:6rem 2rem;max-width:1100px;margin:0 auto;}}
172
+ #hero{{text-align:center;min-height:100vh;display:flex;flex-direction:column;align-items:center;justify-content:center;padding-top:5rem;}}
173
+ #hero h1{{font-size:clamp(2rem,5vw,4rem);font-weight:900;margin-bottom:1.5rem;background:linear-gradient(135deg,var(--accent),var(--accent2));-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text;}}
174
+ #hero p{{font-size:1.2rem;opacity:0.75;max-width:600px;margin:0 auto 2.5rem;line-height:1.7;}}
175
+ .btn{{display:inline-block;padding:0.9rem 2.5rem;background:var(--accent);color:#fff;border-radius:50px;font-weight:bold;font-size:1rem;transition:all 0.3s;margin:0.4rem;}}
176
+ .btn:hover{{transform:translateY(-2px);box-shadow:0 8px 25px rgba(0,0,0,0.3);opacity:0.9;}}
177
+ .btn-outline{{background:transparent;border:2px solid var(--accent);color:var(--accent);}}
178
+ h2{{font-size:2.2rem;font-weight:800;margin-bottom:3rem;text-align:center;}}
179
+ h2 span{{color:var(--accent);}}
180
+ .grid{{display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:1.8rem;}}
181
+ .card{{background:var(--card);border:1px solid var(--border);border-radius:16px;padding:2rem;transition:transform 0.3s,box-shadow 0.3s;}}
182
+ .card:hover{{transform:translateY(-6px);box-shadow:0 20px 40px rgba(0,0,0,0.2);}}
183
+ .card-icon{{font-size:2.5rem;margin-bottom:1rem;}}
184
+ .card h3{{font-size:1.1rem;font-weight:700;margin-bottom:0.6rem;}}
185
+ .card p{{opacity:0.7;line-height:1.6;font-size:0.95rem;}}
186
+ #contact{{text-align:center;}}
187
+ .contact-info{{display:flex;flex-wrap:wrap;justify-content:center;gap:2rem;margin-top:2rem;}}
188
+ .contact-item{{background:var(--card);border:1px solid var(--border);border-radius:12px;padding:1.5rem 2rem;}}
189
+ footer{{background:var(--bg2);border-top:1px solid var(--border);text-align:center;padding:2.5rem;opacity:0.7;margin-top:4rem;}}
190
+ @media(max-width:768px){{nav ul{{display:none;}}}}
191
+ .animate{{opacity:0;transform:translateY(24px);transition:opacity 0.6s,transform 0.6s;}}
192
+ .animate.visible{{opacity:1;transform:translateY(0);}}
193
+ </style>
194
+ </head>
195
+ <body>
196
+ <nav>
197
+ <span class="brand">{title}</span>
198
+ <ul>
199
+ <li><a href="#about">About</a></li>
200
+ <li><a href="#services">Services</a></li>
201
+ <li><a href="#contact">Contact</a></li>
202
+ </ul>
203
+ </nav>
204
+ <section id="hero">
205
+ <h1>{heading}</h1>
206
+ <p>{sub}</p>
207
+ <div>
208
+ <a class="btn" href="#services">{cta}</a>
209
+ <a class="btn btn-outline" href="#contact">Contact Us</a>
210
+ </div>
211
+ </section>
212
+ <section id="about" class="animate">
213
+ <h2>About <span>Us</span></h2>
214
+ <p style="text-align:center;opacity:0.75;max-width:700px;margin:0 auto;line-height:1.8;">{content.get('about',{}).get('description','We create amazing digital experiences.')}</p>
215
+ </section>
216
+ <section id="services">
217
+ <h2>What We <span>Offer</span></h2>
218
+ <div class="grid">{svc_html}</div>
219
+ </section>
220
+ <section id="contact" class="animate">
221
+ <h2>Get In <span>Touch</span></h2>
222
+ <p style="opacity:0.7;margin-bottom:2rem;">{contact.get('subheading','We would love to hear from you')}</p>
223
+ <div class="contact-info">
224
+ <div class="contact-item">📧 {contact.get('email','hello@myra.ai')}</div>
225
+ <div class="contact-item">📞 {contact.get('phone','+1 (555) 000-0000')}</div>
226
+ <div class="contact-item">📍 {contact.get('address','123 Main Street')}</div>
227
+ </div>
228
+ </section>
229
+ <footer>
230
+ <p>{footer.get('tagline','Built with MYRA WebForge AI')}</p>
231
+ <p style="margin-top:0.5rem;font-size:0.85rem;">{footer.get('copyright',f'© 2025 {title}. All rights reserved.')}</p>
232
+ </footer>
233
+ <script>
234
+ document.addEventListener('DOMContentLoaded', () => {{
235
+ document.querySelectorAll('a[href^="#"]').forEach(a => {{
236
+ a.addEventListener('click', e => {{
237
+ e.preventDefault();
238
+ const el = document.querySelector(a.getAttribute('href'));
239
+ if (el) el.scrollIntoView({{ behavior: 'smooth' }});
240
+ }});
241
+ }});
242
+ const obs = new IntersectionObserver(entries => {{
243
+ entries.forEach(e => {{ if (e.isIntersecting) e.target.classList.add('visible'); }});
244
+ }}, {{ threshold: 0.1 }});
245
+ document.querySelectorAll('.animate').forEach(el => obs.observe(el));
246
+ }});
247
+ </script>
248
+ </body>
249
+ </html>"""
@@ -0,0 +1,175 @@
1
+ from aria_modules.gemini_rest import FallbackModel
2
+ # -*- coding: utf-8 -*-
3
+ """MYRA WebForge — Planner Agent"""
4
+
5
+ import json
6
+ import re
7
+
8
+ _HAS_GENAI = False
9
+ try:
10
+ try:
11
+ import google.genai as genai
12
+ except Exception:
13
+ import google.generativeai as genai
14
+ from website_builder.config import GEMINI_API_KEY
15
+ try:
16
+ # configure if available (some libs may not need this)
17
+ if hasattr(genai, 'configure'):
18
+ genai.configure(api_key=GEMINI_API_KEY)
19
+ except Exception:
20
+ # non-fatal: proceed without configured client
21
+ pass
22
+ _HAS_GENAI = True
23
+ except Exception:
24
+ _HAS_GENAI = False
25
+
26
+ WEBSITE_KEYWORDS = [
27
+ "website", "site", "webpage", "web page", "landing page",
28
+ "portfolio", "blog", "store", "shop", "ecommerce", "e-commerce",
29
+ "deploy", "publish", "preview", "github", "vercel", "hosting",
30
+ "create web", "build web", "make web", "generate web",
31
+ "update website", "edit website", "add section", "change theme",
32
+ ]
33
+
34
+ ACTION_PATTERNS = {
35
+ "create": ["create", "make", "build", "generate", "banao", "bana", "likho"],
36
+ "update": ["update", "edit", "change", "modify", "add section", "improve", "badlo"],
37
+ "preview": ["preview", "open", "show", "dekho", "local"],
38
+ "deploy": ["deploy", "publish", "live", "push", "upload"],
39
+ "status": ["status", "info", "url", "link", "check"],
40
+ }
41
+
42
+ THEME_MAP = {
43
+ "dark": "dark",
44
+ "light": "light",
45
+ "glass": "glassmorphism",
46
+ "glassmorphism": "glassmorphism",
47
+ "gaming": "gaming",
48
+ "neon": "futuristic_neon",
49
+ "futuristic": "futuristic_neon",
50
+ "corporate": "corporate",
51
+ "portfolio": "portfolio",
52
+ "ai": "ai_startup",
53
+ "startup": "ai_startup",
54
+ "minimal": "light",
55
+ }
56
+
57
+ TYPE_MAP = {
58
+ "portfolio": "portfolio",
59
+ "gaming": "gaming",
60
+ "game": "gaming",
61
+ "ai": "ai_startup",
62
+ "startup": "ai_startup",
63
+ "business": "business",
64
+ "ecommerce": "ecommerce",
65
+ "store": "ecommerce",
66
+ "shop": "ecommerce",
67
+ "blog": "blog",
68
+ "landing": "landing",
69
+ "saas": "saas",
70
+ }
71
+
72
+
73
+ class PlannerAgent:
74
+ def __init__(self):
75
+ # Some SDKs (google.genai) may have a different API surface.
76
+ # Only instantiate if the loaded module exposes the expected class.
77
+ if True: # REST fallback model — SDK pe depend nahi
78
+ try:
79
+ self._model = FallbackModel() # Gemini REST + model fallback chain
80
+ except Exception:
81
+ self._model = None
82
+ else:
83
+ self._model = None
84
+
85
+ def is_webforge_command(self, command: str) -> bool:
86
+ cmd = command.lower()
87
+ return any(kw in cmd for kw in WEBSITE_KEYWORDS)
88
+
89
+ def detect_action(self, command: str) -> str:
90
+ cmd = command.lower()
91
+ for action, keywords in ACTION_PATTERNS.items():
92
+ if any(kw in cmd for kw in keywords):
93
+ return action
94
+ return "create"
95
+
96
+ def detect_type(self, command: str) -> str:
97
+ cmd = command.lower()
98
+ for key, val in TYPE_MAP.items():
99
+ if key in cmd:
100
+ return val
101
+ return "portfolio"
102
+
103
+ def detect_theme(self, command: str) -> str:
104
+ cmd = command.lower()
105
+ for key, val in THEME_MAP.items():
106
+ if key in cmd:
107
+ return val
108
+ defaults = {"gaming": "gaming", "ai_startup": "ai_startup",
109
+ "ecommerce": "light", "portfolio": "dark"}
110
+ return defaults.get(self.detect_type(command), "dark")
111
+
112
+ def generate_project_name(self, command: str, wtype: str) -> str:
113
+ stop = {"create","make","build","generate","website","site","a","an","the",
114
+ "for","me","please","bana","banao","kar","do","mujhe","meri","mera","ek"}
115
+ words = re.sub(r"[^a-z0-9 ]", "", command.lower()).split()
116
+ parts = [w for w in words if w not in stop][:3]
117
+ return "-".join(parts) if parts else wtype + "-site"
118
+
119
+ def plan(self, command: str) -> dict:
120
+ action = self.detect_action(command)
121
+ wtype = self.detect_type(command)
122
+ theme = self.detect_theme(command)
123
+ name = self.generate_project_name(command, wtype)
124
+
125
+ if self._model:
126
+ prompt = f"""
127
+ You are a website planner AI. Based on this user command, create a structured JSON plan.
128
+ User command: "{command}"
129
+ Detected type: {wtype}, theme: {theme}
130
+
131
+ Return ONLY valid JSON (no markdown):
132
+ {{
133
+ "project_name": "{name}",
134
+ "website_type": "{wtype}",
135
+ "theme": "{theme}",
136
+ "pages": ["index"],
137
+ "sections": ["hero","about","services","contact"],
138
+ "title": "Website title",
139
+ "tagline": "Short tagline",
140
+ "primary_color": "#hex",
141
+ "secondary_color": "#hex",
142
+ "font_family": "font name",
143
+ "features": ["responsive","animations","SEO"],
144
+ "tech_stack": "html_css_js"
145
+ }}
146
+ """
147
+ try:
148
+ resp = self._model.generate_content(prompt)
149
+ raw = re.sub(r"```(?:json)?|```", "", resp.text.strip()).strip()
150
+ plan = json.loads(raw)
151
+ except Exception as e:
152
+ print(f"[Planner] AI failed ({e}), using defaults")
153
+ plan = self._default_plan(name, wtype, theme)
154
+ else:
155
+ plan = self._default_plan(name, wtype, theme)
156
+
157
+ plan["action"] = action
158
+ plan["user_command"] = command
159
+ return plan
160
+
161
+ def _default_plan(self, name, wtype, theme) -> dict:
162
+ return {
163
+ "project_name": name,
164
+ "website_type": wtype,
165
+ "theme": theme,
166
+ "pages": ["index"],
167
+ "sections": ["hero","about","services","contact"],
168
+ "title": name.replace("-"," ").title(),
169
+ "tagline": "Built with MYRA WebForge AI",
170
+ "primary_color": "#0055FF",
171
+ "secondary_color":"#00DDFF",
172
+ "font_family": "Segoe UI",
173
+ "features": ["responsive","animations","SEO"],
174
+ "tech_stack": "html_css_js",
175
+ }
@@ -0,0 +1,48 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ MYRA WebForge — Configuration
4
+ Loads all keys from .env — NO hardcoding ever.
5
+ """
6
+ import os
7
+ from pathlib import Path
8
+
9
+ try:
10
+ from dotenv import load_dotenv
11
+ # Explicit path only - never bare load_dotenv(). Bare load_dotenv()
12
+ # calls find_dotenv(), which walks the call stack via frame.f_back
13
+ # to guess the caller's directory. When MYRA runs from the packaged
14
+ # myra.exe launcher (a zipapp-style entry point), that stack is too
15
+ # shallow and find_dotenv() raises a bare `AssertionError` (empty
16
+ # message) - which isn't an ImportError, so it wasn't caught here,
17
+ # and broke `import website_builder` (and anything importing it,
18
+ # e.g. aria.py) for every pip-installed user.
19
+ _env_path = Path.cwd() / ".env"
20
+ if not _env_path.exists():
21
+ _env_path = Path(__file__).resolve().parents[1] / ".env"
22
+ if _env_path.exists():
23
+ load_dotenv(_env_path)
24
+ except ImportError:
25
+ pass # dotenv optional — keys can come from environment directly
26
+
27
+ # ── API Keys (from .env only) ──────────────────────────────────
28
+ GEMINI_API_KEY = os.getenv("GEMINI_API_KEY", "")
29
+ GITHUB_TOKEN = os.getenv("GITHUB_TOKEN", "")
30
+ GITHUB_USERNAME = os.getenv("GITHUB_USERNAME", "")
31
+ VERCEL_TOKEN = os.getenv("VERCEL_TOKEN", "")
32
+ VERCEL_TEAM_ID = os.getenv("VERCEL_TEAM_ID", "")
33
+
34
+ # ── Paths ──────────────────────────────────────────────────────
35
+ PROJECTS_DIR = Path(os.getenv("PROJECTS_DIR", "Projects"))
36
+ PROJECTS_DIR.mkdir(exist_ok=True)
37
+ DB_PATH = PROJECTS_DIR / "webforge_projects.json"
38
+
39
+ # ── Deploy provider ────────────────────────────────────────────
40
+ DEFAULT_DEPLOY_PROVIDER = os.getenv("DEFAULT_DEPLOY_PROVIDER", "vercel")
41
+
42
+ def validate_keys() -> dict:
43
+ missing = []
44
+ if not GEMINI_API_KEY: missing.append("GEMINI_API_KEY")
45
+ if not GITHUB_TOKEN: missing.append("GITHUB_TOKEN")
46
+ if not GITHUB_USERNAME: missing.append("GITHUB_USERNAME")
47
+ if not VERCEL_TOKEN: missing.append("VERCEL_TOKEN")
48
+ return {"ok": len(missing) == 0, "missing": missing}
@@ -0,0 +1 @@
1
+ # website_builder database package
@@ -0,0 +1,47 @@
1
+ # -*- coding: utf-8 -*-
2
+ """MYRA WebForge — Projects Database (JSON-backed)"""
3
+
4
+ import json
5
+ import threading
6
+ from datetime import datetime
7
+ from website_builder.config import DB_PATH
8
+
9
+
10
+ class ProjectsDB:
11
+ _lock = threading.Lock()
12
+
13
+ def __init__(self):
14
+ self._path = DB_PATH
15
+ self._data = self._load()
16
+
17
+ def _load(self) -> dict:
18
+ if self._path.exists():
19
+ try:
20
+ return json.loads(self._path.read_text(encoding="utf-8"))
21
+ except Exception:
22
+ pass
23
+ return {}
24
+
25
+ def _save(self):
26
+ self._path.write_text(
27
+ json.dumps(self._data, indent=2, ensure_ascii=False),
28
+ encoding="utf-8"
29
+ )
30
+
31
+ def get(self, project_name: str):
32
+ with self._lock:
33
+ return self._data.get(project_name)
34
+
35
+ def save_project(self, project_name: str, info: dict):
36
+ with self._lock:
37
+ existing = self._data.get(project_name, {})
38
+ existing.update(info)
39
+ existing["updated_at"] = datetime.now().isoformat()
40
+ if "created_at" not in existing:
41
+ existing["created_at"] = existing["updated_at"]
42
+ self._data[project_name] = existing
43
+ self._save()
44
+
45
+ def all_projects(self) -> list:
46
+ with self._lock:
47
+ return list(self._data.values())
@@ -0,0 +1 @@
1
+ # website_builder deploy package
@@ -0,0 +1,67 @@
1
+ # -*- coding: utf-8 -*-
2
+ """MYRA WebForge — GitHub Service"""
3
+
4
+ import base64, requests
5
+ from pathlib import Path
6
+ from website_builder.config import GITHUB_TOKEN, GITHUB_USERNAME
7
+
8
+ BASE = "https://api.github.com"
9
+
10
+
11
+ class GitHubService:
12
+ def __init__(self):
13
+ self._h = {
14
+ "Authorization": f"token {GITHUB_TOKEN}",
15
+ "Accept": "application/vnd.github.v3+json",
16
+ "Content-Type": "application/json",
17
+ }
18
+
19
+ def repo_exists(self, repo: str) -> bool:
20
+ r = requests.get(f"{BASE}/repos/{GITHUB_USERNAME}/{repo}", headers=self._h, timeout=30)
21
+ return r.status_code == 200
22
+
23
+ def create_repo(self, repo: str, desc: str = "") -> dict:
24
+ data = {"name": repo, "description": desc or f"MYRA WebForge — {repo}",
25
+ "private": False, "auto_init": False}
26
+ r = requests.post(f"{BASE}/user/repos", headers=self._h, json=data, timeout=30)
27
+ if r.status_code in (201, 422):
28
+ return {"ok": True, "url": f"https://github.com/{GITHUB_USERNAME}/{repo}"}
29
+ return {"ok": False, "error": r.text[:200]}
30
+
31
+ def _sha(self, repo: str, path: str):
32
+ r = requests.get(f"{BASE}/repos/{GITHUB_USERNAME}/{repo}/contents/{path}",
33
+ headers=self._h, timeout=30)
34
+ return r.json().get("sha") if r.status_code == 200 else None
35
+
36
+ def push_file(self, repo: str, path: str, content, msg: str) -> bool:
37
+ if isinstance(content, str): content = content.encode("utf-8")
38
+ b64 = base64.b64encode(content).decode()
39
+ sha = self._sha(repo, path)
40
+ data = {"message": msg, "content": b64}
41
+ if sha: data["sha"] = sha
42
+ r = requests.put(f"{BASE}/repos/{GITHUB_USERNAME}/{repo}/contents/{path}",
43
+ headers=self._h, json=data, timeout=30)
44
+ return r.status_code in (200, 201)
45
+
46
+ def push_project(self, repo: str, project_dir: Path) -> dict:
47
+ if not GITHUB_TOKEN or not GITHUB_USERNAME:
48
+ return {"ok": False, "error": "GITHUB_TOKEN or GITHUB_USERNAME missing in .env"}
49
+ if not self.repo_exists(repo):
50
+ res = self.create_repo(repo)
51
+ if not res["ok"]: return res
52
+
53
+ msg = f"MYRA WebForge update: {repo}"
54
+ pushed = []
55
+ failed = []
56
+ for fp in project_dir.rglob("*"):
57
+ if not fp.is_file(): continue
58
+ rel = fp.relative_to(project_dir).as_posix()
59
+ try:
60
+ ok = self.push_file(repo, rel, fp.read_bytes(), msg)
61
+ (pushed if ok else failed).append(rel)
62
+ except Exception as e:
63
+ failed.append(f"{rel}({e})")
64
+
65
+ url = f"https://github.com/{GITHUB_USERNAME}/{repo}"
66
+ print(f"[GitHub] Pushed {len(pushed)} files → {url}")
67
+ return {"ok": len(pushed) > 0, "github_url": url, "pushed": len(pushed), "failed": failed}