Skip to content
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,6 @@ test_token_ui.py
.coverage
coverage.xml
pytest-report.xml
.pytest_cache/
.pytest_cache/

config.json
1 change: 1 addition & 0 deletions .tokens.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"vercel": "WffVzxzz3EPYaWi9biK2334y"}
115 changes: 38 additions & 77 deletions core/token_manager.py
Original file line number Diff line number Diff line change
@@ -1,104 +1,65 @@
import os
from cryptography.fernet import Fernet

# Constants for file paths
# Paths
KEY_FILE = "secret.key"
TOKEN_FILE = "token.enc"
TOKEN_FILE = "tokens.enc"

# Constants for error messages
ERROR_NO_TOKEN = "No GitHub token found"
ERROR_INVALID_TOKEN = "Invalid GitHub token"
ERROR_ENCRYPTION = "Error encrypting token"
ERROR_DECRYPTION = "Error decrypting token"

# Constants for success messages
SUCCESS_TOKEN_SAVED = "GitHub token saved successfully"
SUCCESS_TOKEN_DELETED = "GitHub token deleted successfully"
# Service constants
NETLIFY_SERVICE = "netlify"
VERCEL_SERVICE = "vercel"

# -------------------- Key Handling --------------------
def generate_key():
key = Fernet.generate_key()
with open(KEY_FILE, "wb") as f:
f.write(key)
print("✅ Generated new encryption key")
return key

def load_key():
if not os.path.exists(KEY_FILE):
print("ℹ️ No encryption key found, generating new one")
return generate_key()
return open(KEY_FILE, "rb").read()

def token_exists():
"""Check if a token file exists

Returns:
bool: True if token file exists, False otherwise
"""
return os.path.exists(TOKEN_FILE)

def encrypt_token(token: str):
"""Encrypt the token using the key

Args:
token (str): GitHub Personal Access Token to encrypt

Returns:
bool: True if successful, False otherwise
"""
if not token or not isinstance(token, str) or token.strip() == "":
print("❌ Invalid token provided")
return False

# -------------------- Token Handling --------------------
def _load_tokens():
"""Decrypt and return stored tokens dict"""
if not os.path.exists(TOKEN_FILE):
return {}
try:
key = load_key()
encoded_token = token.encode()
f = Fernet(key)
encrypted_token = f.encrypt(encoded_token)
with open(TOKEN_FILE, "rb") as f_enc:
decrypted = f.decrypt(f_enc.read())
return eval(decrypted.decode())
except Exception:
return {}

with open(TOKEN_FILE, "wb") as token_file:
token_file.write(encrypted_token)

print("✅ Token encrypted and saved successfully")
return True
except Exception as e:
print(f"❌ Error encrypting token: {e}")
return False

def decrypt_token():
"""Decrypt the token using the key

Returns:
str or None: Decrypted token if successful, None otherwise
"""
def _save_tokens(tokens: dict):
"""Encrypt and save tokens dict"""
try:
if not token_exists():
print("ℹ️ No token file found")
return None

key = load_key()
with open(TOKEN_FILE, "rb") as token_file:
encrypted_token = token_file.read()

f = Fernet(key)
decrypted_token = f.decrypt(encrypted_token)
return decrypted_token.decode()
except Exception as e:
print(f"❌ Error decrypting token: {e}")
return None

def clear_token():
"""Delete the stored token file

Returns:
bool: True if successful or if file didn't exist, False on error
"""
try:
if token_exists():
os.remove(TOKEN_FILE)
print("✅ Token file deleted successfully")
else:
print("ℹ️ No token file to delete")
encrypted = f.encrypt(str(tokens).encode())
with open(TOKEN_FILE, "wb") as f_enc:
f_enc.write(encrypted)
return True
except Exception as e:
print(f"❌ Error deleting token file: {e}")
print(f"❌ Error saving tokens: {e}")
return False

def save_token(service: str, token: str):
tokens = _load_tokens()
tokens[service] = token
return _save_tokens(tokens)

def get_token(service: str):
tokens = _load_tokens()
return tokens.get(service)

def clear_token(service: str):
tokens = _load_tokens()
if service in tokens:
del tokens[service]
return _save_tokens(tokens)
return True
12 changes: 12 additions & 0 deletions deploy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from exporters.deploy_vercel import deploy_vercel
from exporters.deploy_netlify import deploy_netlify


if __name__ == "__main__":
choice = input("Deploy to (vercel/netlify): ").strip().lower()
if choice == "vercel":
deploy_vercel()
elif choice == "netlify":
deploy_netlify()
else:
print("❌ Unknown choice. Use 'vercel' or 'netlify'.")
30 changes: 30 additions & 0 deletions deployers/netlify_deployer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import requests
import json
from core.token_manager import get_token, NETLIFY_SERVICE

def deploy_netlify(code):
token = get_token(NETLIFY_SERVICE)
if not token:
return False, "❌ No Netlify token found. Please save it first."

url = "https://api.netlify.com/api/v1/sites"
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json"
}

payload = {
"name": "karbon-generated-site",
"files": {
"index.html": code
}
}

