mozhost-cli 2.2.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.2.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
@@ -201,9 +201,7 @@ class ApiClient {
201
201
  return response.data;
202
202
  }
203
203
 
204
- }
205
-
206
- // Domains
204
+ // Domains
207
205
  async listDomains() {
208
206
  const client = await this.getClient();
209
207
  const response = await client.get('/api/domains');
@@ -234,4 +232,14 @@ class ApiClient {
234
232
  return response.data;
235
233
  }
236
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
+ }
243
+ }
244
+
237
245
  module.exports = new ApiClient();
File without changes