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.
- Aria_office_manage.py +1985 -0
- Main_normal.py +261 -0
- aria.py +2872 -0
- aria_modules/__init__.py +1 -0
- aria_modules/aria_activation.py +335 -0
- aria_modules/aria_advanced_features.py +1269 -0
- aria_modules/aria_advanced_tools.py +1099 -0
- aria_modules/assets/about/cofounder.png +0 -0
- aria_modules/assets/about/developer.png +0 -0
- aria_modules/assets/fonts/OFL-Poppins.txt +93 -0
- aria_modules/assets/fonts/Poppins-Bold.ttf +0 -0
- aria_modules/assets/fonts/Poppins-ExtraBold.ttf +0 -0
- aria_modules/assets/fonts/Poppins-Medium.ttf +0 -0
- aria_modules/assets/fonts/Poppins-Regular.ttf +0 -0
- aria_modules/assets/fonts/Poppins-SemiBold.ttf +0 -0
- aria_modules/assets/myra_girl.png +0 -0
- aria_modules/assets/myra_logo.png +0 -0
- aria_modules/assets/splash_bg.jpg +0 -0
- aria_modules/assets/ui_reference.png +0 -0
- aria_modules/cloud_database.py +10 -0
- aria_modules/game_install.py +1056 -0
- aria_modules/gemini_rest.py +102 -0
- aria_modules/haarcascade_frontalface_default.xml +33314 -0
- aria_modules/license_client.py +250 -0
- aria_modules/minibar_aria.py +355 -0
- aria_modules/tools.py +2295 -0
- aria_ui.py +818 -0
- camera_vision_module.py +1040 -0
- linux_extra.py +240 -0
- myra/__init__.py +10 -0
- myra/__main__.py +3 -0
- myra/cli.py +141 -0
- myra/config.py +22 -0
- myra_actions.py +327 -0
- myra_apps.py +846 -0
- myra_autostart.py +170 -0
- myra_coder_integration.py +641 -0
- myra_edge_glow.py +274 -0
- myra_float_orb.py +471 -0
- myra_guardian.py +181 -0
- myra_lang.py +133 -0
- myra_linux-0.1.0.dist-info/METADATA +99 -0
- myra_linux-0.1.0.dist-info/RECORD +80 -0
- myra_linux-0.1.0.dist-info/WHEEL +5 -0
- myra_linux-0.1.0.dist-info/entry_points.txt +3 -0
- myra_linux-0.1.0.dist-info/licenses/LICENSE +36 -0
- myra_linux-0.1.0.dist-info/top_level.txt +31 -0
- myra_linux_boot.py +207 -0
- myra_linux_tools.py +314 -0
- myra_pages.py +1246 -0
- myra_prefs.py +71 -0
- myra_quick_features.py +748 -0
- myra_slot.py +39 -0
- myra_splash.py +301 -0
- myra_spotify.py +316 -0
- myra_studio.py +916 -0
- myra_ui.py +1057 -0
- normal_prompt.py +41 -0
- prompt.py +252 -0
- screen_share_module.py +447 -0
- website_builder/__init__.py +5 -0
- website_builder/agents/__init__.py +1 -0
- website_builder/agents/content_agent.py +99 -0
- website_builder/agents/generator_agent.py +249 -0
- website_builder/agents/planner_agent.py +175 -0
- website_builder/config.py +48 -0
- website_builder/database/__init__.py +1 -0
- website_builder/database/projects_db.py +47 -0
- website_builder/deploy/__init__.py +1 -0
- website_builder/deploy/github_service.py +67 -0
- website_builder/deploy/qr_display.py +130 -0
- website_builder/deploy/vercel_service.py +71 -0
- website_builder/main.py +25 -0
- website_builder/preview/__init__.py +1 -0
- website_builder/preview/preview_server.py +47 -0
- website_builder/project/__init__.py +1 -0
- website_builder/project/project_manager.py +32 -0
- website_builder/webforge_integration.py +220 -0
- website_builder/webforge_manager.py +209 -0
- 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}
|