@hynaweb/stage 1.0.7 ā 1.3.0
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/LICENSE +9 -0
- package/README.md +11 -0
- package/Test#1/dummy.html +84 -0
- package/Test#1/index.js +128 -0
- package/Test#1/package.json +16 -0
- package/bin/cli.js +96 -0
- package/dashboard.html +240 -0
- package/dummy.html +1 -0
- package/hyna-deploy.zip +0 -0
- package/hyna-jam.zip +0 -0
- package/hyna-live.zip +0 -0
- package/hyna-stage-v1.zip +0 -0
- package/index.html +128 -0
- package/index.js +69 -8
- package/inject_logo.py +32 -0
- package/logo.png +0 -0
- package/package.json +3 -1
package/LICENSE
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
Copyright (c) 2026 HYNAWEB. All Rights Reserved.
|
|
2
|
+
|
|
3
|
+
PROPRIETARY AND CONFIDENTIAL SOFTWARE.
|
|
4
|
+
|
|
5
|
+
This software and associated documentation files (the "Software") are the exclusive property of HYNAWEB.
|
|
6
|
+
|
|
7
|
+
No part of this Software may be copied, reproduced, modified, merged, published, distributed, sublicensed, sold, or used in any form without the prior written permission of HYNAWEB.
|
|
8
|
+
|
|
9
|
+
Unauthorized use, duplication, or distribution of this Software is strictly prohibited and may result in severe civil and criminal penalties.
|
package/README.md
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# ā” HynaStage
|
|
2
|
+
|
|
3
|
+
**Ephemeral Firecracker MicroVMs in 5 seconds. Built on a $150 Android phone.**
|
|
4
|
+
|
|
5
|
+
AI agents write code in seconds, but provisioning staging environments takes minutes. HynaStage is a CLI that spins up isolated E2B sandboxes, deploys your HTML/code, and returns a live URL instantly. They auto-destroy after 1 hour to save costs.
|
|
6
|
+
|
|
7
|
+
## š Quick Start
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install -g @hynaweb/stage
|
|
11
|
+
hyna-stage up -f index.html --key=hyna_your_api_key
|
|
@@ -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/Test#1/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.7');
|
|
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 1 hour)\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();
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@hynaweb/stage",
|
|
3
|
+
"version": "1.0.7",
|
|
4
|
+
"description": "Ephemeral cloud environments in 5 seconds.",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"hyna-stage": "./index.js"
|
|
8
|
+
},
|
|
9
|
+
"type": "module",
|
|
10
|
+
"dependencies": {
|
|
11
|
+
"chalk": "^5.3.0",
|
|
12
|
+
"commander": "^11.0.0",
|
|
13
|
+
"inquirer": "^9.2.12",
|
|
14
|
+
"ora": "^7.0.1"
|
|
15
|
+
}
|
|
16
|
+
}
|
package/bin/cli.js
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const axios = require('axios');
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const readline = require('readline');
|
|
6
|
+
|
|
7
|
+
const API_URL = 'https://mczesspakhkomlnmfstw.supabase.co/functions/v1/deploy-stage';
|
|
8
|
+
const DESTROY_URL = 'https://mczesspakhkomlnmfstw.supabase.co/functions/v1/destroy-stage';
|
|
9
|
+
const SUPABASE_KEY = 'sb_publishable_sG0q8k6txKHOihBduyr67w_UKfolcvj';
|
|
10
|
+
|
|
11
|
+
const configPath = path.join(require('os').homedir(), '.hyna-stage');
|
|
12
|
+
|
|
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
|
|
23
|
+
if (fs.existsSync(configPath)) {
|
|
24
|
+
return fs.readFileSync(configPath, 'utf8').trim();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Otherwise prompt
|
|
28
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
29
|
+
return new Promise((resolve) => {
|
|
30
|
+
rl.question('š Enter your HYNA-Stage API Key: ', (key) => {
|
|
31
|
+
fs.writeFileSync(configPath, key.trim());
|
|
32
|
+
rl.close();
|
|
33
|
+
resolve(key.trim());
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async function main() {
|
|
39
|
+
const command = process.argv[2];
|
|
40
|
+
const apiKey = await getApiKey();
|
|
41
|
+
|
|
42
|
+
if (command === 'up') {
|
|
43
|
+
console.log('š Initializing HYNA-Stage...');
|
|
44
|
+
console.log('š¦ Spinning up cloud environment...');
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
const res = await axios.post(API_URL, {}, {
|
|
48
|
+
headers: {
|
|
49
|
+
'Authorization': 'Bearer ' + apiKey,
|
|
50
|
+
'apikey': SUPABASE_KEY,
|
|
51
|
+
'Content-Type': 'application/json'
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
console.log('\nā
Environment is LIVE!');
|
|
56
|
+
console.log('š URL: \x1b[36m' + res.data.url + '\x1b[0m');
|
|
57
|
+
console.log('š Sandbox ID: ' + res.data.sandbox_id);
|
|
58
|
+
console.log('ā±ļø Auto-destruct in: 1 hour\n');
|
|
59
|
+
|
|
60
|
+
} catch (err) {
|
|
61
|
+
console.error('ā Error:', err.response?.data?.error || err.message);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
else if (command === 'down') {
|
|
65
|
+
const sandboxId = process.argv.find(arg => !arg.startsWith('--') && arg !== 'down' && arg !== 'up');
|
|
66
|
+
|
|
67
|
+
if (!sandboxId) {
|
|
68
|
+
console.error('ā Please provide a sandbox ID: hyna-stage down <sandbox_id>');
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
console.log('šļø Destroying sandbox ' + sandboxId + '...');
|
|
73
|
+
|
|
74
|
+
try {
|
|
75
|
+
await axios.post(DESTROY_URL, { sandbox_id: sandboxId }, {
|
|
76
|
+
headers: {
|
|
77
|
+
'Authorization': 'Bearer ' + apiKey,
|
|
78
|
+
'apikey': SUPABASE_KEY,
|
|
79
|
+
'Content-Type': 'application/json'
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
console.log('\nā
Environment destroyed successfully.\n');
|
|
84
|
+
|
|
85
|
+
} catch (err) {
|
|
86
|
+
console.error('ā Error:', err.response?.data?.error || err.message);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
console.log('Usage:');
|
|
91
|
+
console.log(' hyna-stage up Spin up a new environment');
|
|
92
|
+
console.log(' hyna-stage down <id> Destroy a specific environment');
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
main();
|