mozhost-cli 2.3.0 → 2.4.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/bin/mozhost.js CHANGED
@@ -7,6 +7,7 @@ const containerCommands = require('../src/commands/containers');
7
7
  const deployCommands = require('../src/commands/deploy');
8
8
  const domainCommands = require('../src/commands/domains');
9
9
  const databaseCommands = require('../src/commands/databases'); // 👈 NOVO
10
+ const terminalCommands = require('../src/commands/terminal');
10
11
  const packageJson = require('../package.json');
11
12
 
12
13
  program
@@ -171,6 +172,20 @@ program
171
172
  .action(domainCommands.remove);
172
173
 
173
174
 
175
+ // ============================================
176
+ // TERMINAL COMMANDS 👈 ADICIONAR ANTES DO PARSE
177
+ // ============================================
178
+ program
179
+ .command('ssh <container>')
180
+ .description('Acessar terminal do container')
181
+ .action(terminalCommands.ssh);
182
+
183
+ program
184
+ .command('exec <container> <command>')
185
+ .description('Executar comando no container')
186
+ .action(terminalCommands.exec);
187
+
188
+
174
189
  // ============================================
175
190
  // PARSE & HELP
176
191
  // ============================================
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mozhost-cli",
3
- "version": "2.3.0",
3
+ "version": "2.4.0",
4
4
  "description": "Command-line interface for MozHost container platform",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -0,0 +1,198 @@
