@hynaweb/stage 1.0.7 → 2.0.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 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
package/bin/cli.js ADDED
@@ -0,0 +1,208 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from 'commander';
3
+ import chalk from 'chalk';
4
+ import ora from 'ora';
5
+ import fs from 'node:fs';
6
+ import path from 'node:path';
7
+ import os from 'node:os';
8
+ import readline from 'node:readline/promises';
9
+
10
+ const VERSION = '2.0.0';
11
+ const CONFIG_PATH = path.join(os.homedir(), '.hyna-stage');
12
+ const BASE_URL = process.env.HYNA_BASE_URL || 'https://mczesspakhkomlnmfstw.supabase.co/functions/v1';
13
+ const EP = {
14
+ login: process.env.HYNA_LOGIN_ENDPOINT || 'login',
15
+ deploy: 'deploy-stage',
16
+ destroy: 'destroy-stage',
17
+ };
18
+
19
+ function readConfig() {
20
+ try {
21
+ if (!fs.existsSync(CONFIG_PATH)) return {};
22
+ const raw = fs.readFileSync(CONFIG_PATH, 'utf8').trim();
23
+ if (!raw) return {};
24
+ return raw.startsWith('{') ? JSON.parse(raw) : { token: raw };
25
+ } catch { return {}; }
26
+ }
27
+ function writeConfig(c) {
28
+ fs.writeFileSync(CONFIG_PATH, JSON.stringify(c, null, 2), { mode: 0o600 });
29
+ try { fs.chmodSync(CONFIG_PATH, 0o600); } catch {}
30
+ }
31
+ function requireAuth() {
32
+ const c = readConfig();
33
+ if (!c.token) fail('Not logged in. Run: hyna-stage login');
34
+ return c;
35
+ }
36
+ const isJson = () => process.argv.includes('--json');
37
+ const info = (m) => { if (!isJson()) console.log(m); };
38
+ const out = (d) => { if (isJson()) console.log(JSON.stringify(d, null, 2)); };
39
+ function fail(m, code = 1) {
40
+ if (isJson()) console.log(JSON.stringify({ ok: false, error: m }));
41
+ else console.error(chalk.red('X ') + m);
42
+ process.exit(code);
43
+ }
44
+ function promptHidden(question) {
45
+ return new Promise((resolve) => {
46
+ process.stdout.write(question);
47
+ const stdin = process.stdin;
48
+ stdin.setRawMode(true);
49
+ stdin.resume();
50
+ stdin.setEncoding('utf8');
51
+ let buf = '';
52
+ const onData = (ch) => {
53
+ if (ch === '\r' || ch === '\n' || ch === '\u0004') {
54
+ stdin.setRawMode(false); stdin.pause();
55
+ stdin.removeListener('data', onData);
56
+ process.stdout.write('\n'); resolve(buf);
57
+ } else if (ch === '\u0003') {
58
+ stdin.setRawMode(false); process.stdout.write('\n'); process.exit(130);
59
+ } else if (ch === '\u007f' || ch === '\b') {
60
+ buf = buf.slice(0, -1);
61
+ } else { buf += ch; }
62
+ };
63
+ stdin.on('data', onData);
64
+ });
65
+ }
66
+ async function api(endpoint, { body, auth = true, method = 'POST' } = {}) {
67
+ const cfg = readConfig();
68
+ const headers = { 'Content-Type': 'application/json', 'x-hyna-key': process.env.HYNA_STAGE_KEY || 'hyna_xc5ub3l52qh' };
69
+ if (auth) headers.Authorization = `Bearer ${cfg.token}`;
70
+ const res = await fetch(`${BASE_URL}/${endpoint}`, {
71
+ method, headers, body: body ? JSON.stringify(body) : undefined,
72
+ });
73
+ const text = await res.text();
74
+ let data;
75
+ try { data = JSON.parse(text); } catch { data = { raw: text }; }
76
+ if (res.status === 401 || res.status === 403)
77
+ throw new Error('Session expired. Run: hyna-stage login');
78
+ if (!res.ok) throw new Error(data?.error || data?.message || res.statusText);
79
+ return data;
80
+ }
81
+ const program = new Command();
82
+ program
83
+ .name('hyna-stage')
84
+ .description('Ephemeral cloud sandboxes in seconds')
85
+ .version(VERSION, '-v, --version')
86
+ .option('--json', 'Machine-readable JSON output')
87
+ .addHelpCommand(false);
88
+
89
+ program.command('login')
90
+ .description('Sign in with username and password')
91
+ .argument('[username]', 'Username (prompted if omitted)')
92
+ .option('-p, --password <password>', 'Password (prompted hidden if omitted)')
93
+ .action(async (username, opts) => {
94
+ let user = username;
95
+ if (!user) {
96
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
97
+ user = (await rl.question('Username: ')).trim();
98
+ rl.close();
99
+ }
100
+ let pass = opts.password;
101
+ if (!pass) pass = await promptHidden('Password: ');
102
+ if (!user || !pass) fail('Username and password are required.');
103
+ const spinner = isJson() ? null : ora('Signing in...').start();
104
+ try {
105
+ const d = await api(EP.login, { auth: false, body: { username: user, password: pass } });
106
+ const token = d.token || d.access_token || d.session?.access_token;
107
+ if (!token) throw new Error('Server did not return a token.');
108
+ writeConfig({ username: user, token, baseUrl: BASE_URL, savedAt: new Date().toISOString() });
109
+ spinner?.succeed(`Signed in as ${chalk.cyan(user)}`);
110
+ out({ ok: true, username: user });
111
+ } catch (e) { spinner?.fail(e.message); fail(e.message); }
112
+ });
113
+
114
+ program.command('logout')
115
+ .description('Sign out and remove saved token')
116
+ .action(() => {
117
+ const c = readConfig();
118
+ if (!c.token) { info(chalk.yellow('Already logged out.')); return; }
119
+ writeConfig({ ...c, token: undefined, username: undefined });
120
+ out({ ok: true, loggedOut: true });
121
+ info(chalk.green('OK ') + 'Logged out.');
122
+ });
123
+
124
+ program.command('whoami')
125
+ .description('Show current session')
126
+ .action(() => {
127
+ const c = readConfig();
128
+ if (!c.token) { out({ ok: true, loggedIn: false }); info(chalk.yellow('Not logged in.')); return; }
129
+ out({ ok: true, loggedIn: true, username: c.username, baseUrl: c.baseUrl || BASE_URL });
130
+ info(`Signed in as ${chalk.cyan(c.username || 'unknown')} -> ${chalk.dim(c.baseUrl || BASE_URL)}`);
131
+ });
132
+
133
+ program.command('up')
134
+ .alias('deploy')
135
+ .description('Deploy a sandbox from a file')
136
+ .argument('<file>', 'File to deploy')
137
+ .option('-n, --name <name>', 'Sandbox name')
138
+ .option('-t, --ttl <seconds>', 'Time-to-live in seconds', '600')
139
+ .action(async (file, opts) => {
140
+ requireAuth();
141
+ if (!fs.existsSync(file)) fail(`File not found: ${file}`);
142
+ const content = fs.readFileSync(file, 'utf8');
143
+ const payload = { file: path.basename(file), name: opts.name, ttl: Number(opts.ttl), version: VERSION, content };
144
+ const spinner = isJson() ? null : ora('Deploying sandbox...').start();
145
+ try {
146
+ const d = await api(EP.deploy, { body: payload });
147
+ spinner?.succeed('Sandbox is live');
148
+ out({ ok: true, ...d });
149
+ if (!isJson()) {
150
+ console.log(`\n ID ${d.id}`);
151
+ console.log(` URL ${chalk.cyan(d.url)}`);
152
+ console.log(` TTL ${d.ttl || opts.ttl}s\n`);
153
+ console.log(` Destroy: hyna-stage down ${d.id}\n`);
154
+ }
155
+ } catch (e) { spinner?.fail(e.message); fail(e.message); }
156
+ });
157
+
158
+ program.command('down')
159
+ .alias('destroy')
160
+ .alias('kill')
161
+ .description('Destroy a running sandbox')
162
+ .argument('<id>', 'Sandbox ID')
163
+ .action(async (id) => {
164
+ requireAuth();
165
+ const spinner = isJson() ? null : ora('Destroying sandbox...').start();
166
+ try {
167
+ const d = await api(EP.destroy, { body: { sandbox_id: id } });
168
+ spinner?.succeed(`Sandbox ${id} destroyed`);
169
+ out({ ok: true, id, ...d });
170
+ } catch (e) { spinner?.fail(e.message); fail(e.message); }
171
+ });
172
+
173
+
174
+
175
+ program.command('help')
176
+ .description('Show detailed help with examples')
177
+ .argument('[command]')
178
+ .action((cmd) => {
179
+ if (cmd) {
180
+ const c = program.commands.find((x) => x.name() === cmd || x.aliases().includes(cmd));
181
+ if (!c) fail(`Unknown command: ${cmd}`);
182
+ return c.outputHelp();
183
+ }
184
+ console.log(`
185
+ HYNA-Stage CLI v${VERSION}
186
+ Ephemeral cloud sandboxes in seconds.
187
+
188
+ USAGE
189
+ hyna-stage <command> [options]
190
+
191
+ AUTH
192
+ login [username] Sign in (prompts for password)
193
+ logout Remove saved token
194
+ whoami Show current session
195
+
196
+ SANDBOXES
197
+ up <file> Deploy a sandbox (-n name, -t ttl)
198
+ down <id> Destroy a sandbox
199
+
200
+
201
+ GLOBAL
202
+ --json Machine-readable output
203
+ -v, --version Show version
204
+ `);
205
+ });
206
+
207
+ program.on('command:*', (args) => fail(`Unknown command: ${args[0]}. Run: hyna-stage help`));
208
+ program.parseAsync(process.argv).catch((e) => fail(e.message));
package/package.json CHANGED
@@ -1,16 +1,31 @@
1
1
  {
2
2
  "name": "@hynaweb/stage",
3
- "version": "1.0.7",
4
- "description": "Ephemeral cloud environments in 5 seconds.",
5
- "main": "index.js",
3
+ "version": "2.0.0",
4
+ "description": "Ephemeral cloud sandboxes in seconds. Built for the KODA Hacker Stage.",
5
+ "author": "Harun Solo Dev",
6
+ "type": "module",
6
7
  "bin": {
7
- "hyna-stage": "./index.js"
8
+ "hyna-stage": "bin/cli.js"
8
9
  },
9
- "type": "module",
10
+ "files": [
11
+ "bin/cli.js",
12
+ "README.md",
13
+ "LICENSE"
14
+ ],
15
+ "engines": {
16
+ "node": ">=18"
17
+ },
18
+ "keywords": [
19
+ "cli",
20
+ "sandbox",
21
+ "e2b",
22
+ "cloud",
23
+ "hyna",
24
+ "koda"
25
+ ],
10
26
  "dependencies": {
11
27
  "chalk": "^5.3.0",
12
- "commander": "^11.0.0",
13
- "inquirer": "^9.2.12",
14
- "ora": "^7.0.1"
28
+ "commander": "^12.1.0",
29
+ "ora": "^8.0.1"
15
30
  }
16
- }
31
+ }
package/dummy.html DELETED
@@ -1,84 +0,0 @@
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/index.js DELETED
@@ -1,128 +0,0 @@
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();