@hynaweb/stage 1.3.0 → 2.0.1

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/bin/cli.js CHANGED
@@ -1,96 +1,208 @@
1
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
- }
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';
21
9
 
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
- });
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);
36
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);
37
88
 
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');
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('Email or Username: ')).trim();
98
+ rl.close();
93
99
  }
94
- }
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
+ });
95
206
 
96
- main();
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,18 +1,31 @@
1
1
  {
2
2
  "name": "@hynaweb/stage",
3
- "version": "1.3.0",
4
- "description": "Ephemeral cloud environments in 5 seconds.",
5
- "main": "index.js",
3
+ "version": "2.0.1",
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
- "license": "UNLICENSED",
11
- "author": "Harun (HYNAWEB)",
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
+ ],
12
26
  "dependencies": {
13
27
  "chalk": "^5.3.0",
14
- "commander": "^11.0.0",
15
- "inquirer": "^9.2.12",
16
- "ora": "^7.0.1"
28
+ "commander": "^12.1.0",
29
+ "ora": "^8.0.1"
17
30
  }
18
31
  }
package/Test#1/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/Test#1/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();
@@ -1,16 +0,0 @@
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
- }