bimmo-cli 2.1.3 → 2.2.1

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.1.3",
4
- "description": "🌿 Plataforma de IA universal com Modo Normal, Agentes e Swarms. Suporte a Diffs coloridos, Auto-Edit (Auto/Manual) e Contexto Inteligente.",
3
+ "version": "2.2.1",
4
+ "description": "🌿 Plataforma de IA universal com Modo Normal, Agentes e Swarms. Suporte a Autocomplete de arquivos, Diffs coloridos e Contexto Inteligente.",
5
5
  "bin": {
6
6
  "bimmo": "bin/bimmo"
7
7
  },
@@ -18,8 +18,7 @@
18
18
  "zai",
19
19
  "agent",
20
20
  "swarm",
21
- "diff",
22
- "multimodal",
21
+ "autocomplete",
23
22
  "terminal"
24
23
  ],
25
24
  "author": "Judah",
@@ -42,7 +41,9 @@
42
41
  "conf": "^13.0.0",
43
42
  "diff": "^7.0.0",
44
43
  "figlet": "^1.7.0",
44
+ "fuzzy": "^0.1.3",
45
45
  "inquirer": "^10.1.0",
46
+ "inquirer-autocomplete-prompt": "^3.0.1",
46
47
  "marked": "^14.0.0",
47
48
  "marked-terminal": "^7.0.0",
48
49
  "mime-types": "^2.1.35",
package/src/config.js CHANGED
@@ -30,6 +30,7 @@ export async function configure() {
30
30
  { name: 'Criar novo perfil de IA', value: 'create' },
31
31
  { name: 'Selecionar perfil ativo', value: 'select' },
32
32
  { name: 'Gerenciar Agentes Especialistas', value: 'agents' },
33
+ { name: 'Configurar Idioma', value: 'language' },
33
34
  { name: 'Configurar chave Tavily', value: 'tavily' },
34
35
  { name: 'Sair', value: 'exit' }
35
36
  ]
@@ -38,6 +39,22 @@ export async function configure() {
38
39
 
39
40
  if (action === 'exit') return;
40
41
 
42
+ if (action === 'language') {
43
+ const { lang } = await inquirer.prompt([{
44
+ type: 'list',
45
+ name: 'lang',
46
+ message: 'Escolha o idioma do sistema:',
47
+ choices: [
48
+ { name: 'Português (Brasil)', value: 'pt-BR' },
49
+ { name: 'English', value: 'en-US' }
50
+ ],
51
+ default: config.get('language') || 'pt-BR'
52
+ }]);
53
+ config.set('language', lang);
54
+ console.log(chalk.green(`✓ Idioma definido para: ${lang}`));
55
+ return;
56
+ }
57
+
41
58
  if (action === 'agents') return configureAgents();
42
59
 
43
60
  if (action === 'tavily') {
package/src/interface.js CHANGED
@@ -1,6 +1,8 @@
1
1
  import chalk from 'chalk';
2
2
  import figlet from 'figlet';
3
3
  import inquirer from 'inquirer';
4
+ import autocompletePrompt from 'inquirer-autocomplete-prompt';
5
+ import fuzzy from 'fuzzy';
4
6
  import { marked } from 'marked';
5
7
  import TerminalRenderer from 'marked-terminal';
6
8
  import ora from 'ora';
@@ -16,12 +18,14 @@ import { getProjectContext } from './project-context.js';
16
18
  import { SwarmOrchestrator } from './orchestrator.js';
17
19
  import { editState } from './agent.js';
18
20
 
21
+ // Registrar plugin de autocomplete
22
+ inquirer.registerPrompt('autocomplete', autocompletePrompt);
23
+
19
24
  const __filename = fileURLToPath(import.meta.url);
20
25
  const __dirname = path.dirname(__filename);
21
26
  const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, '../package.json'), 'utf-8'));
22
27
  const version = pkg.version;
23
28
 
