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