@hynaweb/stage 1.0.2 ā 1.0.5
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.
- package/Test#1/dummy.html +84 -0
- package/bin/cli.js +12 -1
- package/dashboard.html +224 -0
- package/dummy.html +85 -0
- package/hyna-deploy.zip +0 -0
- package/hyna-live.zip +0 -0
- package/index.html +120 -0
- package/index.js +128 -0
- package/logo.png +0 -0
- package/package.json +10 -11
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>HYNA-Stage Test</title>
|
|
7
|
+
<style>
|
|
8
|
+
body {
|
|
9
|
+
background-color: #030303;
|
|
10
|
+
color: #ffffff;
|
|
11
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
|
12
|
+
display: flex;
|
|
13
|
+
flex-direction: column;
|
|
14
|
+
align-items: center;
|
|
15
|
+
justify-content: center;
|
|
16
|
+
height: 100vh;
|
|
17
|
+
margin: 0;
|
|
18
|
+
overflow: hidden;
|
|
19
|
+
}
|
|
20
|
+
.glow {
|
|
21
|
+
position: absolute;
|
|
22
|
+
width: 300px;
|
|
23
|
+
height: 300px;
|
|
24
|
+
background: #3B82F6;
|
|
25
|
+
border-radius: 50%;
|
|
26
|
+
filter: blur(80px);
|
|
27
|
+
opacity: 0.3;
|
|
28
|
+
z-index: -1;
|
|
29
|
+
}
|
|
30
|
+
.card {
|
|
31
|
+
background: rgba(10, 10, 10, 0.8);
|
|
32
|
+
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
33
|
+
padding: 40px;
|
|
34
|
+
border-radius: 20px;
|
|
35
|
+
text-align: center;
|
|
36
|
+
backdrop-filter: blur(10px);
|
|
37
|
+
box-shadow: 0 10px 30px rgba(0,0,0,0.5);
|
|
38
|
+
}
|
|
39
|
+
h1 {
|
|
40
|
+
font-size: 2rem;
|
|
41
|
+
margin-bottom: 10px;
|
|
42
|
+
background: linear-gradient(to right, #60A5FA, #3B82F6);
|
|
43
|
+
-webkit-background-clip: text;
|
|
44
|
+
-webkit-text-fill-color: transparent;
|
|
45
|
+
}
|
|
46
|
+
p {
|
|
47
|
+
color: #9CA3AF;
|
|
48
|
+
margin-bottom: 20px;
|
|
49
|
+
}
|
|
50
|
+
.status {
|
|
51
|
+
display: inline-block;
|
|
52
|
+
padding: 6px 12px;
|
|
53
|
+
background: rgba(34, 197, 94, 0.1);
|
|
54
|
+
color: #22c55e;
|
|
55
|
+
border: 1px solid rgba(34, 197, 94, 0.2);
|
|
56
|
+
border-radius: 20px;
|
|
57
|
+
font-size: 0.85rem;
|
|
58
|
+
font-weight: bold;
|
|
59
|
+
}
|
|
60
|
+
.time {
|
|
61
|
+
margin-top: 20px;
|
|
62
|
+
font-family: monospace;
|
|
63
|
+
color: #6B7280;
|
|
64
|
+
font-size: 0.9rem;
|
|
65
|
+
}
|
|
66
|
+
</style>
|
|
67
|
+
</head>
|
|
68
|
+
<body>
|
|
69
|
+
<div class="glow"></div>
|
|
70
|
+
<div class="card">
|
|
71
|
+
<h1>š HYNA-Stage is LIVE</h1>
|
|
72
|
+
<p>This environment was provisioned in under 5 seconds.</p>
|
|
73
|
+
<div class="status">ā RUNNING</div>
|
|
74
|
+
<div class="time" id="clock">Loading time...</div>
|
|
75
|
+
</div>
|
|
76
|
+
|
|
77
|
+
<script>
|
|
78
|
+
setInterval(() => {
|
|
79
|
+
const now = new Date();
|
|
80
|
+
document.getElementById('clock').innerText = 'Server Time: ' + now.toLocaleTimeString();
|
|
81
|
+
}, 1000);
|
|
82
|
+
</script>
|
|
83
|
+
</body>
|
|
84
|
+
</html>
|
package/bin/cli.js
CHANGED
|
@@ -11,9 +11,20 @@ const SUPABASE_KEY = 'sb_publishable_sG0q8k6txKHOihBduyr67w_UKfolcvj';
|
|
|
11
11
|
const configPath = path.join(require('os').homedir(), '.hyna-stage');
|
|
12
12
|
|
|
13
13
|
async function getApiKey() {
|
|
14
|
+
// Check for --key argument first (Auto-Injection)
|
|
15
|
+
const keyArg = process.argv.find(arg => arg.startsWith('--key='));
|
|
16
|
+
if (keyArg) {
|
|
17
|
+
const key = keyArg.split('=')[1];
|
|
18
|
+
fs.writeFileSync(configPath, key.trim()); // Save it for next time
|
|
19
|
+
return key.trim();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Check cache
|
|
14
23
|
if (fs.existsSync(configPath)) {
|
|
15
24
|
return fs.readFileSync(configPath, 'utf8').trim();
|
|
16
25
|
}
|
|
26
|
+
|
|
27
|
+
// Otherwise prompt
|
|
17
28
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
18
29
|
return new Promise((resolve) => {
|
|
19
30
|
rl.question('š Enter your HYNA-Stage API Key: ', (key) => {
|
|
@@ -51,7 +62,7 @@ async function main() {
|
|
|
51
62
|
}
|
|
52
63
|
}
|
|
53
64
|
else if (command === 'down') {
|
|
54
|
-
const sandboxId = process.argv
|
|
65
|
+
const sandboxId = process.argv.find(arg => !arg.startsWith('--') && arg !== 'down' && arg !== 'up');
|
|
55
66
|
|
|
56
67
|
if (!sandboxId) {
|
|
57
68
|
console.error('ā Please provide a sandbox ID: hyna-stage down <sandbox_id>');
|
package/dashboard.html
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>HYNA-Stage | Dashboard</title>
|
|
7
|
+
<script src="https://cdn.jsdelivr.net/npm/@supabase/supabase-js@2"></script>
|
|
8
|
+
<script src="https://cdn.tailwindcss.com"></script>
|
|
9
|
+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;800&family=JetBrains+Mono&display=swap" rel="stylesheet">
|
|
10
|
+
<style>
|
|
11
|
+
body { background: #030303; color: #fff; font-family: "Inter", sans-serif; overflow-x: hidden; margin: 0; padding: 0; }
|
|
12
|
+
.bento-card { background: rgba(10, 10, 10, 0.7); border: 1px solid rgba(255,255,255,0.08); border-radius: 16px; backdrop-filter: blur(10px); }
|
|
13
|
+
.modal { display: none; position: fixed; inset: 0; background: rgba(0,0,0,0.8); backdrop-filter: blur(10px); z-index: 1000; align-items: center; justify-content: center; }
|
|
14
|
+
.modal.active { display: flex; }
|
|
15
|
+
|
|
16
|
+
/* Hardcoded Logo & Avatar Sizes */
|
|
17
|
+
|
|
18
|
+
.nav-logo {
|
|
19
|
+
width: 32px;
|
|
20
|
+
height: 32px;
|
|
21
|
+
object-fit: contain;
|
|
22
|
+
mix-blend-mode: screen; /* šŖ Makes dark pixels transparent */
|
|
23
|
+
margin-right: 8px;
|
|
24
|
+
vertical-align: middle;
|
|
25
|
+
}
|
|
26
|
+
.logo-container {
|
|
27
|
+
display: flex;
|
|
28
|
+
align-items: center;
|
|
29
|
+
justify-content: center;
|
|
30
|
+
gap: 10px;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.nav-avatar { width: 40px; height: 40px; border-radius: 50%; background: #3B82F6; color: #fff; display: flex; align-items: center; justify-content: center; font-weight: 800; font-size: 1rem; cursor: pointer; border: 2px solid transparent; transition: 0.2s; overflow: hidden; }
|
|
34
|
+
.nav-avatar:hover { border-color: #fff; box-shadow: 0 0 15px rgba(255,255,255,0.2); }
|
|
35
|
+
.nav-avatar img { width: 100%; height: 100%; object-fit: cover; display: none; }
|
|
36
|
+
|
|
37
|
+
.profile-avatar-large { width: 96px; height: 96px; border-radius: 50%; background: #3B82F6; color: #fff; display: flex; align-items: center; justify-content: center; font-weight: 800; font-size: 2rem; cursor: pointer; border: 4px solid rgba(255,255,255,0.1); transition: 0.2s; overflow: hidden; }
|
|
38
|
+
.profile-avatar-large:hover { border-color: rgba(255,255,255,0.3); }
|
|
39
|
+
.profile-avatar-large img { width: 100%; height: 100%; object-fit: cover; display: none; }
|
|
40
|
+
</style>
|
|
41
|
+
</head>
|
|
42
|
+
<body class="min-h-screen p-6 md:p-10">
|
|
43
|
+
|
|
44
|
+
<nav class="flex justify-between items-center border-b border-white/10 pb-4 mb-10 max-w-4xl mx-auto">
|
|
45
|
+
<div class="flex items-center gap-3 font-extrabold text-xl tracking-tighter">
|
|
46
|
+
<div class="logo-container"><img src="logo.png" class="nav-logo"> HYNA-Stage</div>
|
|
47
|
+
</div>
|
|
48
|
+
<div class="flex items-center gap-5">
|
|
49
|
+
<div class="nav-avatar" id="navAvatar" onclick="openProfile()">
|
|
50
|
+
<span id="navAvatarText">U</span>
|
|
51
|
+
<img id="navAvatarImg" src="">
|
|
52
|
+
</div>
|
|
53
|
+
<button onclick="logout()" class="text-red-500 font-semibold text-sm border border-red-500/30 px-4 py-2 rounded-lg hover:bg-red-500/10 transition">Log Out</button>
|
|
54
|
+
</div>
|
|
55
|
+
</nav>
|
|
56
|
+
|
|
57
|
+
<main class="max-w-4xl mx-auto">
|
|
58
|
+
<h1 class="text-3xl font-extrabold mb-8">Dashboard</h1>
|
|
59
|
+
|
|
60
|
+
<div class="grid grid-cols-2 gap-4 mb-8">
|
|
61
|
+
<div class="bento-card p-6 text-center">
|
|
62
|
+
<div class="text-3xl font-extrabold mb-1" id="statActive">0</div>
|
|
63
|
+
<div class="text-xs text-gray-500 uppercase font-semibold">Active Now</div>
|
|
64
|
+
</div>
|
|
65
|
+
<div class="bento-card p-6 text-center">
|
|
66
|
+
<div class="text-3xl font-extrabold mb-1 text-blue-500" id="statTotal">0</div>
|
|
67
|
+
<div class="text-xs text-gray-500 uppercase font-semibold">Total Deployed</div>
|
|
68
|
+
</div>
|
|
69
|
+
</div>
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
<div class="bento-card p-6 mb-8">
|
|
73
|
+
<div class="flex justify-between items-center mb-4">
|
|
74
|
+
<h2 class="text-sm font-bold text-gray-400 uppercase">CLI Quick Start</h2>
|
|
75
|
+
<span class="text-xs text-blue-400 bg-blue-500/10 px-2 py-1 rounded border border-blue-500/20">API Key Auto-Injected</span>
|
|
76
|
+
</div>
|
|
77
|
+
<div class="bg-black border border-white/10 rounded-lg p-4 font-mono text-sm text-gray-300 mb-4 overflow-x-auto">
|
|
78
|
+
<div class="mb-2 whitespace-nowrap"><span class="text-green-400">$</span> npm install -g @hynaweb/stage</div>
|
|
79
|
+
<div class="whitespace-nowrap"><span class="text-green-400">$</span> <span id="dynamicCmd">hyna-stage up</span></div>
|
|
80
|
+
</div>
|
|
81
|
+
<label class="text-xs text-gray-400 font-bold uppercase">Your API Key</label>
|
|
82
|
+
<div class="flex gap-2 mt-1">
|
|
83
|
+
<input type="text" id="apiKey" readonly onclick="this.select()" class="w-full bg-black border border-white/10 text-blue-500 p-3 rounded-lg font-mono text-sm focus:outline-none">
|
|
84
|
+
<button onclick="navigator.clipboard.writeText(document.getElementById('dynamicCmd').innerText); alert('Command Copied!')" class="bg-white text-black px-4 rounded-lg font-bold text-sm hover:scale-105 transition">Copy</button>
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
|
87
|
+
|
|
88
|
+
<div class="bento-card overflow-hidden">
|
|
89
|
+
<div class="p-5 border-b border-white/10"><h2 class="font-bold">Environments</h2></div>
|
|
90
|
+
<div id="envList" class="p-5 text-gray-500 text-sm">Loading...</div>
|
|
91
|
+
</div>
|
|
92
|
+
|
|
93
|
+
</main>
|
|
94
|
+
|
|
95
|
+
<div id="profileModal" class="modal">
|
|
96
|
+
<div class="bento-card p-8 w-full max-w-sm relative shadow-[0_0_50px_rgba(59,130,246,0.15)] text-center">
|
|
97
|
+
<button onclick="closeProfile()" class="absolute top-4 right-4 text-gray-500 hover:text-white text-2xl">×</button>
|
|
98
|
+
<h2 class="text-2xl font-extrabold mb-6">Profile Settings</h2>
|
|
99
|
+
|
|
100
|
+
<div class="relative inline-block mb-6">
|
|
101
|
+
<div class="profile-avatar-large mx-auto" onclick="document.getElementById('avatarUpload').click()">
|
|
102
|
+
<span id="modalAvatarText">U</span>
|
|
103
|
+
<img id="modalAvatarImg" src="">
|
|
104
|
+
</div>
|
|
105
|
+
<input type="file" id="avatarUpload" style="display:none;" accept="image/*">
|
|
106
|
+
<div class="text-xs text-blue-400 mt-2 cursor-pointer" onclick="document.getElementById('avatarUpload').click()">Change Picture</div>
|
|
107
|
+
</div>
|
|
108
|
+
|
|
109
|
+
<div class="text-left mb-4">
|
|
110
|
+
<label class="text-xs text-gray-400 font-bold uppercase">Full Name</label>
|
|
111
|
+
<input type="text" id="profileName" class="w-full bg-black border border-white/10 text-white p-3 rounded-lg mt-1 focus:outline-none focus:border-blue-500">
|
|
112
|
+
</div>
|
|
113
|
+
<div class="text-left mb-6">
|
|
114
|
+
<label class="text-xs text-gray-400 font-bold uppercase">Email</label>
|
|
115
|
+
<input type="email" id="profileEmail" disabled class="w-full bg-black border border-white/10 text-gray-500 p-3 rounded-lg mt-1 focus:outline-none">
|
|
116
|
+
</div>
|
|
117
|
+
<button onclick="saveProfile()" class="w-full bg-white text-black font-bold py-3 rounded-lg hover:scale-[1.02] transition">Save Changes</button>
|
|
118
|
+
</div>
|
|
119
|
+
</div>
|
|
120
|
+
|
|
121
|
+
<script>
|
|
122
|
+
const sb = supabase.createClient("https://mczesspakhkomlnmfstw.supabase.co", "sb_publishable_sG0q8k6txKHOihBduyr67w_UKfolcvj");
|
|
123
|
+
let currentUser = null;
|
|
124
|
+
|
|
125
|
+
async function init() {
|
|
126
|
+
let sess = await sb.auth.getSession();
|
|
127
|
+
if (!sess.data.session) { window.location.href = "/"; return; }
|
|
128
|
+
currentUser = sess.data.session.user;
|
|
129
|
+
|
|
130
|
+
let prof = await sb.from("profiles").select("*").eq("id", currentUser.id).single();
|
|
131
|
+
if (prof.data) {
|
|
132
|
+
document.getElementById("navAvatarText").innerText = prof.data.full_name.charAt(0).toUpperCase();
|
|
133
|
+
document.getElementById("modalAvatarText").innerText = prof.data.full_name.charAt(0).toUpperCase();
|
|
134
|
+
document.getElementById("profileName").value = prof.data.full_name;
|
|
135
|
+
document.getElementById("profileEmail").value = currentUser.email;
|
|
136
|
+
|
|
137
|
+
if (prof.data.avatar_url) {
|
|
138
|
+
document.getElementById("navAvatarImg").src = prof.data.avatar_url;
|
|
139
|
+
document.getElementById("navAvatarImg").style.display = "block";
|
|
140
|
+
document.getElementById("navAvatarText").style.display = "none";
|
|
141
|
+
document.getElementById("modalAvatarImg").src = prof.data.avatar_url;
|
|
142
|
+
document.getElementById("modalAvatarImg").style.display = "block";
|
|
143
|
+
document.getElementById("modalAvatarText").style.display = "none";
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
let keys = await sb.from("api_keys").select("key").eq("user_id", currentUser.id).single();
|
|
148
|
+
if (keys.data) {
|
|
149
|
+
document.getElementById("apiKey").value = keys.data.key;
|
|
150
|
+
// Auto-inject the key into the visible command
|
|
151
|
+
document.getElementById("dynamicCmd").innerHTML = `hyna-stage up --key=${keys.data.key}`;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
loadEnvs();
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function openProfile() { document.getElementById("profileModal").classList.add("active"); }
|
|
158
|
+
function closeProfile() { document.getElementById("profileModal").classList.remove("active"); }
|
|
159
|
+
|
|
160
|
+
document.getElementById("avatarUpload").addEventListener("change", async (e) => {
|
|
161
|
+
const file = e.target.files[0];
|
|
162
|
+
if (!file) return;
|
|
163
|
+
const fileName = currentUser.id + "-" + Date.now() + ".png";
|
|
164
|
+
|
|
165
|
+
document.getElementById("modalAvatarText").innerText = "...";
|
|
166
|
+
|
|
167
|
+
const { error: upErr } = await sb.storage.from("avatars").upload(fileName, file);
|
|
168
|
+
if (upErr) { alert("Upload failed: " + upErr.message); return; }
|
|
169
|
+
|
|
170
|
+
const { data: urlData } = sb.storage.from("avatars").getPublicUrl(fileName);
|
|
171
|
+
await sb.from("profiles").update({ avatar_url: urlData.publicUrl }).eq("id", currentUser.id);
|
|
172
|
+
|
|
173
|
+
document.getElementById("navAvatarImg").src = urlData.publicUrl;
|
|
174
|
+
document.getElementById("navAvatarImg").style.display = "block";
|
|
175
|
+
document.getElementById("navAvatarText").style.display = "none";
|
|
176
|
+
document.getElementById("modalAvatarImg").src = urlData.publicUrl;
|
|
177
|
+
document.getElementById("modalAvatarImg").style.display = "block";
|
|
178
|
+
document.getElementById("modalAvatarText").style.display = "none";
|
|
179
|
+
alert("Profile picture updated!");
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
async function saveProfile() {
|
|
183
|
+
const name = document.getElementById("profileName").value;
|
|
184
|
+
await sb.from("profiles").update({ full_name: name }).eq("id", currentUser.id);
|
|
185
|
+
alert("Profile saved!");
|
|
186
|
+
closeProfile();
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
async function loadEnvs() {
|
|
190
|
+
let res = await sb.from("environments").select("*").eq("user_id", currentUser.id).order("created_at", { ascending: false });
|
|
191
|
+
let active = res.data.filter(e => e.status === "running");
|
|
192
|
+
document.getElementById("statActive").innerText = active.length;
|
|
193
|
+
document.getElementById("statTotal").innerText = res.data.length;
|
|
194
|
+
|
|
195
|
+
let list = document.getElementById("envList");
|
|
196
|
+
if (res.data.length === 0) { list.innerHTML = "No environments found. Run <code>hyna-stage up</code> in your terminal."; return; }
|
|
197
|
+
|
|
198
|
+
let htmlStr = "";
|
|
199
|
+
for (let i = 0; i < res.data.length; i++) {
|
|
200
|
+
let e = res.data[i];
|
|
201
|
+
let isRunning = e.status === "running";
|
|
202
|
+
let btn = isRunning ? '<button onclick="destroy(\'' + e.sandbox_id + '\')" class="text-red-500 border border-red-500/30 px-3 py-1 rounded-md text-xs font-bold hover:bg-red-500/10 ml-auto">Destroy</button>' : '<span class="text-green-500 text-xs font-bold ml-auto">Saved</span>';
|
|
203
|
+
let url = isRunning ? '<a href="' + e.url + '" target="_blank" class="text-blue-500 font-mono text-sm hover:underline block mt-1">' + e.url + '</a>' : '<span class="text-gray-600 text-sm block mt-1">Destroyed</span>';
|
|
204
|
+
|
|
205
|
+
htmlStr += '<div class="flex items-center justify-between py-4 border-b border-white/10 last:border-0">';
|
|
206
|
+
htmlStr += '<div><div class="font-mono font-bold text-white">' + e.sandbox_id + '</div>' + url + '</div>';
|
|
207
|
+
htmlStr += btn;
|
|
208
|
+
htmlStr += '</div>';
|
|
209
|
+
}
|
|
210
|
+
list.innerHTML = htmlStr;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
async function destroy(id) {
|
|
214
|
+
if(!confirm("Destroy sandbox?")) return;
|
|
215
|
+
await sb.from("environments").update({ status: "destroyed" }).eq("sandbox_id", id);
|
|
216
|
+
await fetch("https://mczesspakhkomlnmfstw.supabase.co/functions/v1/destroy-stage", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ sandbox_id: id }) });
|
|
217
|
+
loadEnvs();
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
async function logout() { await sb.auth.signOut(); window.location.href = "/"; }
|
|
221
|
+
init();
|
|
222
|
+
</script>
|
|
223
|
+
</body>
|
|
224
|
+
</html>
|
package/dummy.html
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>HYNA-Stage Test</title>
|
|
7
|
+
<style>
|
|
8
|
+
body {
|
|
9
|
+
background-color: #030303;
|
|
10
|
+
color: #ffffff;
|
|
11
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
|
12
|
+
display: flex;
|
|
13
|
+
flex-direction: column;
|
|
14
|
+
align-items: center;
|
|
15
|
+
justify-content: center;
|
|
16
|
+
height: 100vh;
|
|
17
|
+
margin: 0;
|
|
18
|
+
overflow: hidden;
|
|
19
|
+
}
|
|
20
|
+
.glow {
|
|
21
|
+
position: absolute;
|
|
22
|
+
width: 300px;
|
|
23
|
+
height: 300px;
|
|
24
|
+
background: #3B82F6;
|
|
25
|
+
border-radius: 50%;
|
|
26
|
+
filter: blur(80px);
|
|
27
|
+
opacity: 0.3;
|
|
28
|
+
z-index: -1;
|
|
29
|
+
}
|
|
30
|
+
.card {
|
|
31
|
+
background: rgba(10, 10, 10, 0.8);
|
|
32
|
+
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
33
|
+
padding: 40px;
|
|
34
|
+
border-radius: 20px;
|
|
35
|
+
text-align: center;
|
|
36
|
+
backdrop-filter: blur(10px);
|
|
37
|
+
box-shadow: 0 10px 30px rgba(0,0,0,0.5);
|
|
38
|
+
}
|
|
39
|
+
h1 {
|
|
40
|
+
font-size: 2rem;
|
|
41
|
+
margin-bottom: 10px;
|
|
42
|
+
background: linear-gradient(to right, #60A5FA, #3B82F6);
|
|
43
|
+
-webkit-background-clip: text;
|
|
44
|
+
-webkit-text-fill-color: transparent;
|
|
45
|
+
}
|
|
46
|
+
p {
|
|
47
|
+
color: #9CA3AF;
|
|
48
|
+
margin-bottom: 20px;
|
|
49
|
+
}
|
|
50
|
+
.status {
|
|
51
|
+
display: inline-block;
|
|
52
|
+
padding: 6px 12px;
|
|
53
|
+
background: rgba(34, 197, 94, 0.1);
|
|
54
|
+
color: #22c55e;
|
|
55
|
+
border: 1px solid rgba(34, 197, 94, 0.2);
|
|
56
|
+
border-radius: 20px;
|
|
57
|
+
font-size: 0.85rem;
|
|
58
|
+
font-weight: bold;
|
|
59
|
+
}
|
|
60
|
+
.time {
|
|
61
|
+
margin-top: 20px;
|
|
62
|
+
font-family: monospace;
|
|
63
|
+
color: #6B7280;
|
|
64
|
+
font-size: 0.9rem;
|
|
65
|
+
}
|
|
66
|
+
</style>
|
|
67
|
+
</head>
|
|
68
|
+
<body>
|
|
69
|
+
<div class="glow"></div>
|
|
70
|
+
<div class="card">
|
|
71
|
+
<h1>š HYNA-Stage is LIVE</h1>
|
|
72
|
+
<p>This environment was provisioned in under 5 seconds.</p>
|
|
73
|
+
<div class="status">ā RUNNING</div>
|
|
74
|
+
<div class="time" id="clock">Loading time...</div>
|
|
75
|
+
</div>
|
|
76
|
+
|
|
77
|
+
<script>
|
|
78
|
+
// Prove that JS is executing inside the VM
|
|
79
|
+
setInterval(() => {
|
|
80
|
+
const now = new Date();
|
|
81
|
+
document.getElementById('clock').innerText = 'Server Time: ' + now.toLocaleTimeString();
|
|
82
|
+
}, 1000);
|
|
83
|
+
</script>
|
|
84
|
+
</body>
|
|
85
|
+
</html>
|
package/hyna-deploy.zip
ADDED
|
Binary file
|
package/hyna-live.zip
ADDED
|
Binary file
|
package/index.html
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>HYNA-Stage | Ephemeral Infrastructure</title>
|
|
7
|
+
<script src="https://cdn.jsdelivr.net/npm/@supabase/supabase-js@2"></script>
|
|
8
|
+
<script src="https://cdn.tailwindcss.com"></script>
|
|
9
|
+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;800&family=JetBrains+Mono&display=swap" rel="stylesheet">
|
|
10
|
+
<style>
|
|
11
|
+
body { background: #030303; color: #fff; font-family: 'Inter', sans-serif; overflow-x: hidden; margin: 0; padding: 0; }
|
|
12
|
+
.grid-bg { position: fixed; inset: 0; background-image: linear-gradient(to right, rgba(255,255,255,0.03) 1px, transparent 1px), linear-gradient(to bottom, rgba(255,255,255,0.03) 1px, transparent 1px); background-size: 40px 40px; z-index: -2; mask-image: radial-gradient(circle at center, black, transparent 80%); }
|
|
13
|
+
.glow-orb { position: fixed; width: 600px; height: 600px; background: #3B82F6; border-radius: 50%; filter: blur(120px); opacity: 0.15; top: -200px; left: 50%; transform: translateX(-50%); z-index: -1; }
|
|
14
|
+
|
|
15
|
+
/* Hardcoded Logo Fix */
|
|
16
|
+
|
|
17
|
+
.nav-logo {
|
|
18
|
+
width: 32px;
|
|
19
|
+
height: 32px;
|
|
20
|
+
object-fit: contain;
|
|
21
|
+
mix-blend-mode: screen; /* šŖ Makes dark pixels transparent */
|
|
22
|
+
margin-right: 8px;
|
|
23
|
+
vertical-align: middle;
|
|
24
|
+
}
|
|
25
|
+
.logo-container {
|
|
26
|
+
display: flex;
|
|
27
|
+
align-items: center;
|
|
28
|
+
justify-content: center;
|
|
29
|
+
gap: 10px;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.hero-logo { position: absolute; top: 10%; left: 50%; transform: translate(-50%, -50%); width: 100%; max-width: 600px; height: auto; opacity: 0.2; filter: blur(6px); z-index: 0; pointer-events: none; }
|
|
33
|
+
|
|
34
|
+
.bento-card { background: rgba(10, 10, 10, 0.7); border: 1px solid rgba(255,255,255,0.08); border-radius: 20px; backdrop-filter: blur(10px); position: relative; z-index: 10; overflow: hidden; transition: 0.3s; }
|
|
35
|
+
.bento-card:hover { border-color: rgba(59, 130, 246, 0.4); transform: translateY(-2px); }
|
|
36
|
+
|
|
37
|
+
.modal { display: none; position: fixed; inset: 0; background: rgba(0,0,0,0.9); backdrop-filter: blur(10px); z-index: 1000; align-items: center; justify-content: center; }
|
|
38
|
+
.modal.active { display: flex; }
|
|
39
|
+
</style>
|
|
40
|
+
</head>
|
|
41
|
+
<body class="min-h-screen flex flex-col">
|
|
42
|
+
|
|
43
|
+
<div class="grid-bg"></div>
|
|
44
|
+
<div class="glow-orb"></div>
|
|
45
|
+
|
|
46
|
+
<nav class="flex justify-between items-center px-6 py-4 border-b border-white/10 bg-[#030303]/80 backdrop-blur-md sticky top-0 z-50">
|
|
47
|
+
<div class="flex items-center gap-3 font-extrabold text-xl tracking-tighter">
|
|
48
|
+
<div class="logo-container"><img src="logo.png" class="nav-logo"> HYNA-Stage</div>
|
|
49
|
+
</div>
|
|
50
|
+
<button onclick="document.getElementById('authModal').classList.add('active')" class="bg-white text-black px-5 py-2 rounded-lg font-bold text-sm hover:scale-105 transition shadow-[0_0_15px_rgba(255,255,255,0.2)]">Sign In</button>
|
|
51
|
+
</nav>
|
|
52
|
+
|
|
53
|
+
<main class="flex-1 flex flex-col items-center px-4 pt-24 pb-16 relative">
|
|
54
|
+
<img src="logo.png" class="hero-logo">
|
|
55
|
+
|
|
56
|
+
<div class="text-center relative z-10 mb-12">
|
|
57
|
+
<span class="text-blue-400 font-bold tracking-widest uppercase text-xs mb-4 border border-blue-500/30 bg-blue-500/10 px-4 py-1.5 rounded-full backdrop-blur-md">Zero-Configuration Infrastructure</span>
|
|
58
|
+
<h1 class="text-5xl md:text-7xl font-extrabold tracking-tighter mb-6 max-w-4xl leading-tight">The staging bottleneck<br>is <span class="text-transparent bg-clip-text bg-gradient-to-r from-blue-500 to-blue-300">officially dead.</span></h1>
|
|
59
|
+
</div>
|
|
60
|
+
|
|
61
|
+
<div class="grid grid-cols-1 md:grid-cols-3 md:grid-rows-2 gap-5 max-w-5xl w-full relative z-10">
|
|
62
|
+
<div class="bento-card p-8 md:col-span-2 md:row-span-1 flex flex-col justify-center">
|
|
63
|
+
<p class="text-gray-400 text-lg md:text-xl mb-8 leading-relaxed">AI agents write code in seconds. HYNA-Stage provisions secure, isolated, auto-destructing cloud environments in 5 seconds.</p>
|
|
64
|
+
<button onclick="document.getElementById('authModal').classList.add('active')" class="bg-white text-black px-6 py-3 rounded-lg font-bold text-lg hover:scale-105 transition shadow-[0_0_30px_rgba(255,255,255,0.15)] self-start">Deploy Your First Environment</button>
|
|
65
|
+
</div>
|
|
66
|
+
|
|
67
|
+
<div class="bento-card p-8 md:col-span-1 md:row-span-1 flex flex-col justify-center">
|
|
68
|
+
<div class="text-5xl font-extrabold text-white mb-2">$15<span class="text-blue-500">.6k</span></div>
|
|
69
|
+
<div class="text-sm text-gray-500 font-medium">Lost productivity per dev, per year.</div>
|
|
70
|
+
</div>
|
|
71
|
+
|
|
72
|
+
<div class="bento-card p-0 md:col-span-1 md:row-span-1 bg-black/80">
|
|
73
|
+
<div class="px-4 py-3 border-b border-white/10 flex gap-2">
|
|
74
|
+
<div class="w-2.5 h-2.5 rounded-full bg-red-500"></div>
|
|
75
|
+
<div class="w-2.5 h-2.5 rounded-full bg-yellow-500"></div>
|
|
76
|
+
<div class="w-2.5 h-2.5 rounded-full bg-green-500"></div>
|
|
77
|
+
</div>
|
|
78
|
+
<div class="p-5 font-mono text-xs md:text-sm text-gray-300 leading-relaxed">
|
|
79
|
+
<div><span class="text-green-400">ā</span> hyna-stage up</div>
|
|
80
|
+
<div class="text-gray-500 mt-2">Spinning up VM...</div>
|
|
81
|
+
<div class="text-blue-400 mt-2">ā
LIVE!</div>
|
|
82
|
+
</div>
|
|
83
|
+
</div>
|
|
84
|
+
|
|
85
|
+
<div class="bento-card p-8 md:col-span-2 md:row-span-1 flex items-center justify-between">
|
|
86
|
+
<div>
|
|
87
|
+
<div class="text-xl font-bold text-white mb-2">Firecracker MicroVMs</div>
|
|
88
|
+
<div class="text-sm text-gray-400">No Docker. No cloud consoles. Just pure execution.</div>
|
|
89
|
+
</div>
|
|
90
|
+
</div>
|
|
91
|
+
</div>
|
|
92
|
+
</main>
|
|
93
|
+
|
|
94
|
+
<div id="authModal" class="modal">
|
|
95
|
+
<div class="bg-[#0a0a0a] border border-white/10 p-8 rounded-2xl w-full max-w-sm relative shadow-[0_0_50px_rgba(59,130,246,0.15)]">
|
|
96
|
+
<button onclick="document.getElementById('authModal').classList.remove('active')" class="absolute top-4 right-4 text-gray-500 hover:text-white text-2xl">×</button>
|
|
97
|
+
<h2 class="text-2xl font-extrabold mb-6">Initialize Sequence</h2>
|
|
98
|
+
<input type="email" id="authEmail" placeholder="Email" class="w-full bg-black border border-white/10 text-white p-3 rounded-lg mb-4 focus:outline-none focus:border-blue-500">
|
|
99
|
+
<input type="password" id="authPass" placeholder="Password" class="w-full bg-black border border-white/10 text-white p-3 rounded-lg mb-6 focus:outline-none focus:border-blue-500">
|
|
100
|
+
<button id="authBtn" class="w-full bg-white text-black font-bold py-3 rounded-lg hover:scale-[1.02] transition">Authenticate</button>
|
|
101
|
+
</div>
|
|
102
|
+
</div>
|
|
103
|
+
|
|
104
|
+
<script>
|
|
105
|
+
const sb = supabase.createClient("https://mczesspakhkomlnmfstw.supabase.co", "sb_publishable_sG0q8k6txKHOihBduyr67w_UKfolcvj");
|
|
106
|
+
document.getElementById('authBtn').addEventListener('click', async () => {
|
|
107
|
+
const email = document.getElementById('authEmail').value;
|
|
108
|
+
const pass = document.getElementById('authPass').value;
|
|
109
|
+
const btn = document.getElementById('authBtn');
|
|
110
|
+
btn.innerText = "Authenticating...";
|
|
111
|
+
let res = await sb.auth.signInWithPassword({ email, password: pass });
|
|
112
|
+
if (res.error) {
|
|
113
|
+
let sup = await sb.auth.signUp({ email, password: pass });
|
|
114
|
+
if (sup.error) { alert(sup.error.message); btn.innerText = "Authenticate"; }
|
|
115
|
+
else { alert("Account created! Sign in."); btn.innerText = "Authenticate"; }
|
|
116
|
+
} else { window.location.href = "/dashboard.html"; }
|
|
117
|
+
});
|
|
118
|
+
</script>
|
|
119
|
+
</body>
|
|
120
|
+
</html>
|
package/index.js
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { program } from 'commander';
|
|
4
|
+
import fs from 'fs';
|
|
5
|
+
import path from 'path';
|
|
6
|
+
import chalk from 'chalk';
|
|
7
|
+
import ora from 'ora';
|
|
8
|
+
import inquirer from 'inquirer';
|
|
9
|
+
|
|
10
|
+
const DEPLOY_URL = "https://mczesspakhkomlnmfstw.supabase.co/functions/v1/deploy-stage";
|
|
11
|
+
const DESTROY_URL = "https://mczesspakhkomlnmfstw.supabase.co/functions/v1/destroy-stage";
|
|
12
|
+
|
|
13
|
+
program
|
|
14
|
+
.name('hyna-stage')
|
|
15
|
+
.description('Ephemeral cloud environments in 5 seconds.')
|
|
16
|
+
.version('1.0.5');
|
|
17
|
+
|
|
18
|
+
// āā UP COMMAND āā
|
|
19
|
+
program
|
|
20
|
+
.command('up')
|
|
21
|
+
.description('Deploy a file to a temporary HYNA environment')
|
|
22
|
+
.option('-f, --file <file>', 'File to deploy', 'index.html')
|
|
23
|
+
.option('-k, --key <key>', 'Your HYNA API key')
|
|
24
|
+
.action(async (options) => {
|
|
25
|
+
let apiKey = options.key;
|
|
26
|
+
if (!apiKey) {
|
|
27
|
+
const answers = await inquirer.prompt([
|
|
28
|
+
{
|
|
29
|
+
type: 'input',
|
|
30
|
+
name: 'key',
|
|
31
|
+
message: 'Enter your HYNA API Key:',
|
|
32
|
+
validate: (input) => input.length > 0 ? true : 'API Key is required'
|
|
33
|
+
}
|
|
34
|
+
]);
|
|
35
|
+
apiKey = answers.key;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const filePath = path.resolve(process.cwd(), options.file);
|
|
39
|
+
if (!fs.existsSync(filePath)) {
|
|
40
|
+
console.error(chalk.red(`ā Error: File not found -> ${options.file}`));
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const stats = fs.statSync(filePath);
|
|
45
|
+
const spinner = ora('Spinning up cloud environment...').start();
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
let fileContent = fs.readFileSync(filePath);
|
|
49
|
+
const isLarge = stats.size > 50000;
|
|
50
|
+
|
|
51
|
+
if (isLarge) {
|
|
52
|
+
spinner.text = 'Encoding large file for transport...';
|
|
53
|
+
fileContent = fileContent.toString('base64');
|
|
54
|
+
} else {
|
|
55
|
+
fileContent = fileContent.toString('utf-8');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
spinner.text = 'Deploying to E2B MicroVM...';
|
|
59
|
+
|
|
60
|
+
const response = await fetch(DEPLOY_URL, {
|
|
61
|
+
method: 'POST',
|
|
62
|
+
headers: {
|
|
63
|
+
'Content-Type': 'application/json',
|
|
64
|
+
'Authorization': `Bearer ${apiKey}`
|
|
65
|
+
},
|
|
66
|
+
body: JSON.stringify({ apiKey: apiKey, code: fileContent, isBase64: isLarge })
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const data = await response.json();
|
|
70
|
+
if (!response.ok) throw new Error(data.error || `Server responded with ${response.status}`);
|
|
71
|
+
|
|
72
|
+
spinner.succeed(chalk.green('Environment is LIVE!'));
|
|
73
|
+
|
|
74
|
+
console.log('');
|
|
75
|
+
console.log(chalk.bgBlue.white.bold(` š URL: ${data.url} `));
|
|
76
|
+
console.log(chalk.gray(` Sandbox ID: ${data.sandbox_id}`));
|
|
77
|
+
console.log(chalk.gray(` (Auto-destroys in 5 minutes)\n`));
|
|
78
|
+
|
|
79
|
+
} catch (err) {
|
|
80
|
+
spinner.fail(chalk.red('Deployment failed'));
|
|
81
|
+
console.error(chalk.red(`\nā ${err.message}\n`));
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// āā DOWN COMMAND āā
|
|
86
|
+
program
|
|
87
|
+
.command('down')
|
|
88
|
+
.description('Destroy an active environment early')
|
|
89
|
+
.argument('<sandbox_id>', 'The Sandbox ID to destroy')
|
|
90
|
+
.option('-k, --key <key>', 'Your HYNA API key')
|
|
91
|
+
.action(async (sandboxId, options) => {
|
|
92
|
+
let apiKey = options.key;
|
|
93
|
+
if (!apiKey) {
|
|
94
|
+
const answers = await inquirer.prompt([
|
|
95
|
+
{
|
|
96
|
+
type: 'input',
|
|
97
|
+
name: 'key',
|
|
98
|
+
message: 'Enter your HYNA API Key:',
|
|
99
|
+
validate: (input) => input.length > 0 ? true : 'API Key is required'
|
|
100
|
+
}
|
|
101
|
+
]);
|
|
102
|
+
apiKey = answers.key;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const spinner = ora(`Destroying sandbox ${sandboxId}...`).start();
|
|
106
|
+
|
|
107
|
+
try {
|
|
108
|
+
const response = await fetch(DESTROY_URL, {
|
|
109
|
+
method: 'POST',
|
|
110
|
+
headers: {
|
|
111
|
+
'Content-Type': 'application/json',
|
|
112
|
+
'Authorization': `Bearer ${apiKey}`
|
|
113
|
+
},
|
|
114
|
+
body: JSON.stringify({ sandbox_id: sandboxId })
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
const data = await response.json();
|
|
118
|
+
if (!response.ok) throw new Error(data.error || 'Failed to destroy sandbox');
|
|
119
|
+
|
|
120
|
+
spinner.succeed(chalk.green('Environment destroyed successfully!'));
|
|
121
|
+
console.log(chalk.gray(` The E2B VM has been terminated.\n`));
|
|
122
|
+
} catch (err) {
|
|
123
|
+
spinner.fail(chalk.red('Destroy failed'));
|
|
124
|
+
console.error(chalk.red(`\nā ${err.message}\n`));
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
program.parse();
|
package/logo.png
ADDED
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hynaweb/stage",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "
|
|
5
|
-
"main": "
|
|
3
|
+
"version": "1.0.5",
|
|
4
|
+
"description": "Ephemeral cloud environments in 5 seconds.",
|
|
5
|
+
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
7
|
-
"hyna-stage": "
|
|
7
|
+
"hyna-stage": "./index.js"
|
|
8
8
|
},
|
|
9
|
-
"
|
|
10
|
-
"bin/"
|
|
11
|
-
],
|
|
9
|
+
"type": "module",
|
|
12
10
|
"dependencies": {
|
|
13
|
-
"
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
11
|
+
"chalk": "^5.3.0",
|
|
12
|
+
"commander": "^11.0.0",
|
|
13
|
+
"inquirer": "^9.2.12",
|
|
14
|
+
"ora": "^7.0.1"
|
|
15
|
+
}
|
|
17
16
|
}
|