bimmo-cli 2.2.5 → 2.2.8

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "bimmo-cli",
3
- "version": "2.2.5",
4
- "description": "🌿 Plataforma de IA universal profissional com Agentes e Swarms. Suporte a Autocomplete, Diffs e Contexto Inteligente.",
3
+ "version": "2.2.8",
4
+ "description": "🌿 Plataforma de IA universal profissional com Agentes e Swarms. Suporte a Autocomplete real-time, Diffs coloridos e Contexto Inteligente.",
5
5
  "bin": {
6
6
  "bimmo": "bin/bimmo"
7
7
  },
package/src/agent.js CHANGED
@@ -28,6 +28,7 @@ export const tools = [
28
28
  },
29
29
  execute: async ({ query }) => {
30
30
  if (!tvly) return 'Erro: Chave de API da Tavily não configurada. Use /config para configurar.';
31
+ console.log(chalk.blue(`\n 🌐 Pesquisando na web: ${chalk.bold(query)}...`));
31
32
  const searchResponse = await tvly.search(query, {
32
33
  searchDepth: 'advanced',
33
34
  maxResults: 5
@@ -51,6 +52,7 @@ export const tools = [
51
52
  },
52
53
  execute: async ({ path: filePath }) => {
53
54
  try {
55
+ console.log(chalk.blue(`\n 📖 Lendo arquivo: ${chalk.bold(filePath)}...`));
54
56
  return fs.readFileSync(filePath, 'utf-8');
55
57
  } catch (err) {
56
58
  return `Erro ao ler arquivo: ${err.message}`;
@@ -77,7 +79,7 @@ export const tools = [
77
79
  const differences = diff.diffLines(oldContent, content);
78
80
 
79
81
  console.log(`\n${chalk.cyan('📝 Alterações propostas em:')} ${chalk.bold(filePath)}`);
80
- console.log(chalk.gray('─'.repeat(40)));
82
+ console.log(chalk.gray('─'.repeat(50)));
81
83
 
82
84
  let hasChanges = false;
83
85
  differences.forEach((part) => {
@@ -85,20 +87,25 @@ export const tools = [
85
87
  const color = part.added ? chalk.green : part.removed ? chalk.red : chalk.gray;
86
88
  const prefix = part.added ? '+' : part.removed ? '-' : ' ';
87
89
 
88
- // Mostra apenas linhas com mudanças ou um pouco de contexto
89
90
  if (part.added || part.removed) {
90
- process.stdout.write(color(`${prefix} ${part.value}`));
91
+ // Garante que cada linha tenha o prefixo
92
+ const lines = part.value.split('\n');
93
+ lines.forEach(line => {
94
+ if (line || part.value.endsWith('\n')) {
95
+ process.stdout.write(color(`${prefix} ${line}\n`));
96
+ }
97
+ });
91
98
  } else {
92
99
  // Mostra apenas as primeiras e últimas linhas de blocos sem mudança para encurtar
93
- const lines = part.value.split('\n');
100
+ const lines = part.value.split('\n').filter(l => l.trim() !== "");
94
101
  if (lines.length > 4) {
95
- process.stdout.write(color(` ${lines[0]}\n ...\n ${lines[lines.length-2]}\n`));
96
- } else {
97
- process.stdout.write(color(` ${part.value}`));
102
+ process.stdout.write(color(` ${lines[0]}\n ...\n ${lines[lines.length-1]}\n`));
103
+ } else if (lines.length > 0) {
104
+ lines.forEach(line => process.stdout.write(color(` ${line}\n`)));
98
105
  }
99
106
  }
100
107
  });
101
- console.log(chalk.gray('\n' + '─'.repeat(40)));
108
+ console.log(chalk.gray('─'.repeat(50)));
102
109
 
103
110
  if (!hasChanges) {
104
111
  return "Nenhuma mudança detectada no arquivo.";
@@ -143,7 +150,7 @@ export const tools = [
143
150
  },
144
151
  execute: async ({ command }) => {
145
152
  try {
146
- console.log(`\n${chalk.yellow('⚠️ Comando proposto:')} ${chalk.bold(command)}`);
153
+ console.log(chalk.yellow(`\n Comando proposto: ${chalk.bold(command)}`));
147
154
 
148
155
  if (!editState.autoAccept) {
149
156
  const { approve } = await inquirer.prompt([{
package/src/interface.js CHANGED
@@ -1,6 +1,5 @@
1
1
  import chalk from 'chalk';
2
2
  import figlet from 'figlet';
3
- import inquirer from 'inquirer';
4
3
  import { marked } from 'marked';
5
4
  import TerminalRenderer from 'marked-terminal';
6
5
  import ora from 'ora';
@@ -21,12 +20,13 @@ const __dirname = path.dirname(__filename);
21
20
  const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, '../package.json'), 'utf-8'));
22
21
  const version = pkg.version;
23
22
 
24
- // CONFIGURAÇÃO DO RENDERIZADOR (CORREÇÃO DEFINITIVA DO <P>)
23
+ // CONFIGURAÇÃO DO RENDERIZADOR - Ignora HTML e foca no Terminal
25
24
  const terminalRenderer = new TerminalRenderer({
26
25
  heading: chalk.hex('#c084fc').bold,
27
26
  code: chalk.hex('#00ff9d'),
28
27
  strong: chalk.bold,
29
28
  em: chalk.italic,
29
+ html: () => '', // DROP TOTAL DE QUALQUER TAG HTML
30
30
  });
31
31
 
32
32
  marked.setOptions({ renderer: terminalRenderer });
@@ -65,17 +65,27 @@ const i18n = {
65
65
  }
66
66
  };
67
67
 
68
- function getFilesForCompletion(partialPath) {
68
+ /**
69
+ * Coleta arquivos para preview e completion
70
+ */
71
+ function getFilesForPreview(partialPath) {
69
72
  try {
70
- const dir = path.dirname(partialPath.startsWith('@') ? partialPath.slice(1) : partialPath) || '.';
71
- const base = path.basename(partialPath.startsWith('@') ? partialPath.slice(1) : partialPath);
72
- const files = fs.readdirSync(path.resolve(process.cwd(), dir));
73
+ const cleanPartial = partialPath.startsWith('@') ? partialPath.slice(1) : partialPath;
74
+ const dir = path.dirname(cleanPartial) === '.' && !cleanPartial.includes('/') ? '.' : path.dirname(cleanPartial);
75
+ const base = path.basename(cleanPartial);
76
+
77
+ const searchDir = path.resolve(process.cwd(), dir);
78
+ if (!fs.existsSync(searchDir)) return [];
79
+
80
+ const files = fs.readdirSync(searchDir);
73
81
  return files
74
- .filter(f => f.startsWith(base) && !f.startsWith('.') && f !== 'node_modules')
75
- .map(f => path.join(dir, f));
76
- } catch (e) {
77
- return [];
78
- }
82
+ .filter(f => (base === '' || f.startsWith(base)) && !f.startsWith('.') && f !== 'node_modules')
83
+ .map(f => {
84
+ const rel = path.join(dir === '.' ? '' : dir, f);
85
+ const isDir = fs.statSync(path.join(searchDir, f)).isDirectory();
86
+ return { name: rel, isDir };
87
+ });
88
+ } catch (e) { return []; }
79
89
  }
80
90
 
81
91
  function cleanAIResponse(text) {
@@ -132,23 +142,58 @@ export async function startInteractive() {
132
142
  const words = line.split(' ');
133
143
  const lastWord = words[words.length - 1];
134
144
  if (lastWord.startsWith('@')) {
135
- const hits = getFilesForCompletion(lastWord);
136
- return [hits.map(h => `@${h}`), lastWord];
145
+ const hits = getFilesForPreview(lastWord).map(h => `@${h.name}`);
146
+ return [hits, lastWord];
137
147
  }
138
148
  return [[], line];
139
149
  }
140
150
  });
141
151
 
142
- // Handler de saída
152
+ let currentPreviewLines = 0;
153
+
154
+ const clearPreview = () => {
155
+ for (let i = 0; i < currentPreviewLines; i++) {
156
+ readline.moveCursor(process.stdout, 0, 1);
157
+ readline.clearLine(process.stdout, 0);
158
+ }
159
+ if (currentPreviewLines > 0) {
160
+ readline.moveCursor(process.stdout, 0, -currentPreviewLines);
161
+ }
162
+ currentPreviewLines = 0;
163
+ };
164
+
165
+ const showPreview = (line) => {
166
+ clearPreview();
167
+ const words = line.split(' ');
168
+ const lastWord = words[words.length - 1];
169
+
170
+ if (lastWord.startsWith('@')) {
171
+ const files = getFilesForPreview(lastWord);
172
+ if (files.length > 0) {
173
+ process.stdout.write('\n');
174
+ files.slice(0, 10).forEach(f => {
175
+ process.stdout.write(gray(` ${f.isDir ? '📁' : '📄'} ${f.name}\n`));
176
+ });
177
+ currentPreviewLines = Math.min(files.length, 10) + 1;
178
+ readline.moveCursor(process.stdout, 0, -currentPreviewLines);
179
+ readline.cursorTo(process.stdout, rl.line.length + rl.getPrompt().length);
180
+ }
181
+ }
182
+ };
183
+
184
+ // Monitora digitação em tempo real
185
+ process.stdin.on('keypress', (s, key) => {
186
+ // Pequeno delay para o readline atualizar a linha interna
187
+ setImmediate(() => showPreview(rl.line));
188
+ });
189
+
143
190
  rl.on('SIGINT', () => {
144
- exitCounter++;
145
- if (exitCounter === 1) {
191
+ if (exitCounter === 0) {
192
+ exitCounter++;
146
193
  console.log(`\n${gray(t.exitHint)}`);
147
- if (exitTimer) clearTimeout(exitTimer);
148
194
  exitTimer = setTimeout(() => { exitCounter = 0; }, 2000);
149
195
  displayPrompt();
150
196
  } else {
151
- console.log(lavender('\n👋 BIMMO encerrando sessão.\n'));
152
197
  process.exit(0);
153
198
  }
154
199
  });
@@ -158,7 +203,7 @@ export async function startInteractive() {
158
203
  let modeLabel = `[${currentMode.toUpperCase()}]`;
159
204
  if (currentMode === 'edit') modeLabel = editState.autoAccept ? '[EDIT(AUTO)]' : '[EDIT(MANUAL)]';
160
205
 
161
- console.log(`\n${gray(`📁 ${process.cwd()}`)}`);
206
+ console.log(`${gray(`📁 ${process.cwd()}`)}`);
162
207
  rl.setPrompt(lavender.bold(personaLabel) + (currentMode === 'edit' ? chalk.red.bold(modeLabel) : lavender.bold(modeLabel)) + green(' > '));
163
208
  rl.prompt();
164
209
  };
@@ -166,39 +211,39 @@ export async function startInteractive() {
166
211
  displayPrompt();
167
212
 
168
213
  rl.on('line', async (input) => {
214
+ clearPreview();
169
215
  const rawInput = input.trim();
170
216
  const cmd = rawInput.toLowerCase();
171
217
 
172
218
  if (rawInput === '') { displayPrompt(); return; }
173
219
 
174
- // COMANDOS INTERNOS
175
220
  if (cmd === '/exit' || cmd === 'sair') process.exit(0);
176
221
  if (cmd === '/chat') { currentMode = 'chat'; displayPrompt(); return; }
177
222
  if (cmd === '/plan') { currentMode = 'plan'; displayPrompt(); return; }
178
223
  if (cmd === '/edit' || cmd === '/edit manual') { currentMode = 'edit'; editState.autoAccept = false; displayPrompt(); return; }
179
224
  if (cmd === '/edit auto') { currentMode = 'edit'; editState.autoAccept = true; displayPrompt(); return; }
180
-
181
225
  if (cmd === '/clear') { resetMessages(); console.clear(); displayPrompt(); return; }
182
226
  if (cmd === '/help') { console.log(gray(t.help)); displayPrompt(); return; }
183
-
227
+
184
228
  if (cmd === '/init') {
185
- const bimmoRcPath = path.join(process.cwd(), '.bimmorc.json');
186
- const initialConfig = { projectName: path.basename(process.cwd()), rules: ["Clean code"], ignorePatterns: ["node_modules"] };
187
- fs.writeFileSync(bimmoRcPath, JSON.stringify(initialConfig, null, 2));
188
- console.log(green(`\n✅ .bimmorc.json criado.`));
229
+ console.log(chalk.cyan('\n🚀 Analisando projeto para gerar .bimmorc.json inteligente...\n'));
230
+ const initPrompt = `Analise a estrutura atual deste projeto e crie um arquivo chamado .bimmorc.json na raiz com nome, regras, stack e arquitetura. Use write_file.`;
231
+
232
+ const controller = new AbortController();
233
+ const spinner = ora({ text: lavender(`${t.thinking}`), color: 'red' }).start();
234
+ try {
235
+ const res = await provider.sendMessage([...messages, { role: 'user', content: initPrompt }], { signal: controller.signal });
236
+ spinner.stop();
237
+ console.log(marked.parse(cleanAIResponse(res)));
238
+ } catch (e) { spinner.stop(); console.error(chalk.red(e.message)); }
189
239
  resetMessages();
190
240
  displayPrompt();
191
241
  return;
192
242
  }
193
243
 
194
244
  if (cmd === '/config') {
195
- rl.pause();
196
- await configure();
197
- config = getConfig();
198
- provider = createProvider(config);
199
- rl.resume();
200
- displayPrompt();
201
- return;
245
+ rl.pause(); await configure(); config = getConfig(); provider = createProvider(config); rl.resume();
246
+ displayPrompt(); return;
202
247
  }
203
248
 
204
249
  if (cmd.startsWith('/switch ')) {
@@ -228,13 +273,15 @@ export async function startInteractive() {
228
273
  // PROCESSAMENTO IA
229
274
  const controller = new AbortController();
230
275
  const abortHandler = () => controller.abort();
276
+
277
+ // Remove temporariamente o listener de preview para não conflitar com a saída da IA
278
+ process.stdin.removeAllListeners('keypress');
231
279
  process.on('SIGINT', abortHandler);
232
280
 
233
281
  let modeInstr = "";
234
282
  if (currentMode === 'plan') modeInstr = "\n[MODO PLAN] Apenas analise.";
235
283
  else if (currentMode === 'edit') modeInstr = `\n[MODO EDIT] Auto-Accept: ${editState.autoAccept ? 'ON' : 'OFF'}`;
236
284
 
237
- // Processar anexos @
238
285
  const processedContent = [];
239
286
  const words = rawInput.split(' ');
240
287
  for (const word of words) {
@@ -248,26 +295,27 @@ export async function startInteractive() {
248
295
  }
249
296
 
250
297
  messages.push({ role: 'user', content: [...processedContent, { type: 'text', text: modeInstr }] });
251
-
252
298
  const spinner = ora({ text: lavender(`${t.thinking} (Ctrl+C para parar)`), color: currentMode === 'edit' ? 'red' : 'magenta' }).start();
253
299
 
254
300
  try {
255
301
  let responseText = await provider.sendMessage(messages, { signal: controller.signal });
256
302
  spinner.stop();
257
-
258
303
  const cleanedText = cleanAIResponse(responseText);
259
304
  messages.push({ role: 'assistant', content: responseText });
260
-
261
305
  console.log(`\n${lavender('bimmo ')}${currentMode.toUpperCase()}`);
262
306
  console.log(lavender('─'.repeat(50)));
263
- console.log(marked.parse(cleanedText)); // Usamos parse para garantir o renderer terminal
264
- console.log(gray('─'.repeat(50)));
307
+ process.stdout.write(marked.parse(cleanedText));
308
+ console.log(gray('\n' + '─'.repeat(50)));
265
309
  } catch (err) {
266
310
  spinner.stop();
267
311
  if (controller.signal.aborted) { console.log(yellow(`\n⚠️ ${t.interrupted}`)); messages.pop(); }
268
312
  else { console.error(chalk.red(`\n✖ Erro: ${err.message}`)); }
269
313
  } finally {
270
314
  process.removeListener('SIGINT', abortHandler);
315
+ // Reativa o listener de preview
316
+ process.stdin.on('keypress', (s, key) => {
317
+ setImmediate(() => showPreview(rl.line));
318
+ });
271
319
  displayPrompt();
272
320
  }
273
321
  });
@@ -18,6 +18,7 @@ export class AnthropicProvider extends BaseProvider {
18
18
  type: 'image',
19
19
  source: { type: 'base64', media_type: part.mimeType, data: part.data }
20
20
  };
21
+ return part;
21
22
  });
22
23
  }
23
24
 
@@ -51,8 +52,6 @@ export class AnthropicProvider extends BaseProvider {
51
52
 
52
53
  if (tool) {
53
54
  if (options.signal?.aborted) throw new Error('Abortado pelo usuário');
54
-
55
- console.log(`\n ${tool.name === 'search_internet' ? '🌐' : '🛠️'} Executando: ${tool.name}...`);
56
55
  const result = await tool.execute(toolUse.input);
57
56
 
58
57
  const nextMessages = [
@@ -15,6 +15,7 @@ export class GeminiProvider extends BaseProvider {
15
15
  if (part.type === 'image') return {
16
16
  inlineData: { mimeType: part.mimeType, data: part.data }
17
17
  };
18
+ return part;
18
19
  });
19
20
  }
20
21
 
@@ -58,7 +59,6 @@ export class GeminiProvider extends BaseProvider {
58
59
 
59
60
  const tool = tools.find(t => t.name === call.functionCall.name);
60
61
  if (tool) {
61
- console.log(`\n ${tool.name === 'search_internet' ? '🌐' : '🛠️'} Executando: ${tool.name}...`);
62
62
  const toolResult = await tool.execute(call.functionCall.args);
63
63
 
64
64
  const resultResponse = await chat.sendMessage([{
@@ -21,12 +21,8 @@ export class OpenAIProvider extends BaseProvider {
21
21
 
22
22
  formatMessages(messages) {
23
23
  return messages.map(msg => {
24
- // Se content for string ou null (comum em tool calls), retorna como está
25
- if (typeof msg.content === 'string' || msg.content === null) {
26
- return msg;
27
- }
24
+ if (typeof msg.content === 'string' || msg.content === null) return msg;
28
25
 
29
- // Se for um array (multimodal), processa as partes
30
26
  if (Array.isArray(msg.content)) {
31
27
  const content = msg.content.map(part => {
32
28
  if (part.type === 'text') return { type: 'text', text: part.text };
@@ -34,11 +30,10 @@ export class OpenAIProvider extends BaseProvider {
34
30
  type: 'image_url',
35
31
  image_url: { url: `data:${part.mimeType};base64,${part.data}` }
36
32
  };
37
- return part; // Mantém outras partes (como tool_result se houver)
33
+ return part;
38
34
  });
39
35
  return { ...msg, content };
40
36
  }
41
-
42
37
  return msg;
43
38
  });
44
39
  }
@@ -77,7 +72,6 @@ export class OpenAIProvider extends BaseProvider {
77
72
 
78
73
  const tool = tools.find(t => t.name === toolCall.function.name);
79
74
  if (tool) {
80
- console.log(`\n ${tool.name === 'search_internet' ? '🌐' : '🛠️'} Executando: ${tool.name}...`);
81
75
  const args = JSON.parse(toolCall.function.arguments);
82
76
  const result = await tool.execute(args);
83
77