bimmo-cli 2.1.1 → 2.1.3

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/interface.js +57 -181
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bimmo-cli",
3
- "version": "2.1.1",
3
+ "version": "2.1.3",
4
4
  "description": "🌿 Plataforma de IA universal com Modo Normal, Agentes e Swarms. Suporte a Diffs coloridos, Auto-Edit (Auto/Manual) e Contexto Inteligente.",
5
5
  "bin": {
6
6
  "bimmo": "bin/bimmo"
package/src/interface.js CHANGED
@@ -8,6 +8,7 @@ import fs from 'fs';
8
8
  import path from 'path';
9
9
  import mime from 'mime-types';
10
10
  import readline from 'readline';
11
+ import { fileURLToPath } from 'url';
11
12
 
12
13
  import { getConfig, configure, updateActiveModel, switchProfile } from './config.js';
13
14
  import { createProvider } from './providers/factory.js';
@@ -15,6 +16,12 @@ import { getProjectContext } from './project-context.js';
15
16
  import { SwarmOrchestrator } from './orchestrator.js';
16
17
  import { editState } from './agent.js';
17
18
 
19
+ const __filename = fileURLToPath(import.meta.url);
20
+ const __dirname = path.dirname(__filename);
21
+ const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, '../package.json'), 'utf-8'));
22
+ const version = pkg.version;
23
+
24
+ // Configuração do renderizador
18
25
  marked.use(new TerminalRenderer({
19
26
  heading: chalk.hex('#c084fc').bold,
20
27
  code: chalk.hex('#00ff9d'),
@@ -43,21 +50,12 @@ async function processInput(input) {
43
50
  const stats = fs.statSync(filePath);
44
51
  if (stats.isFile()) {
45
52
  const mimeType = mime.lookup(filePath) || 'application/octet-stream';
46
-
47
53
  if (mimeType.startsWith('image/')) {
48
54
  const base64Image = fs.readFileSync(filePath, { encoding: 'base64' });
49
- processedContent.push({
50
- type: 'image',
51
- mimeType,
52
- data: base64Image,
53
- fileName: path.basename(filePath)
54
- });
55
+ processedContent.push({ type: 'image', mimeType, data: base64Image, fileName: path.basename(filePath) });
55
56
  } else {
56
57
  const textContent = fs.readFileSync(filePath, 'utf-8');
57
- processedContent.push({
58
- type: 'text',
59
- text: `\n--- Arquivo: ${path.basename(filePath)} ---\n${textContent}\n--- Fim do arquivo ---\n`
60
- });
58
+ processedContent.push({ type: 'text', text: `\n--- Arquivo: ${path.basename(filePath)} ---\n${textContent}\n--- Fim do arquivo ---\n` });
61
59
  }
62
60
  }
63
61
  } else {
@@ -72,27 +70,19 @@ async function processInput(input) {
72
70
  }
73
71
 
74
72
  const hasImage = processedContent.some(c => c.type === 'image');
75
-
76
- if (!hasImage) {
77
- return processedContent.map(c => c.text).join(' ');
78
- }
73
+ if (!hasImage) return processedContent.map(c => c.text).join(' ');
79
74
 
80
75
  const finalContent = [];
81
76
  let currentText = "";
82
-
83
77
  for (const item of processedContent) {
84
78
  if (item.type === 'text') {
85
79
  currentText += (currentText ? " " : "") + item.text;
86
80
  } else {
87
- if (currentText) {
88
- finalContent.push({ type: 'text', text: currentText });
89
- currentText = "";
90
- }
81
+ if (currentText) { finalContent.push({ type: 'text', text: currentText }); currentText = ""; }
91
82
  finalContent.push(item);
92
83
  }
93
84
  }
94
85
  if (currentText) finalContent.push({ type: 'text', text: currentText });
95
-
96
86
  return finalContent;
97
87
  }
98
88
 
@@ -107,6 +97,24 @@ function getModeStyle() {
107
97
  }
108
98
  }
109
99
 
100
+ /**
101
+ * LIMPEZA BRUTA DE HTML
102
+ * Remove tags como <p>, <br>, <div> e qualquer outra tag que venha da IA.
103
+ */
104
+ function cleanAIResponse(text) {
105
+ if (!text) return "";
106
+ return text
107
+ .replace(/<br\s*\/?>/gi, '\n') // <br> vira nova linha
108
+ .replace(/<\/p>/gi, '\n\n') // </p> vira duas novas linhas
109
+ .replace(/<\/div>/gi, '\n') // </div> vira nova linha
110
+ .replace(/<[^>]*>?/gm, '') // REMOVE QUALQUER TAG RESTANTE (Regex robusta)
111
+ .replace(/&nbsp;/g, ' ') // Limpa espaços HTML
112
+ .replace(/&lt;/g, '<') // Desescapa <
113
+ .replace(/&gt;/g, '>') // Desescapa >
114
+ .replace(/&amp;/g, '&') // Desescapa &
115
+ .trim();
116
+ }
117
+
110
118
  export async function startInteractive() {
111
119
  let config = getConfig();
112
120
 
@@ -127,9 +135,7 @@ export async function startInteractive() {
127
135
  messages.push({ role: 'system', content: projectContext });
128
136
  if (activePersona) {
129
137
  const agent = (config.agents || {})[activePersona];
130
- if (agent) {
131
- messages.push({ role: 'system', content: `Sua persona atual é: ${agent.name}. Sua tarefa: ${agent.role}` });
132
- }
138
+ if (agent) messages.push({ role: 'system', content: `Sua persona atual é: ${agent.name}. Sua tarefa: ${agent.role}` });
133
139
  }
134
140
  };
135
141
 
@@ -137,7 +143,7 @@ export async function startInteractive() {
137
143
 
138
144
  console.clear();
139
145
  console.log(lavender(figlet.textSync('bimmo')));
140
- console.log(lavender('─'.repeat(60)));
146
+ console.log(lavender(` v${version} `.padStart(60, '─')));
141
147
  console.log(green(` Perfil: ${bold(config.activeProfile || 'Padrão')} • IA: ${bold(config.provider.toUpperCase())}`));
142
148
  console.log(green(` Modelo: ${bold(config.model)}`));
143
149
  console.log(gray(' /chat | /plan | /edit | /swarm | /use [agente] | /help'));
@@ -158,231 +164,101 @@ export async function startInteractive() {
158
164
  };
159
165
 
160
166
  process.on('SIGINT', globalSigIntHandler);
161
-
162
167
  readline.emitKeypressEvents(process.stdin);
163
168
  if (process.stdin.isTTY) process.stdin.setRawMode(true);
164
169
 
165
170
  while (true) {
166
171
  const modeIndicator = getModeStyle();
167
172
  let input;
168
-
169
173
  try {
170
- const answers = await inquirer.prompt([
171
- {
172
- type: 'input',
173
- name: 'input',
174
- message: modeIndicator + green('Você'),
175
- prefix: '→',
176
- }
177
- ]);
174
+ const answers = await inquirer.prompt([{ type: 'input', name: 'input', message: modeIndicator + green('Você'), prefix: '→' }]);
178
175
  input = answers.input;
179
- } catch (e) {
180
- continue;
181
- }
176
+ } catch (e) { continue; }
182
177
 
178
+ console.log(gray(` 📁 ${process.cwd()}`));
183
179
  const rawInput = input.trim();
184
180
  const cmd = rawInput.toLowerCase();
185
181
 
186
- if (cmd === '/exit' || cmd === 'exit' || cmd === 'sair') {
187
- console.log(lavender('\n👋 BIMMO encerrando sessão. Até logo!\n'));
188
- process.exit(0);
189
- }
190
-
182
+ if (cmd === '/exit' || cmd === 'exit' || cmd === 'sair') { console.log(lavender('\n👋 BIMMO encerrando sessão. Até logo!\n')); process.exit(0); }
191
183
  if (cmd === '/chat') { currentMode = 'chat'; console.log(lavender('✓ Modo CHAT.\n')); continue; }
192
184
  if (cmd === '/plan') { currentMode = 'plan'; console.log(yellow('✓ Modo PLAN.\n')); continue; }
193
-
194
- if (cmd === '/edit') {
195
- currentMode = 'edit';
196
- console.log(chalk.red(`⚠️ Modo EDIT ativado (Sub-modo atual: ${editState.autoAccept ? 'AUTO' : 'MANUAL'}).\n`));
197
- continue;
198
- }
199
- if (cmd === '/edit auto') {
200
- currentMode = 'edit';
201
- editState.autoAccept = true;
202
- console.log(chalk.red('⚠️ Modo EDIT (AUTO) ativado. Mudanças serão aplicadas sem perguntar.\n'));
203
- continue;
204
- }
205
- if (cmd === '/edit manual') {
206
- currentMode = 'edit';
207
- editState.autoAccept = false;
208
- console.log(chalk.red('⚠️ Modo EDIT (MANUAL) ativado. Pedirei permissão para cada mudança.\n'));
209
- continue;
210
- }
185
+ if (cmd === '/edit') { currentMode = 'edit'; console.log(chalk.red(`⚠️ Modo EDIT ativado.\n`)); continue; }
186
+ if (cmd === '/edit auto') { currentMode = 'edit'; editState.autoAccept = true; console.log(chalk.red('⚠️ Modo EDIT (AUTO) ativado.\n')); continue; }
187
+ if (cmd === '/edit manual') { currentMode = 'edit'; editState.autoAccept = false; console.log(chalk.red('⚠️ Modo EDIT (MANUAL) ativado.\n')); continue; }
211
188
 
212
189
  if (cmd.startsWith('/switch ')) {
213
190
  const profileName = rawInput.split(' ')[1];
214
191
  if (profileName && switchProfile(profileName)) {
215
- config = getConfig();
216
- provider = createProvider(config);
192
+ config = getConfig(); provider = createProvider(config);
217
193
  console.log(green(`\n✓ Perfil "${bold(profileName)}" ativado!`));
218
194
  continue;
219
195
  }
220
- console.log(chalk.red(`\n✖ Perfil não encontrado.\n`));
221
- continue;
196
+ console.log(chalk.red(`\n✖ Perfil não encontrado.\n`)); continue;
222
197
  }
223
198
 
224
199
  if (cmd.startsWith('/use ')) {
225
200
  const agentName = rawInput.split(' ')[1];
226
201
  const agents = config.agents || {};
227
- if (agentName === 'normal' || agentName === 'default') {
228
- activePersona = null;
229
- console.log(lavender(`\n✓ Voltando para o Modo Normal.\n`));
230
- resetMessages();
231
- continue;
232
- }
202
+ if (agentName === 'normal' || agentName === 'default') { activePersona = null; console.log(lavender(`\n✓ Voltando para o Modo Normal.\n`)); resetMessages(); continue; }
233
203
  if (agents[agentName]) {
234
204
  activePersona = agentName;
235
205
  const agent = agents[agentName];
236
- if (switchProfile(agent.profile)) {
237
- config = getConfig();
238
- provider = createProvider(config);
239
- }
206
+ if (switchProfile(agent.profile)) { config = getConfig(); provider = createProvider(config); }
240
207
  currentMode = agent.mode || 'chat';
241
208
  console.log(green(`\n✓ Agora você está falando com o Agente: ${bold(agentName)}`));
242
- console.log(gray(` Task: ${agent.role.substring(0, 100)}...\n`));
243
209
  resetMessages();
244
- } else {
245
- console.log(chalk.red(`\n✖ Agente "${agentName}" não encontrado.\n`));
246
- }
210
+ } else { console.log(chalk.red(`\n✖ Agente "${agentName}" não encontrado.\n`)); }
247
211
  continue;
248
212
  }
249
213
 
250
- if (cmd === '/clear') {
251
- resetMessages();
252
- console.clear();
253
- console.log(lavender('✓ Histórico limpo, contexto preservado.\n'));
254
- continue;
255
- }
214
+ if (cmd === '/clear') { resetMessages(); console.clear(); console.log(lavender('✓ Histórico limpo, contexto preservado.\n')); continue; }
256
215
 
257
216
  if (cmd === '/help') {
258
217
  console.log(gray(`
259
- Comandos de Modo:
260
- /chat Modo conversa
261
- /plan Modo planejamento
262
- /edit [auto/manual] → Modo edição (padrão manual)
263
- /use [agente] → Usar um Agente Especialista
264
- /use normal → Voltar para o chat normal
265
- /swarm → Rodar fluxos complexos
266
-
267
- Gerenciamento:
268
- /switch [nome] → Mudar perfil de IA completo
269
- /model [nome] → Mudar modelo atual
270
- /config → Perfis e Agentes
271
- /init → Inicializar .bimmorc.json
272
- @arquivo → Ler arquivo ou imagem
218
+ Comandos:
219
+ /chat /plan /edit [auto/manual] Mudar modo
220
+ /use [agente] | /use normal Usar Agentes
221
+ /switch [nome] | /model [nome] → Mudar IA/Modelo
222
+ /config | /init | @arquivo → Configurações
273
223
  `));
274
224
  continue;
275
225
  }
276
226
 
277
227
  if (cmd === '/config') { await configure(); config = getConfig(); provider = createProvider(config); continue; }
278
228
 
279
- if (cmd === '/init') {
280
- const bimmoRcPath = path.join(process.cwd(), '.bimmorc.json');
281
- if (fs.existsSync(bimmoRcPath)) {
282
- const { overwrite } = await inquirer.prompt([{
283
- type: 'confirm',
284
- name: 'overwrite',
285
- message: 'O arquivo .bimmorc.json já existe. Deseja sobrescrever?',
286
- default: false
287
- }]);
288
- if (!overwrite) continue;
289
- }
290
- const initialConfig = {
291
- projectName: path.basename(process.cwd()),
292
- rules: ["Siga as convenções existentes.", "Prefira código modular."],
293
- preferredTech: [],
294
- ignorePatterns: ["node_modules", ".git"]
295
- };
296
- fs.writeFileSync(bimmoRcPath, JSON.stringify(initialConfig, null, 2));
297
- console.log(green(`\n✅ .bimmorc.json criado com sucesso.\n`));
298
-
299
- resetMessages();
300
- continue;
301
- }
302
-
303
- if (cmd === '/swarm') {
304
- const agents = config.agents || {};
305
- const agentList = Object.keys(agents);
306
- if (agentList.length < 2) {
307
- console.log(chalk.yellow('\nCrie pelo menos 2 Agentes em /config primeiro.\n'));
308
- continue;
309
- }
310
- const { swarmAction } = await inquirer.prompt([{
311
- type: 'list',
312
- name: 'swarmAction',
313
- message: 'Tipo de Enxame:',
314
- choices: ['Sequencial (A → B)', 'Hierárquico (Líder + Workers)', 'Voltar']
315
- }]);
316
- if (swarmAction === 'Voltar') continue;
317
- const { goal } = await inquirer.prompt([{ type: 'input', name: 'goal', message: 'Objetivo do enxame:' }]);
318
-
319
- try {
320
- let result;
321
- if (swarmAction.includes('Sequencial')) {
322
- const { selectedAgents } = await inquirer.prompt([{ type: 'checkbox', name: 'selectedAgents', message: 'Ordem dos agentes:', choices: agentList }]);
323
- result = await orchestrator.runSequential(selectedAgents, goal);
324
- } else {
325
- const { manager } = await inquirer.prompt([{ type: 'list', name: 'manager', message: 'Líder:', choices: agentList }]);
326
- const { workers } = await inquirer.prompt([{ type: 'checkbox', name: 'workers', message: 'Workers:', choices: agentList.filter(a => a !== manager) }]);
327
- result = await orchestrator.runHierarchical(manager, workers, goal);
328
- }
329
- console.log(lavender('\n=== RESULTADO FINAL ===\n') + marked(result));
330
- } catch (e) {
331
- console.error(chalk.red(`\nErro: ${e.message}`));
332
- }
333
- continue;
334
- }
335
-
336
229
  if (rawInput === '') continue;
337
230
 
338
231
  const controller = new AbortController();
339
232
  const localInterruptHandler = () => controller.abort();
340
-
341
- // Switch de SIGINT para modo processamento
342
233
  process.removeListener('SIGINT', globalSigIntHandler);
343
234
  process.on('SIGINT', localInterruptHandler);
344
235
 
345
236
  let modeInstr = "";
346
237
  if (currentMode === 'plan') modeInstr = "\n[MODO PLAN] Apenas analise.";
347
- else if (currentMode === 'edit') modeInstr = `\n[MODO EDIT] Você tem permissão para usar ferramentas. (Auto-Accept: ${editState.autoAccept ? 'ON' : 'OFF'})`;
238
+ else if (currentMode === 'edit') modeInstr = `\n[MODO EDIT] Auto-Accept: ${editState.autoAccept ? 'ON' : 'OFF'}`;
348
239
 
349
240
  const content = await processInput(rawInput);
350
- messages.push({
351
- role: 'user',
352
- content: typeof content === 'string' ? content + modeInstr : [...content, { type: 'text', text: modeInstr }]
353
- });
241
+ messages.push({ role: 'user', content: typeof content === 'string' ? content + modeInstr : [...content, { type: 'text', text: modeInstr }] });
354
242
 
355
- const spinner = ora({
356
- text: lavender(`bimmo pensando... (Ctrl+C para interromper)`),
357
- color: currentMode === 'edit' ? 'red' : 'magenta'
358
- }).start();
243
+ const spinner = ora({ text: lavender(`bimmo pensando... (Ctrl+C para interromper)`), color: currentMode === 'edit' ? 'red' : 'magenta' }).start();
359
244
 
360
245
  try {
361
246
  let responseText = await provider.sendMessage(messages, { signal: controller.signal });
362
247
  spinner.stop();
363
248
 
364
- // LIMPEZA AGRESSIVA DE HTML
365
- const cleanedText = responseText
366
- .replace(/<br\s*\/?>/gi, '\n') // Converte <br> em newline real
367
- .replace(/<p>/gi, '') // Remove tags <p> iniciais
368
- .replace(/<\/p>/gi, '\n\n') // Converte </p> em double newline
369
- .replace(/<\/?[^>]+(>|$)/g, ""); // Remove QUALQUER outra tag residual
249
+ // EXECUTA LIMPEZA BRUTA
250
+ const cleanedText = cleanAIResponse(responseText);
370
251
 
371
252
  messages.push({ role: 'assistant', content: responseText });
372
253
  console.log('\n' + lavender('bimmo ') + getModeStyle());
373
254
  console.log(lavender('─'.repeat(50)));
374
- console.log(marked(cleanedText.trim()));
255
+ console.log(marked(cleanedText));
375
256
  console.log(gray('─'.repeat(50)) + '\n');
376
257
  } catch (err) {
377
258
  spinner.stop();
378
- if (controller.signal.aborted || err.name === 'AbortError') {
379
- console.log(yellow('\n\n⚠️ Operação interrompida pelo usuário.\n'));
380
- messages.pop();
381
- } else {
382
- console.error(chalk.red('\n✖ Erro:') + ' ' + err.message + '\n');
383
- }
259
+ if (controller.signal.aborted || err.name === 'AbortError') { console.log(yellow('\n\n⚠️ Interrompido.\n')); messages.pop(); }
260
+ else { console.error(chalk.red('\n Erro:') + ' ' + err.message + '\n'); }
384
261
  } finally {
385
- // Restaura o modo global de saída
386
262
  process.removeListener('SIGINT', localInterruptHandler);
387
263
  process.on('SIGINT', globalSigIntHandler);
388
264
  }