24
- // Configuração do renderizador
25
29
  marked.use(new TerminalRenderer({
26
30
  heading: chalk.hex('#c084fc').bold,
27
31
  code: chalk.hex('#00ff9d'),
@@ -38,6 +42,28 @@ let activePersona = null;
38
42
  let exitCounter = 0;
39
43
  let exitTimer = null;
40
44
 
45
+ /**
46
+ * Procura arquivos e diretórios recursivamente para o autocomplete do @
47
+ */
48
+ function getFiles(dir, filter = '') {
49
+ const results = [];
50
+ try {
51
+ const list = fs.readdirSync(dir);
52
+ for (const file of list) {
53
+ if (file === 'node_modules' || file === '.git') continue;
54
+ const fullPath = path.join(dir, file);
55
+ const relPath = path.relative(process.cwd(), fullPath);
56
+ if (relPath.toLowerCase().includes(filter.toLowerCase())) {
57
+ results.push(relPath);
58
+ }
59
+ if (fs.statSync(fullPath).isDirectory()) {
60
+ // Limitamos profundidade para performance se necessário
61
+ }
62
+ }
63
+ } catch (e) {}
64
+ return results;
65
+ }
66
+
41
67
  async function processInput(input) {
42
68
  const parts = input.split(' ');
43
69
  const processedContent = [];
@@ -97,21 +123,17 @@ function getModeStyle() {
97
123
  }
98
124
  }
99
125
 
100
- /**
101
- * LIMPEZA BRUTA DE HTML
102
- * Remove tags como <p>, <br>, <div> e qualquer outra tag que venha da IA.
103
- */
104
126
  function cleanAIResponse(text) {
105
127
  if (!text) return "";
106
128
  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 &
129
+ .replace(/<br\s*\/?>/gi, '\n')
130
+ .replace(/<\/p>/gi, '\n\n')
131
+ .replace(/<\/div>/gi, '\n')
132
+ .replace(/<[^>]*>?/gm, '')
133
+ .replace(/&nbsp;/g, ' ')
134
+ .replace(/&lt;/g, '<')
135
+ .replace(/&gt;/g, '>')
136
+ .replace(/&amp;/g, '&')
115
137
  .trim();
116
138
  }
117
139
 
@@ -146,6 +168,7 @@ export async function startInteractive() {
146
168
  console.log(lavender(` v${version} `.padStart(60, '─')));
147
169
  console.log(green(` Perfil: ${bold(config.activeProfile || 'Padrão')} • IA: ${bold(config.provider.toUpperCase())}`));
148
170
  console.log(green(` Modelo: ${bold(config.model)}`));
171
+ console.log(gray(` 📁 ${process.cwd()}`));
149
172
  console.log(gray(' /chat | /plan | /edit | /swarm | /use [agente] | /help'));
150
173
  console.log(lavender('─'.repeat(60)) + '\n');
151
174
 
@@ -170,22 +193,66 @@ export async function startInteractive() {
170
193
  while (true) {
171
194
  const modeIndicator = getModeStyle();
172
195
  let input;
196
+
173
197
  try {
174
- const answers = await inquirer.prompt([{ type: 'input', name: 'input', message: modeIndicator + green('Você'), prefix: '→' }]);
198
+ // Usamos autocomplete para permitir navegação de arquivos com @
199
+ const answers = await inquirer.prompt([
200
+ {
201
+ type: 'autocomplete',
202
+ name: 'input',
203
+ message: modeIndicator + green('>'),
204
+ prefix: '',
205
+ source: async (answersSoFar, inputSearch) => {
206
+ const currentInput = inputSearch || '';
207
+
208
+ // Se o usuário digitar @, oferecemos arquivos
209
+ if (currentInput.includes('@')) {
210
+ const lastWord = currentInput.split(' ').pop();
211
+ if (lastWord.startsWith('@')) {
212
+ const searchPath = lastWord.slice(1);
213
+ const files = getFiles(process.cwd(), searchPath);
214
+ return files.map(f => ({
215
+ name: `@${f} ${fs.statSync(f).isDirectory() ? '(DIR)' : '(FILE)'}`,
216
+ value: currentInput.substring(0, currentInput.lastIndexOf('@')) + '@' + f
217
+ }));
218
+ }
219
+ }
220
+
221
+ // Caso contrário, apenas retorna o que ele está digitando como única opção
222
+ // para não atrapalhar o chat normal
223
+ return [currentInput];
224
+ }
225
+ }
226
+ ]);
175
227
  input = answers.input;
176
228
  } catch (e) { continue; }
177
229
 
230
+ // Diretório constante
178
231
  console.log(gray(` 📁 ${process.cwd()}`));
232
+
179
233
  const rawInput = input.trim();
180
234
  const cmd = rawInput.toLowerCase();
181
235
 
182
- if (cmd === '/exit' || cmd === 'exit' || cmd === 'sair') { console.log(lavender('\n👋 BIMMO encerrando sessão. Até logo!\n')); process.exit(0); }
236
+ if (cmd === '/exit' || cmd === 'exit' || cmd === 'sair') { process.exit(0); }
183
237
  if (cmd === '/chat') { currentMode = 'chat'; console.log(lavender('✓ Modo CHAT.\n')); continue; }
184
238
  if (cmd === '/plan') { currentMode = 'plan'; console.log(yellow('✓ Modo PLAN.\n')); continue; }
185
239
  if (cmd === '/edit') { currentMode = 'edit'; console.log(chalk.red(`⚠️ Modo EDIT ativado.\n`)); continue; }
186
240
  if (cmd === '/edit auto') { currentMode = 'edit'; editState.autoAccept = true; console.log(chalk.red('⚠️ Modo EDIT (AUTO) ativado.\n')); continue; }
187
241
  if (cmd === '/edit manual') { currentMode = 'edit'; editState.autoAccept = false; console.log(chalk.red('⚠️ Modo EDIT (MANUAL) ativado.\n')); continue; }
188
242
 
243
+ if (cmd === '/init') {
244
+ const bimmoRcPath = path.join(process.cwd(), '.bimmorc.json');
245
+ if (fs.existsSync(bimmoRcPath)) {
246
+ const { overwrite } = await inquirer.prompt([{ type: 'confirm', name: 'overwrite', message: 'O arquivo .bimmorc.json já existe. Sobrescrever?', default: false }]);
247
+ if (!overwrite) continue;
248
+ }
249
+ const initialConfig = { projectName: path.basename(process.cwd()), rules: ["Siga as convenções."], ignorePatterns: ["node_modules", ".git"] };
250
+ fs.writeFileSync(bimmoRcPath, JSON.stringify(initialConfig, null, 2));
251
+ console.log(green(`\n✅ .bimmorc.json criado com sucesso.\n`));
252
+ resetMessages();
253
+ continue;
254
+ }
255
+
189
256
  if (cmd.startsWith('/switch ')) {
190
257
  const profileName = rawInput.split(' ')[1];
191
258
  if (profileName && switchProfile(profileName)) {
@@ -199,28 +266,22 @@ export async function startInteractive() {
199
266
  if (cmd.startsWith('/use ')) {
200
267
  const agentName = rawInput.split(' ')[1];
201
268
  const agents = config.agents || {};
202
- if (agentName === 'normal' || agentName === 'default') { activePersona = null; console.log(lavender(`\n✓ Voltando para o Modo Normal.\n`)); resetMessages(); continue; }
269
+ if (agentName === 'normal' || agentName === 'default') { activePersona = null; resetMessages(); continue; }
203
270
  if (agents[agentName]) {
204
271
  activePersona = agentName;
205
272
  const agent = agents[agentName];
206
273
  if (switchProfile(agent.profile)) { config = getConfig(); provider = createProvider(config); }
207
274
  currentMode = agent.mode || 'chat';
208
- console.log(green(`\n✓ Agora você está falando com o Agente: ${bold(agentName)}`));
275
+ console.log(green(`\n✓ Ativado Agente: ${bold(agentName)}`));
209
276
  resetMessages();
210
- } else { console.log(chalk.red(`\n✖ Agente "${agentName}" não encontrado.\n`)); }
277
+ } else { console.log(chalk.red(`\n✖ Agente não encontrado.\n`)); }
211
278
  continue;
212
279
  }
213
280
 
214
- if (cmd === '/clear') { resetMessages(); console.clear(); console.log(lavender('✓ Histórico limpo, contexto preservado.\n')); continue; }
281
+ if (cmd === '/clear') { resetMessages(); console.clear(); continue; }
215
282
 
216
283
  if (cmd === '/help') {
217
- console.log(gray(`
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
223
- `));
284
+ console.log(gray(`\nComandos:\n /chat | /plan | /edit [auto/manual] | /init\n /switch [nome] | /model [nome] | /use [agente]\n /config | /clear | @arquivo\n`));
224
285
  continue;
225
286
  }
226
287
 
@@ -245,10 +306,7 @@ Comandos:
245
306
  try {
246
307
  let responseText = await provider.sendMessage(messages, { signal: controller.signal });
247
308
  spinner.stop();
248
-
249
- // EXECUTA LIMPEZA BRUTA
250
309
  const cleanedText = cleanAIResponse(responseText);
251
-
252
310
  messages.push({ role: 'assistant', content: responseText });
253
311
  console.log('\n' + lavender('bimmo ') + getModeStyle());
254
312
  console.log(lavender('─'.repeat(50)));
@@ -256,7 +314,7 @@ Comandos:
256
314
  console.log(gray('─'.repeat(50)) + '\n');
257
315
  } catch (err) {
258
316
  spinner.stop();
259
- if (controller.signal.aborted || err.name === 'AbortError') { console.log(yellow('\n\n⚠️ Interrompido.\n')); messages.pop(); }
317
+ if (controller.signal.aborted || err.name === 'AbortError') { console.log(yellow('\n⚠️ Interrompido.\n')); messages.pop(); }
260
318
  else { console.error(chalk.red('\n✖ Erro:') + ' ' + err.message + '\n'); }
261
319
  } finally {
262
320
  process.removeListener('SIGINT', localInterruptHandler);