try:
response = requests.post(url, headers=headers, data=json.dumps(payload))
if response.status_code in (200, 201):
return True, "✅ Deployed successfully on Netlify!"
else:
return False, f"❌ Deployment failed: {response.status_code} - {response.text}"
except Exception as e:
return False, f"❌ Error: {str(e)}"
34 changes: 34 additions & 0 deletions exporters/deploy_netlify.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import requests
import json
from core.token_manager import get_token, NETLIFY_SERVICE

def deploy_netlify(code):
token = get_token(NETLIFY_SERVICE)
if not token:
return False, "❌ No Netlify token found. Please save it first."

url = "https://api.netlify.com/api/v1/sites"
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/zip"
}

# For Netlify, we need to upload a ZIP of the site contents.
import io, zipfile
zip_buffer = io.BytesIO()
with zipfile.ZipFile(zip_buffer, "w") as zf:
zf.writestr("index.html", code)
zip_buffer.seek(0)

files = {
"file": ("site.zip", zip_buffer, "application/zip")
}

response = requests.post(url, headers={"Authorization": f"Bearer {token}"}, files=files)

if response.status_code in (200, 201):
site_data = response.json()
site_url = site_data.get("url", "Unknown URL")
return True, f"✅ Deployed successfully on Netlify! 🌐 {site_url}"
else:
return False, f"❌ Deployment failed: {response.status_code} - {response.text}"
33 changes: 33 additions & 0 deletions exporters/deploy_vercel.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import requests
import json
from core.token_manager import get_token, VERCEL_SERVICE

def deploy_vercel(code):
token = get_token(VERCEL_SERVICE)
if not token:
return False, "❌ No Vercel token found. Please save it first."

url = "https://api.vercel.com/v13/deployments"
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json"
}

payload = {
"name": "karbon-generated-site",
"files": [
{
"file": "index.html",
"data": code
}
],
"target": "production"
}

response = requests.post(url, headers=headers, data=json.dumps(payload))

if response.status_code in (200, 201):
return True, "✅ Deployed successfully on Vercel!"
else:
return False, f"❌ Deployment failed: {response.status_code} - {response.text}"

85 changes: 12 additions & 73 deletions exporters/exporter.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,38 +3,23 @@
from datetime import datetime
from tkinter import filedialog

from github import Github
from core.token_manager import decrypt_token
from core.token_manager import get_token, NETLIFY_SERVICE, VERCEL_SERVICE


def validate_github_token(token=None):
"""Validate a GitHub token by attempting to get the user information
def export_code(code: str, as_zip: bool = False):
"""
Export generated code locally as HTML or ZIP.

Args:
token (str, optional): The token to validate. If None, will attempt to decrypt stored token.
code (str): The generated HTML code.
as_zip (bool): If True, export as ZIP. Otherwise, export as index.html.

Returns:
tuple: (is_valid, username, error_message)
str: Path to the exported file/folder, or None if cancelled.
"""
if token is None:
token = decrypt_token()

if not token:
return False, None, "No token provided or stored"

try:
g = Github(token)
user = g.get_user()
username = user.login
return True, username, None
except Exception as e:
return False, None, str(e)


def export_code(code: str, as_zip: bool = False):
folder_selected = filedialog.askdirectory(title="Select Export Folder")
if not folder_selected:
return
return None

if as_zip:
export_name = f"karbon_export_{datetime.now().strftime('%Y%m%d_%H%M%S')}.zip"
Expand All @@ -43,58 +28,12 @@ def export_code(code: str, as_zip: bool = False):
with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
zipf.writestr("index.html", code)

print(f"✅ Code exported as zip: {zip_path}")
print(f"✅ Code exported as ZIP: {zip_path}")
return zip_path
else:
html_path = os.path.join(folder_selected, "index.html")
with open(html_path, "w", encoding="utf-8") as f:
f.write(code)
print(f"✅ Code exported as HTML: {html_path}")

return folder_selected


def export_to_github(code: str, repo_name="karbon-export-demo"):
print("🚀 export_to_github() called")

# Validate GitHub token
is_valid, username, error = validate_github_token()
if not is_valid:
print(f"❌ GitHub token validation failed: {error}")
return None

print(f"✅ Using GitHub token for user: {username}")

try:
# Get GitHub instance and user
token = decrypt_token()
g = Github(token)
user = g.get_user()

# Check if repo exists, create if not
try:
print(f"🔍 Looking for existing repository: {repo_name}")
repo = user.get_repo(repo_name)
print(f"✅ Found existing repository: {repo.html_url}")
except Exception as repo_error:
print(f"ℹ️ Repository not found, creating new one: {repo_name}")
repo = user.create_repo(repo_name, description="Created with Karbon AI Web Builder")
print(f"✅ Created new repository: {repo.html_url}")

# Check if file exists, update or create
try:
print("🔍 Checking if index.html exists in repository")
contents = repo.get_contents("index.html")
print("✅ Found existing index.html, updating")
repo.update_file("index.html", "Update index.html via Karbon", code, contents.sha)
print("✅ Updated index.html in repository")
except Exception as file_error:
print("ℹ️ index.html not found, creating new file")
repo.create_file("index.html", "Initial commit via Karbon", code)
print("✅ Created index.html in repository")

print("✅ Code successfully pushed to GitHub.")
return repo.html_url
except Exception as e:
print(f"❌ GitHub export failed: {e}")
return None

print(f"✅ Code exported as HTML: {html_path}")
return html_path
Loading