1
+ const chalk = require('chalk');
2
+ const ora = require('ora');
3
+ const WebSocket = require('ws');
4
+ const api = require('../utils/api');
5
+ const config = require('../utils/config');
6
+
7
+ // ============================================
8
+ // HELPER - Resolver Container
9
+ // ============================================
10
+ async function resolveContainer(identifier) {
11
+ try {
12
+ const response = await api.getContainer(identifier);
13
+ return response.container;
14
+ } catch (error) {
15
+ if (error.response?.status === 404) {
16
+ const listResponse = await api.listContainers();
17
+ const found = listResponse.containers.find(c =>
18
+ c.name === identifier ||
19
+ c.id === identifier ||
20
+ c.id.startsWith(identifier)
21
+ );
22
+
23
+ if (!found) {
24
+ const similar = listResponse.containers.filter(c =>
25
+ c.name.toLowerCase().includes(identifier.toLowerCase()) ||
26
+ c.id.includes(identifier)
27
+ );
28
+
29
+ if (similar.length > 0) {
30
+ console.log(chalk.yellow('\n💡 Você quis dizer:'));
31
+ similar.forEach(c => {
32
+ console.log(chalk.white(` • ${c.name} ${chalk.gray(`(${c.id.slice(0, 8)})`)}`));
33
+ });
34
+ }
35
+
36
+ throw new Error(`Container "${identifier}" não encontrado`);
37
+ }
38
+
39
+ return found;
40
+ }
41
+ throw error;
42
+ }
43
+ }
44
+
45
+ // ============================================
46
+ // EXEC - Executar comando remoto
47
+ // ============================================
48
+ async function exec(containerIdentifier, command) {
49
+ try {
50
+ if (!command) {
51
+ console.error(chalk.red('❌ Comando não especificado'));
52
+ console.log(chalk.gray(' Use: mozhost exec <container> "<comando>"'));
53
+ console.log(chalk.gray(' Exemplo: mozhost exec meu-bot "npm install"'));
54
+ process.exit(1);
55
+ }
56
+
57
+ const spinner = ora('Resolvendo container...').start();
58
+ const container = await resolveContainer(containerIdentifier);
59
+
60
+ if (container.status !== 'running') {
61
+ spinner.fail(chalk.red(`Container está ${container.status}, não rodando`));
62
+ process.exit(1);
63
+ }
64
+
65
+ spinner.text = `Executando comando em ${container.name}...`;
66
+
67
+ const response = await api.executeCommand(container.id, command);
68
+
69
+ spinner.stop();
70
+
71
+ console.log(chalk.cyan.bold(`\n📟 Executando em ${container.name}\n`));
72
+ console.log(chalk.gray(`$ ${command}\n`));
73
+
74
+ if (response.output) {
75
+ console.log(response.output);
76
+ }
77
+
78
+ console.log();
79
+
80
+ if (response.exitCode === 0) {
81
+ console.log(chalk.green(`✅ Comando executado com sucesso (exit code: ${response.exitCode})`));
82
+ } else {
83
+ console.log(chalk.red(`❌ Comando falhou (exit code: ${response.exitCode})`));
84
+ process.exit(response.exitCode);
85
+ }
86
+
87
+ } catch (error) {
88
+ console.error(chalk.red('\n❌ Erro ao executar comando:'));
89
+ console.error(chalk.red(` ${error.response?.data?.error || error.message}`));
90
+ process.exit(1);
91
+ }
92
+ }
93
+
94
+ // ============================================
95
+ // SSH - Terminal interativo via WebSocket
96
+ // ============================================
97
+ async function ssh(containerIdentifier) {
98
+ try {
99
+ const spinner = ora('Resolvendo container...').start();
100
+ const container = await resolveContainer(containerIdentifier);
101
+
102
+ if (container.status !== 'running') {
103
+ spinner.fail(chalk.red(`Container está ${container.status}, não rodando`));
104
+ process.exit(1);
105
+ }
106
+
107
+ const token = await config.getToken();
108
+ if (!token) {
109
+ spinner.fail(chalk.red('Não autenticado'));
110
+ console.log(chalk.gray(' Use: mozhost auth'));
111
+ process.exit(1);
112
+ }
113
+
114
+ const apiUrl = await config.getApiUrl();
115
+ const wsUrl = apiUrl
116
+ .replace('https://', 'wss://')
117
+ .replace('http://', 'ws://');
118
+
119
+ spinner.text = `Conectando ao terminal de ${container.name}...`;
120
+
121
+ const ws = new WebSocket(
122
+ `${wsUrl}/api/terminal/${container.id}?token=${token}`
123
+ );
124
+
125
+ ws.on('open', () => {
126
+ spinner.stop();
127
+ console.log(chalk.green(`\n✅ Conectado ao container ${chalk.bold(container.name)}`));
128
+ console.log(chalk.gray(' Digite comandos normalmente. Ctrl+C para sair.\n'));
129
+
130
+ // Configurar stdin para modo raw
131
+ if (process.stdin.isTTY) {
132
+ process.stdin.setRawMode(true);
133
+ process.stdin.resume();
134
+ process.stdin.setEncoding('utf8');
135
+ }
136
+
137
+ // Enviar input do usuário para o WebSocket
138
+ process.stdin.on('data', (key) => {
139
+ // Ctrl+C para sair
140
+ if (key === '\u0003') {
141
+ console.log(chalk.yellow('\n\n👋 Desconectando...'));
142
+ ws.close();
143
+ process.exit(0);
144
+ }
145
+
146
+ // Enviar tecla para o container
147
+ ws.send(JSON.stringify({
148
+ type: 'input',
149
+ data: key
150
+ }));
151
+ });
152
+ });
153
+
154
+ ws.on('message', (data) => {
155
+ try {
156
+ const message = JSON.parse(data.toString());
157
+
158
+ if (message.type === 'output') {
159
+ process.stdout.write(message.data);
160
+ } else if (message.type === 'error') {
161
+ console.error(chalk.red(`\n❌ ${message.message}`));
162
+ ws.close();
163
+ process.exit(1);
164
+ }
165
+ } catch (error) {
166
+ console.error(chalk.red('Erro ao processar mensagem:', error.message));
167
+ }
168
+ });
169
+
170
+ ws.on('error', (error) => {
171
+ spinner.stop();
172
+ console.error(chalk.red('\n❌ Erro de conexão:'));
173
+ console.error(chalk.red(` ${error.message}`));
174
+ process.exit(1);
175
+ });
176
+
177
+ ws.on('close', () => {
178
+ if (process.stdin.isTTY) {
179
+ process.stdin.setRawMode(false);
180
+ }
181
+ console.log(chalk.yellow('\n\n👋 Conexão encerrada'));
182
+ process.exit(0);
183
+ });
184
+
185
+ } catch (error) {
186
+ console.error(chalk.red('\n❌ Erro ao conectar:'));
187
+ console.error(chalk.red(` ${error.message}`));
188
+ process.exit(1);
189
+ }
190
+ }
191
+
192
+ // ============================================
193
+ // EXPORTS
194
+ // ============================================
195
+ module.exports = {
196
+ exec,
197
+ ssh
198
+ };
package/src/utils/api.js CHANGED
@@ -231,6 +231,15 @@ class ApiClient {
231
231
  const response = await client.delete(`/api/domains/${domainId}`);
232
232
  return response.data;
233
233
  }
234
+
235
+ // Terminal
236
+ async executeCommand(containerId, command) {
237
+ const client = await this.getClient();
238
+ const response = await client.post(`/api/terminal/${containerId}/exec`, {
239
+ command
240
+ });
241
+ return response.data;
242
+ }
234
243
  }
235
244
 
236
245
  module.exports = new ApiClient();