genbox 1.0.113 → 1.0.115

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.
@@ -93,9 +93,15 @@ exports.listCommand = new commander_1.Command('list')
93
93
  endHourInfo = chalk_1.default.red(' hour ending...');
94
94
  }
95
95
  }
96
- // Show auto-destroy status
96
+ // Show auto-destroy status (not applicable for stopped/terminated)
97
97
  let protectedInfo = '';
98
- if (genbox.autoDestroyOnInactivity === false) {
98
+ if (genbox.status === 'stopped') {
99
+ protectedInfo = chalk_1.default.dim(' → gb start to resume');
100
+ }
101
+ else if (genbox.status === 'terminated') {
102
+ // No extra info for terminated
103
+ }
104
+ else if (genbox.autoDestroyOnInactivity === false) {
99
105
  protectedInfo = chalk_1.default.yellow(' [protected]');
100
106
  }
101
107
  else if (genbox.protectedUntil) {
@@ -115,7 +121,17 @@ exports.listCommand = new commander_1.Command('list')
115
121
  }
116
122
  const namePart = chalk_1.default.cyan(nameWithProject.padEnd(nameWidth));
117
123
  const statusPart = statusColor(genbox.status.padEnd(statusWidth));
118
- const ipPart = chalk_1.default.dim((genbox.ipAddress || 'Pending IP').padEnd(ipWidth));
124
+ // Show appropriate IP text based on status
125
+ let ipText = genbox.ipAddress || 'Pending IP';
126
+ if (!genbox.ipAddress) {
127
+ if (genbox.status === 'stopped') {
128
+ ipText = '(paused)';
129
+ }
130
+ else if (genbox.status === 'terminated') {
131
+ ipText = '-';
132
+ }
133
+ }
134
+ const ipPart = chalk_1.default.dim(ipText.padEnd(ipWidth));
119
135
  const sizePart = `(${genbox.size})`.padEnd(sizeWidth);
120
136
  const extraInfo = endHourInfo + protectedInfo;
121
137
  console.log(`${namePart} ${statusPart} ${ipPart} ${sizePart}${extraInfo}`);
@@ -0,0 +1,132 @@
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.startCommand = void 0;
7
+ const commander_1 = require("commander");
8
+ const chalk_1 = __importDefault(require("chalk"));
9
+ const ora_1 = __importDefault(require("ora"));
10
+ const api_1 = require("../api");
11
+ const genbox_selector_1 = require("../genbox-selector");
12
+ const ssh_config_1 = require("../ssh-config");
13
+ exports.startCommand = new commander_1.Command('start')
14
+ .alias('resume')
15
+ .description('Start/resume a stopped Genbox')
16
+ .argument('[name]', 'Name of the Genbox to start')
17
+ .option('-w, --wait', 'Wait for genbox to be fully ready')
18
+ .action(async (name, options) => {
19
+ try {
20
+ const { genbox: target, cancelled } = await (0, genbox_selector_1.selectGenbox)(name, {
21
+ selectMessage: 'Select a genbox to start:',
22
+ statusFilter: 'stopped',
23
+ });
24
+ if (cancelled) {
25
+ console.log(chalk_1.default.dim('Cancelled.'));
26
+ return;
27
+ }
28
+ if (!target) {
29
+ return;
30
+ }
31
+ if (target.status !== 'stopped') {
32
+ if (target.status === 'running') {
33
+ console.log(chalk_1.default.yellow(`Genbox '${target.name}' is already running`));
34
+ console.log(chalk_1.default.dim(` IP: ${target.ipAddress}`));
35
+ console.log(chalk_1.default.dim(` SSH: ssh ${target.name}`));
36
+ return;
37
+ }
38
+ console.error(chalk_1.default.red(`Genbox '${target.name}' cannot be started (status: ${target.status})`));
39
+ return;
40
+ }
41
+ // Start the genbox
42
+ const spinner = (0, ora_1.default)(`Starting ${target.name}...`).start();
43
+ const result = await (0, api_1.fetchApi)(`/genboxes/${target._id}/start`, {
44
+ method: 'POST',
45
+ });
46
+ // Show boot source info
47
+ const bootSourceLabels = {
48
+ snapshot: 'from snapshot (instant)',
49
+ template: 'from project template',
50
+ backup: 'from S3 backup',
51
+ base: 'fresh boot',
52
+ };
53
+ spinner.succeed(chalk_1.default.green(`Genbox '${target.name}' starting ${chalk_1.default.dim(`(${bootSourceLabels[result.bootSource]})`)}`));
54
+ console.log('');
55
+ console.log(chalk_1.default.dim(` Estimated boot time: ${result.estimatedBootTime}`));
56
+ // If we have an IP already (snapshot/template boot), show connection info
57
+ if (result.ipAddress) {
58
+ // Add SSH config entry
59
+ (0, ssh_config_1.addSshConfigEntry)({ name: result.name, ipAddress: result.ipAddress });
60
+ console.log('');
61
+ console.log(chalk_1.default.green(' Ready to connect:'));
62
+ console.log(chalk_1.default.cyan(` ssh ${result.name}`));
63
+ if (result.urls && Object.keys(result.urls).length > 0) {
64
+ console.log('');
65
+ console.log(chalk_1.default.dim(' URLs:'));
66
+ for (const [service, url] of Object.entries(result.urls)) {
67
+ console.log(chalk_1.default.dim(` ${service}: ${url}`));
68
+ }
69
+ }
70
+ }
71
+ else {
72
+ console.log('');
73
+ console.log(chalk_1.default.dim(' Server is booting...'));
74
+ console.log(chalk_1.default.dim(` Run ${chalk_1.default.cyan(`gb status ${result.name}`)} to check progress`));
75
+ }
76
+ // If --wait flag, poll until ready
77
+ if (options.wait) {
78
+ console.log('');
79
+ const waitSpinner = (0, ora_1.default)('Waiting for genbox to be ready...').start();
80
+ let attempts = 0;
81
+ const maxAttempts = 60; // 5 minutes max (5s intervals)
82
+ while (attempts < maxAttempts) {
83
+ await new Promise(resolve => setTimeout(resolve, 5000));
84
+ attempts++;
85
+ try {
86
+ const status = await (0, api_1.fetchApi)(`/genboxes/${result._id}`);
87
+ if (status.status === 'running' && status.ipAddress) {
88
+ waitSpinner.succeed(chalk_1.default.green('Genbox is ready!'));
89
+ // Add SSH config entry
90
+ (0, ssh_config_1.addSshConfigEntry)({ name: status.name, ipAddress: status.ipAddress });
91
+ console.log('');
92
+ console.log(chalk_1.default.green(' Connect with:'));
93
+ console.log(chalk_1.default.cyan(` ssh ${status.name}`));
94
+ if (status.urls && Object.keys(status.urls).length > 0) {
95
+ console.log('');
96
+ console.log(chalk_1.default.dim(' URLs:'));
97
+ for (const [service, url] of Object.entries(status.urls)) {
98
+ console.log(chalk_1.default.dim(` ${service}: ${url}`));
99
+ }
100
+ }
101
+ return;
102
+ }
103
+ if (status.status === 'error') {
104
+ waitSpinner.fail(chalk_1.default.red('Genbox failed to start'));
105
+ if (status.setupErrors) {
106
+ console.log(chalk_1.default.dim(` Errors: ${status.setupErrors}`));
107
+ }
108
+ return;
109
+ }
110
+ waitSpinner.text = `Waiting for genbox to be ready... (${attempts * 5}s)`;
111
+ }
112
+ catch {
113
+ // Ignore polling errors
114
+ }
115
+ }
116
+ waitSpinner.warn(chalk_1.default.yellow('Timed out waiting for genbox'));
117
+ console.log(chalk_1.default.dim(` Run ${chalk_1.default.cyan(`gb status ${result.name}`)} to check progress`));
118
+ }
119
+ }
120
+ catch (error) {
121
+ if (error.name === 'ExitPromptError' || error.message?.includes('force closed')) {
122
+ console.log('');
123
+ console.log(chalk_1.default.dim('Cancelled.'));
124
+ return;
125
+ }
126
+ if (error instanceof api_1.AuthenticationError) {
127
+ (0, api_1.handleApiError)(error);
128
+ return;
129
+ }
130
+ console.error(chalk_1.default.red(`Error: ${error.message}`));
131
+ }
132
+ });
@@ -0,0 +1,94 @@
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.stopCommand = void 0;
7
+ const commander_1 = require("commander");
8
+ const chalk_1 = __importDefault(require("chalk"));
9
+ const confirm_1 = __importDefault(require("@inquirer/confirm"));
10
+ const ora_1 = __importDefault(require("ora"));
11
+ const api_1 = require("../api");
12
+ const genbox_selector_1 = require("../genbox-selector");
13
+ const ssh_config_1 = require("../ssh-config");
14
+ exports.stopCommand = new commander_1.Command('stop')
15
+ .description('Stop a running Genbox (saves state for quick resume)')
16
+ .argument('[name]', 'Name of the Genbox to stop')
17
+ .option('-y, --yes', 'Skip confirmation prompt')
18
+ .action(async (name, options) => {
19
+ try {
20
+ const { genbox: target, cancelled } = await (0, genbox_selector_1.selectGenbox)(name, {
21
+ selectMessage: 'Select a genbox to stop:',
22
+ statusFilter: 'running',
23
+ });
24
+ if (cancelled) {
25
+ console.log(chalk_1.default.dim('Cancelled.'));
26
+ return;
27
+ }
28
+ if (!target) {
29
+ return;
30
+ }
31
+ if (target.status !== 'running') {
32
+ console.error(chalk_1.default.red(`Genbox '${target.name}' is not running (status: ${target.status})`));
33
+ return;
34
+ }
35
+ // Confirm
36
+ let confirmed = options.yes;
37
+ if (!confirmed) {
38
+ console.log('');
39
+ console.log(chalk_1.default.blue('Stop will:'));
40
+ console.log(chalk_1.default.dim(' 1. Save all uncommitted changes'));
41
+ console.log(chalk_1.default.dim(' 2. Create a snapshot for quick resume'));
42
+ console.log(chalk_1.default.dim(' 3. Destroy the server (to save costs)'));
43
+ console.log('');
44
+ confirmed = await (0, confirm_1.default)({
45
+ message: `Stop '${target.name}'?`,
46
+ default: true,
47
+ });
48
+ }
49
+ if (!confirmed) {
50
+ console.log(chalk_1.default.dim('Operation cancelled.'));
51
+ return;
52
+ }
53
+ // Stop the genbox
54
+ const spinner = (0, ora_1.default)(`Stopping ${target.name}...`).start();
55
+ spinner.text = 'Saving changes and creating snapshot...';
56
+ const result = await (0, api_1.fetchApi)(`/genboxes/${target._id}/stop`, {
57
+ method: 'POST',
58
+ });
59
+ if (result.success) {
60
+ spinner.succeed(chalk_1.default.green(`Genbox '${target.name}' stopped`));
61
+ console.log('');
62
+ if (result.bootSource === 'snapshot') {
63
+ console.log(chalk_1.default.green(' Snapshot created for instant resume'));
64
+ console.log(chalk_1.default.dim(` Resume time: ${result.estimatedResumeTime}`));
65
+ }
66
+ else {
67
+ console.log(chalk_1.default.yellow(' Backup created (snapshot not available on your plan)'));
68
+ console.log(chalk_1.default.dim(` Resume time: ${result.estimatedResumeTime}`));
69
+ console.log('');
70
+ console.log(chalk_1.default.cyan(' Upgrade to Pro for instant snapshots:'));
71
+ console.log(chalk_1.default.dim(' https://genbox.dev/pricing'));
72
+ }
73
+ console.log('');
74
+ console.log(chalk_1.default.dim(` To resume: ${chalk_1.default.cyan(`gb start ${target.name}`)}`));
75
+ // Remove SSH config entry (server is being destroyed)
76
+ (0, ssh_config_1.removeSshConfigEntry)(target.name);
77
+ }
78
+ else {
79
+ spinner.fail(chalk_1.default.red(`Failed to stop genbox: ${result.message}`));
80
+ }
81
+ }
82
+ catch (error) {
83
+ if (error.name === 'ExitPromptError' || error.message?.includes('force closed')) {
84
+ console.log('');
85
+ console.log(chalk_1.default.dim('Cancelled.'));
86
+ return;
87
+ }
88
+ if (error instanceof api_1.AuthenticationError) {
89
+ (0, api_1.handleApiError)(error);
90
+ return;
91
+ }
92
+ console.error(chalk_1.default.red(`Error: ${error.message}`));
93
+ }
94
+ });
@@ -60,10 +60,14 @@ function isInProjectContext() {
60
60
  */
61
61
  async function selectGenbox(name, options = {}) {
62
62
  const projectName = getProjectContext();
63
- const genboxes = await getGenboxes({
63
+ let genboxes = await getGenboxes({
64
64
  all: options.all,
65
65
  includeTerminated: options.includeTerminated,
66
66
  });
67
+ // Filter by status if specified
68
+ if (options.statusFilter) {
69
+ genboxes = genboxes.filter(g => g.status === options.statusFilter);
70
+ }
67
71
  // If name is provided, find it directly
68
72
  if (name) {
69
73
  const genbox = genboxes.find(g => g.name === name);
package/dist/index.js CHANGED
@@ -35,6 +35,8 @@ const cleanup_ssh_1 = require("./commands/cleanup-ssh");
35
35
  const restart_1 = require("./commands/restart");
36
36
  const backup_1 = require("./commands/backup");
37
37
  const backups_1 = require("./commands/backups");
38
+ const stop_1 = require("./commands/stop");
39
+ const start_1 = require("./commands/start");
38
40
  program
39
41
  .addCommand(init_1.initCommand)
40
42
  .addCommand(create_1.createCommand)
@@ -62,5 +64,7 @@ program
62
64
  .addCommand(cleanup_ssh_1.cleanupSshCommand)
63
65
  .addCommand(restart_1.restartCommand)
64
66
  .addCommand(backup_1.backupCommand)
65
- .addCommand(backups_1.backupsCommand);
67
+ .addCommand(backups_1.backupsCommand)
68
+ .addCommand(stop_1.stopCommand)
69
+ .addCommand(start_1.startCommand);
66
70
  program.parse(process.argv);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "genbox",
3
- "version": "1.0.113",
3
+ "version": "1.0.115",
4
4
  "description": "Genbox CLI - AI-Powered Development Environments",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -10,6 +10,17 @@
10
10
  "files": [
11
11
  "dist/**/*"
12
12
  ],
13
+ "scripts": {
14
+ "build": "tsc",
15
+ "start": "node dist/index.js",
16
+ "dev": "ts-node src/index.ts",
17
+ "prepublishOnly": "npm run build",
18
+ "publish:patch": "./scripts/publish.sh patch",
19
+ "publish:minor": "./scripts/publish.sh minor",
20
+ "publish:major": "./scripts/publish.sh major",
21
+ "release": "./scripts/publish.sh patch",
22
+ "test": "echo \"Error: no test specified\" && exit 1"
23
+ },
13
24
  "keywords": [
14
25
  "genbox",
15
26
  "ai",
@@ -54,15 +65,5 @@
54
65
  "inquirer": "^13.0.2",
55
66
  "js-yaml": "^4.1.1",
56
67
  "ora": "^9.0.0"
57
- },
58
- "scripts": {
59
- "build": "tsc",
60
- "start": "node dist/index.js",
61
- "dev": "ts-node src/index.ts",
62
- "publish:patch": "./scripts/publish.sh patch",
63
- "publish:minor": "./scripts/publish.sh minor",
64
- "publish:major": "./scripts/publish.sh major",
65
- "release": "./scripts/publish.sh patch",
66
- "test": "echo \"Error: no test specified\" && exit 1"
67
68
  }
68
- }
69
+ }