genbox 1.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/dist/api.js ADDED
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.fetchApi = fetchApi;
4
+ const config_store_1 = require("./config-store");
5
+ const API_URL = process.env.GENBOX_API_URL || 'https://api.genbox.dev';
6
+ async function fetchApi(endpoint, options = {}) {
7
+ const url = `${API_URL}${endpoint}`;
8
+ const token = config_store_1.ConfigStore.getToken();
9
+ const headers = {
10
+ 'Content-Type': 'application/json',
11
+ ...options.headers,
12
+ };
13
+ if (token) {
14
+ headers['Authorization'] = `Bearer ${token}`;
15
+ }
16
+ else {
17
+ // Fallback for prototype (optional, or remove if we want forced auth)
18
+ headers['X-User-ID'] = 'user-1 (prototype)';
19
+ }
20
+ const response = await fetch(url, {
21
+ ...options,
22
+ headers,
23
+ });
24
+ if (!response.ok) {
25
+ const text = await response.text();
26
+ throw new Error(`API Error (${response.status}): ${text}`);
27
+ }
28
+ // Handle 204 No Content
29
+ if (response.status === 204) {
30
+ return null;
31
+ }
32
+ // Handle empty bodies
33
+ const contentLength = response.headers.get('content-length');
34
+ if (contentLength === '0') {
35
+ return null;
36
+ }
37
+ return response.json();
38
+ }
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.balanceCommand = void 0;
7
+ const commander_1 = require("commander");
8
+ const chalk_1 = __importDefault(require("chalk"));
9
+ const api_1 = require("../api");
10
+ exports.balanceCommand = new commander_1.Command('balance')
11
+ .description('Show current credit balance')
12
+ .action(async () => {
13
+ try {
14
+ const user = await (0, api_1.fetchApi)('/users/me');
15
+ console.log(chalk_1.default.bold('User ID:'), user.externalId);
16
+ console.log(chalk_1.default.bold('Credits:'), chalk_1.default.green(user.credits));
17
+ }
18
+ catch (error) {
19
+ console.error(chalk_1.default.red('Failed to fetch balance:'), error.message);
20
+ }
21
+ });
@@ -0,0 +1,96 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.connectCommand = void 0;
40
+ const commander_1 = require("commander");
41
+ const chalk_1 = __importDefault(require("chalk"));
42
+ const api_1 = require("../api");
43
+ const os = __importStar(require("os"));
44
+ const path = __importStar(require("path"));
45
+ const fs = __importStar(require("fs"));
46
+ const child_process_1 = require("child_process");
47
+ function getPrivateSshKey() {
48
+ const home = os.homedir();
49
+ const potentialKeys = [
50
+ path.join(home, '.ssh', 'id_rsa'),
51
+ path.join(home, '.ssh', 'id_ed25519'),
52
+ ];
53
+ for (const keyPath of potentialKeys) {
54
+ if (fs.existsSync(keyPath)) {
55
+ return keyPath;
56
+ }
57
+ }
58
+ throw new Error('No SSH private key found in ~/.ssh/');
59
+ }
60
+ exports.connectCommand = new commander_1.Command('connect')
61
+ .description('SSH into a Genbox')
62
+ .argument('<name>', 'Name of the Genbox')
63
+ .action(async (name) => {
64
+ try {
65
+ // 1. Find Genbox
66
+ const genboxes = await (0, api_1.fetchApi)('/genboxes');
67
+ const target = genboxes.find((a) => a.name === name);
68
+ if (!target) {
69
+ console.error(chalk_1.default.red(`Genbox '${name}' not found.`));
70
+ return;
71
+ }
72
+ if (!target.ipAddress) {
73
+ console.error(chalk_1.default.yellow(`Genbox '${name}' is still provisioning (no IP). Please wait.`));
74
+ return;
75
+ }
76
+ // 2. Get Key
77
+ const keyPath = getPrivateSshKey();
78
+ // 3. Connect
79
+ console.log(chalk_1.default.dim(`Connecting to ${chalk_1.default.bold(name)} (${target.ipAddress})...`));
80
+ const sshArgs = [
81
+ '-i', keyPath,
82
+ '-o', 'StrictHostKeyChecking=no',
83
+ '-o', 'UserKnownHostsFile=/dev/null',
84
+ `dev@${target.ipAddress}`
85
+ ];
86
+ const ssh = (0, child_process_1.spawn)('ssh', sshArgs, { stdio: 'inherit' });
87
+ ssh.on('close', (code) => {
88
+ if (code !== 0) {
89
+ console.log(chalk_1.default.dim(`Connection closed with code ${code}`));
90
+ }
91
+ });
92
+ }
93
+ catch (error) {
94
+ console.error(chalk_1.default.red(`Error: ${error.message}`));
95
+ }
96
+ });
@@ -0,0 +1,219 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.createCommand = void 0;
40
+ const commander_1 = require("commander");
41
+ const inquirer_1 = __importDefault(require("inquirer"));
42
+ const chalk_1 = __importDefault(require("chalk"));
43
+ const ora_1 = __importDefault(require("ora"));
44
+ const config_1 = require("../config");
45
+ const fs = __importStar(require("fs"));
46
+ const path = __importStar(require("path"));
47
+ const os = __importStar(require("os"));
48
+ const api_1 = require("../api");
49
+ const ssh_config_1 = require("../ssh-config");
50
+ // Real API Client
51
+ async function provisionGenbox(options) {
52
+ return (0, api_1.fetchApi)('/genboxes', {
53
+ method: 'POST',
54
+ body: JSON.stringify(options),
55
+ });
56
+ }
57
+ function getPublicSshKey() {
58
+ const home = os.homedir();
59
+ const potentialKeys = [
60
+ path.join(home, '.ssh', 'id_rsa.pub'),
61
+ path.join(home, '.ssh', 'id_ed25519.pub'),
62
+ ];
63
+ for (const keyPath of potentialKeys) {
64
+ if (fs.existsSync(keyPath)) {
65
+ const content = fs.readFileSync(keyPath, 'utf-8').trim();
66
+ if (content)
67
+ return content;
68
+ }
69
+ }
70
+ throw new Error('No public SSH key found in ~/.ssh/ (checked id_rsa.pub and id_ed25519.pub)');
71
+ }
72
+ function getPrivateSshKey() {
73
+ const home = os.homedir();
74
+ const potentialKeys = [
75
+ path.join(home, '.ssh', 'id_rsa'),
76
+ path.join(home, '.ssh', 'id_ed25519'),
77
+ ];
78
+ for (const keyPath of potentialKeys) {
79
+ if (fs.existsSync(keyPath)) {
80
+ return keyPath;
81
+ }
82
+ }
83
+ throw new Error('No SSH private key found in ~/.ssh/');
84
+ }
85
+ exports.createCommand = new commander_1.Command('create')
86
+ .description('Create a new Genbox')
87
+ .argument('<name>', 'Name of the Genbox')
88
+ .option('--size <size>', 'Size of the Genbox (small, medium, large, xl)')
89
+ .action(async (name, options) => {
90
+ try {
91
+ const config = (0, config_1.loadConfig)();
92
+ const size = options.size || 'small';
93
+ const publicKey = getPublicSshKey();
94
+ // Check if SSH auth is needed for git repos (only ask if using SSH, not PAT)
95
+ let privateKeyContent;
96
+ const usesSSHAuth = config.git_auth?.method === 'ssh' ||
97
+ (config.repos && Object.values(config.repos).some(r => r.auth === 'ssh'));
98
+ if (usesSSHAuth) {
99
+ const { injectKey } = await inquirer_1.default.prompt([{
100
+ type: 'confirm',
101
+ name: 'injectKey',
102
+ message: 'Inject local SSH Private Key for git cloning?',
103
+ default: true
104
+ }]);
105
+ if (injectKey) {
106
+ const home = os.homedir();
107
+ const privKeyPath = [path.join(home, '.ssh', 'id_rsa'), path.join(home, '.ssh', 'id_ed25519')].find(kv => fs.existsSync(kv));
108
+ if (privKeyPath) {
109
+ privateKeyContent = fs.readFileSync(privKeyPath, 'utf-8');
110
+ console.log(chalk_1.default.dim(` Using private key: ${privKeyPath}`));
111
+ }
112
+ else {
113
+ console.warn(chalk_1.default.yellow('No private key found to inject!'));
114
+ }
115
+ }
116
+ }
117
+ const spinner = (0, ora_1.default)(`Creating Genbox '${name}' for project '${config.project_name}'...`).start();
118
+ try {
119
+ // Prepare files bundle
120
+ const filesBundle = [];
121
+ if (config.files) {
122
+ for (const f of config.files) {
123
+ const sourcePath = path.resolve(process.cwd(), f.source);
124
+ if (fs.existsSync(sourcePath)) {
125
+ const content = fs.readFileSync(sourcePath, 'utf-8');
126
+ filesBundle.push({
127
+ path: f.target,
128
+ content: content,
129
+ permissions: f.permissions || '0644'
130
+ });
131
+ }
132
+ else {
133
+ console.warn(chalk_1.default.yellow(`Warning: File ${f.source} not found. Skipping.`));
134
+ }
135
+ }
136
+ }
137
+ // Scripts handling (simplistic: upload and run)
138
+ const postDetails = [];
139
+ if (config.scripts) {
140
+ for (const s of config.scripts) {
141
+ const scriptName = path.basename(s);
142
+ const targetPath = `/home/dev/${scriptName}`;
143
+ const sourcePath = path.resolve(process.cwd(), s);
144
+ if (fs.existsSync(sourcePath)) {
145
+ const content = fs.readFileSync(sourcePath, 'utf-8');
146
+ filesBundle.push({
147
+ path: targetPath,
148
+ content: content,
149
+ permissions: '0755'
150
+ });
151
+ postDetails.push(`su - dev -c "${targetPath}"`);
152
+ }
153
+ }
154
+ }
155
+ // project_name from config becomes 'workspace' in the API
156
+ const workspace = config.project_name || 'default';
157
+ // Load environment variables (including GIT_TOKEN for PAT auth)
158
+ const envVars = (0, config_1.loadEnvVars)();
159
+ const gitToken = envVars.GIT_TOKEN;
160
+ const genbox = await provisionGenbox({
161
+ name,
162
+ size,
163
+ publicKey,
164
+ workspace,
165
+ services: config.services,
166
+ files: filesBundle,
167
+ postDetails,
168
+ repos: config.repos,
169
+ privateKey: privateKeyContent,
170
+ gitToken,
171
+ });
172
+ spinner.succeed(chalk_1.default.green(`Genbox '${name}' created successfully!`));
173
+ // Add SSH config entry for easy access
174
+ if (genbox.ipAddress) {
175
+ const sshConfigAdded = (0, ssh_config_1.addSshConfigEntry)({
176
+ name,
177
+ ipAddress: genbox.ipAddress,
178
+ });
179
+ if (sshConfigAdded) {
180
+ console.log(chalk_1.default.dim(` SSH config added: ssh ${(0, ssh_config_1.getSshAlias)(name)}`));
181
+ }
182
+ }
183
+ console.log(chalk_1.default.dim('───────────────────────────────────────────────'));
184
+ console.log(` ${chalk_1.default.bold('Environment:')} ${name}`);
185
+ console.log(` ${chalk_1.default.bold('Workspace:')} ${genbox.workspace || workspace}`);
186
+ console.log(` ${chalk_1.default.bold('Size:')} ${genbox.size}`);
187
+ console.log(` ${chalk_1.default.bold('Status:')} ${chalk_1.default.yellow(genbox.status)}`);
188
+ if (genbox.ipAddress) {
189
+ console.log(` ${chalk_1.default.bold('IP:')} ${genbox.ipAddress}`);
190
+ }
191
+ console.log(chalk_1.default.dim('───────────────────────────────────────────────'));
192
+ // Display URLs
193
+ if (genbox.urls && Object.keys(genbox.urls).length > 0) {
194
+ console.log('\n' + chalk_1.default.bold('Access URLs:'));
195
+ for (const [service, url] of Object.entries(genbox.urls)) {
196
+ console.log(` ${chalk_1.default.cyan(service.padEnd(10))} ${url}`);
197
+ }
198
+ }
199
+ console.log(chalk_1.default.dim('───────────────────────────────────────────────'));
200
+ console.log(`\n${chalk_1.default.bold('Quick Commands:')}`);
201
+ console.log(` SSH: ${chalk_1.default.cyan(`genbox connect ${name}`)}`);
202
+ console.log(` Status: ${chalk_1.default.cyan(`genbox status ${name}`)}`);
203
+ console.log(` URLs: ${chalk_1.default.cyan(`genbox urls ${name}`)}`);
204
+ console.log(` Destroy: ${chalk_1.default.cyan(`genbox destroy ${name}`)}`);
205
+ }
206
+ catch (innerError) {
207
+ spinner.fail(chalk_1.default.red(`Failed to create Genbox '${name}': ${innerError.message}`));
208
+ }
209
+ }
210
+ catch (error) {
211
+ // Handle user cancellation (Ctrl+C) gracefully
212
+ if (error.name === 'ExitPromptError' || error.message?.includes('force closed')) {
213
+ console.log('');
214
+ console.log(chalk_1.default.dim('Cancelled.'));
215
+ return;
216
+ }
217
+ console.error(chalk_1.default.red(`Error: ${error.message}`));
218
+ }
219
+ });
@@ -0,0 +1,62 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.destroyCommand = void 0;
7
+ const commander_1 = require("commander");
8
+ const chalk_1 = __importDefault(require("chalk"));
9
+ const inquirer_1 = __importDefault(require("inquirer"));
10
+ const ora_1 = __importDefault(require("ora"));
11
+ const api_1 = require("../api");
12
+ const ssh_config_1 = require("../ssh-config");
13
+ exports.destroyCommand = new commander_1.Command('destroy')
14
+ .description('Destroy a Genbox')
15
+ .argument('<name>', 'Name of the Genbox to destroy')
16
+ .option('-y, --yes', 'Skip confirmation')
17
+ .action(async (name, options) => {
18
+ try {
19
+ // 1. Find ID by name
20
+ const genboxes = await (0, api_1.fetchApi)('/genboxes');
21
+ const target = genboxes.find((a) => a.name === name);
22
+ if (!target) {
23
+ console.error(chalk_1.default.red(`Genbox '${name}' not found.`));
24
+ return;
25
+ }
26
+ // 2. Confirm
27
+ let confirmed = options.yes;
28
+ if (!confirmed) {
29
+ const answer = await inquirer_1.default.prompt([
30
+ {
31
+ type: 'confirm',
32
+ name: 'confirmed',
33
+ message: `Are you sure you want to PERMANENTLY destroy '${name}' (${target.ipAddress})?`,
34
+ default: false,
35
+ },
36
+ ]);
37
+ confirmed = answer.confirmed;
38
+ }
39
+ if (!confirmed) {
40
+ console.log('Operation cancelled.');
41
+ return;
42
+ }
43
+ // 3. Delete
44
+ const spinner = (0, ora_1.default)(`Destroying ${name}...`).start();
45
+ await (0, api_1.fetchApi)(`/genboxes/${target._id}`, {
46
+ method: 'DELETE',
47
+ });
48
+ // Remove SSH config entry
49
+ (0, ssh_config_1.removeSshConfigEntry)(name);
50
+ spinner.succeed(chalk_1.default.green(`Genbox '${name}' destroyed successfully.`));
51
+ console.log(chalk_1.default.dim(' SSH config entry removed'));
52
+ }
53
+ catch (error) {
54
+ // Handle user cancellation (Ctrl+C) gracefully
55
+ if (error.name === 'ExitPromptError' || error.message?.includes('force closed')) {
56
+ console.log('');
57
+ console.log(chalk_1.default.dim('Cancelled.'));
58
+ return;
59
+ }
60
+ console.error(chalk_1.default.red(`Error: ${error.message}`));
61
+ }
62
+ });
@@ -0,0 +1,166 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.forwardCommand = void 0;
40
+ const commander_1 = require("commander");
41
+ const chalk_1 = __importDefault(require("chalk"));
42
+ const api_1 = require("../api");
43
+ const os = __importStar(require("os"));
44
+ const path = __importStar(require("path"));
45
+ const fs = __importStar(require("fs"));
46
+ const child_process_1 = require("child_process");
47
+ const config_1 = require("../config");
48
+ function getPrivateSshKey() {
49
+ const home = os.homedir();
50
+ const potentialKeys = [
51
+ path.join(home, '.ssh', 'id_rsa'),
52
+ path.join(home, '.ssh', 'id_ed25519'),
53
+ ];
54
+ for (const keyPath of potentialKeys) {
55
+ if (fs.existsSync(keyPath)) {
56
+ return keyPath;
57
+ }
58
+ }
59
+ throw new Error('No SSH private key found in ~/.ssh/');
60
+ }
61
+ exports.forwardCommand = new commander_1.Command('forward')
62
+ .description('Set up SSH port forwarding to a Genbox for local debugging')
63
+ .argument('<name>', 'Name of the Genbox')
64
+ .option('-p, --ports <ports>', 'Additional ports to forward (comma-separated, e.g., "8080,9000")')
65
+ .action(async (name, options) => {
66
+ try {
67
+ // 1. Find Genbox
68
+ const genboxes = await (0, api_1.fetchApi)('/genboxes');
69
+ const target = genboxes.find((a) => a.name === name);
70
+ if (!target) {
71
+ console.error(chalk_1.default.red(`Genbox '${name}' not found.`));
72
+ return;
73
+ }
74
+ if (!target.ipAddress) {
75
+ console.error(chalk_1.default.yellow(`Genbox '${name}' is still provisioning (no IP). Please wait.`));
76
+ return;
77
+ }
78
+ // 2. Get SSH key
79
+ let keyPath;
80
+ try {
81
+ keyPath = getPrivateSshKey();
82
+ }
83
+ catch (error) {
84
+ console.error(chalk_1.default.red(error.message));
85
+ return;
86
+ }
87
+ // 3. Build port mappings from config or defaults
88
+ const portMappings = [];
89
+ // Try to load config for service ports
90
+ try {
91
+ const config = (0, config_1.loadConfig)();
92
+ if (config.services) {
93
+ for (const [serviceName, serviceConfig] of Object.entries(config.services)) {
94
+ portMappings.push({
95
+ name: serviceName,
96
+ localPort: serviceConfig.port,
97
+ remotePort: serviceConfig.port,
98
+ });
99
+ }
100
+ }
101
+ }
102
+ catch {
103
+ // No config, use default GoodPass ports
104
+ portMappings.push({ name: 'Web App', localPort: 3000, remotePort: 3000 }, { name: 'Gateway', localPort: 3050, remotePort: 3050 }, { name: 'Auth', localPort: 3051, remotePort: 3051 }, { name: 'Notifications', localPort: 3052, remotePort: 3052 }, { name: 'Products', localPort: 3053, remotePort: 3053 }, { name: 'Partner API', localPort: 3054, remotePort: 3054 }, { name: 'Admin UI', localPort: 5173, remotePort: 5173 });
105
+ }
106
+ // Add custom ports if specified
107
+ if (options.ports) {
108
+ const customPorts = options.ports.split(',').map((p) => parseInt(p.trim(), 10));
109
+ for (const port of customPorts) {
110
+ if (!isNaN(port) && !portMappings.find(m => m.localPort === port)) {
111
+ portMappings.push({
112
+ name: `Custom:${port}`,
113
+ localPort: port,
114
+ remotePort: port,
115
+ });
116
+ }
117
+ }
118
+ }
119
+ // 4. Display what will be forwarded
120
+ console.log(chalk_1.default.blue(`[INFO] Setting up port forwarding to ${chalk_1.default.bold(name)}...`));
121
+ console.log('');
122
+ console.log(chalk_1.default.dim('Forwarded ports:'));
123
+ for (const mapping of portMappings) {
124
+ console.log(` ${chalk_1.default.cyan(mapping.name.padEnd(15))} localhost:${mapping.localPort}`);
125
+ }
126
+ console.log('');
127
+ console.log(chalk_1.default.yellow('Press Ctrl+C to stop forwarding'));
128
+ console.log('');
129
+ // 5. Build SSH args with port forwarding
130
+ const sshArgs = [
131
+ '-N', // No command, just forwarding
132
+ '-i', keyPath,
133
+ '-o', 'IdentitiesOnly=yes',
134
+ '-o', 'StrictHostKeyChecking=no',
135
+ '-o', 'UserKnownHostsFile=/dev/null',
136
+ '-o', 'LogLevel=ERROR',
137
+ ];
138
+ // Add port forwards
139
+ for (const mapping of portMappings) {
140
+ sshArgs.push('-L', `${mapping.localPort}:localhost:${mapping.remotePort}`);
141
+ }
142
+ sshArgs.push(`dev@${target.ipAddress}`);
143
+ // 6. Start SSH tunnel
144
+ const ssh = (0, child_process_1.spawn)('ssh', sshArgs, { stdio: 'inherit' });
145
+ ssh.on('error', (error) => {
146
+ console.error(chalk_1.default.red(`Failed to start SSH: ${error.message}`));
147
+ });
148
+ ssh.on('close', (code) => {
149
+ if (code !== 0 && code !== null) {
150
+ console.log(chalk_1.default.dim(`Port forwarding ended with code ${code}`));
151
+ }
152
+ else {
153
+ console.log(chalk_1.default.dim('Port forwarding stopped.'));
154
+ }
155
+ });
156
+ // Handle SIGINT gracefully
157
+ process.on('SIGINT', () => {
158
+ console.log('');
159
+ console.log(chalk_1.default.dim('Stopping port forwarding...'));
160
+ ssh.kill('SIGINT');
161
+ });
162
+ }
163
+ catch (error) {
164
+ console.error(chalk_1.default.red(`Error: ${error.message}`));
165
+ }
166
+ });