bimmo-cli 2.0.1 → 2.0.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 +2 -2
  2. package/src/interface.js +134 -92
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "bimmo-cli",
3
- "version": "2.0.1",
4
- "description": "🌿 Plataforma de Orquestração de Agentes IA (Swarms) com interface verde/lavanda, modo Auto-Edit e contexto inteligente.",
3
+ "version": "2.0.3",
4
+ "description": "🌿 Plataforma de IA universal com modo Normal, Agentes Especialistas e Enxames (Swarms). Suporte a Auto-Edit, Contexto Inteligente e interrupção inteligente.",
5
5
  "bin": {
6
6
  "bimmo": "bin/bimmo"
7
7
  },
package/src/interface.js CHANGED
@@ -25,7 +25,10 @@ const gray = chalk.gray;
25
25
  const bold = chalk.bold;
26
26
  const yellow = chalk.yellow;
27
27
 
28
- let currentMode = 'chat'; // 'chat', 'plan', 'edit'
28
+ let currentMode = 'chat';
29
+ let activePersona = null;
30
+ let exitCounter = 0;
31
+ let exitTimer = null;
29
32
 
30
33
  async function processInput(input) {
31
34
  const parts = input.split(' ');
@@ -93,10 +96,11 @@ async function processInput(input) {
93
96
  }
94
97
 
95
98
  function getModeStyle() {
99
+ const personaLabel = activePersona ? `[${activePersona.toUpperCase()}] ` : '';
96
100
  switch (currentMode) {
97
- case 'plan': return yellow.bold(' [PLAN] ');
98
- case 'edit': return chalk.red.bold(' [EDIT] ');
99
- default: return lavender.bold(' [CHAT] ');
101
+ case 'plan': return yellow.bold(`${personaLabel}[PLAN] `);
102
+ case 'edit': return chalk.red.bold(`${personaLabel}[EDIT] `);
103
+ default: return lavender.bold(`${personaLabel}[CHAT] `);
100
104
  }
101
105
  }
102
106
 
@@ -112,37 +116,66 @@ export async function startInteractive() {
112
116
 
113
117
  let provider = createProvider(config);
114
118
  const orchestrator = new SwarmOrchestrator(config);
115
- const messages = [];
119
+ let messages = [];
120
+
121
+ const resetMessages = () => {
122
+ messages = [];
123
+ const projectContext = getProjectContext();
124
+ messages.push({ role: 'system', content: projectContext });
125
+ if (activePersona) {
126
+ const agent = (config.agents || {})[activePersona];
127
+ if (agent) {
128
+ messages.push({ role: 'system', content: `Sua persona atual é: ${agent.name}. Sua tarefa: ${agent.role}` });
129
+ }
130
+ }
131
+ };
116
132
 
117
- const projectContext = getProjectContext();
118
- messages.push({
119
- role: 'system',
120
- content: projectContext
121
- });
133
+ resetMessages();
122
134
 
123
135
  console.clear();
124
136
  console.log(lavender(figlet.textSync('bimmo')));
125
137
  console.log(lavender('─'.repeat(60)));
126
- console.log(green(` Perfil Ativo: ${bold(config.activeProfile || 'Padrão')} (${config.provider.toUpperCase()})`));
138
+ console.log(green(` Perfil: ${bold(config.activeProfile || 'Padrão')} • IA: ${bold(config.provider.toUpperCase())}`));
127
139
  console.log(green(` Modelo: ${bold(config.model)}`));
128
- console.log(gray(' /chat | /plan | /edit | /swarm | /switch [perfil] | /model [novo] | /help'));
140
+ console.log(gray(' /chat | /plan | /edit | /swarm | /use [agente] | /help'));
129
141
  console.log(lavender('─'.repeat(60)) + '\n');
130
142
 
131
- console.log(lavender('👋 Olá! Sou seu agente BIMMO. No que posso atuar?\n'));
143
+ console.log(lavender('👋 Olá! Estou pronto. No que posso ajudar?\n'));
144
+
145
+ // Handler Global de SIGINT para o modo ocioso (Idle)
146
+ process.on('SIGINT', () => {
147
+ exitCounter++;
148
+ if (exitCounter === 1) {
149
+ console.log(gray('\n(Pressione Ctrl+C novamente para sair)'));
150
+ if (exitTimer) clearTimeout(exitTimer);
151
+ exitTimer = setTimeout(() => { exitCounter = 0; }, 2000);
152
+ } else {
153
+ console.log(lavender('\n👋 BIMMO encerrando sessão. Até logo!\n'));
154
+ process.exit(0);
155
+ }
156
+ });
132
157
 
133
158
  readline.emitKeypressEvents(process.stdin);
134
159
  if (process.stdin.isTTY) process.stdin.setRawMode(true);
135
160
 
136
161
  while (true) {
137
162
  const modeIndicator = getModeStyle();
138
- const { input } = await inquirer.prompt([
139
- {
140
- type: 'input',
141
- name: 'input',
142
- message: modeIndicator + green('Você'),
143
- prefix: '',
144
- }
145
- ]);
163
+ let input;
164
+
165
+ try {
166
+ const answers = await inquirer.prompt([
167
+ {
168
+ type: 'input',
169
+ name: 'input',
170
+ message: modeIndicator + green('Você'),
171
+ prefix: '→',
172
+ }
173
+ ]);
174
+ input = answers.input;
175
+ } catch (e) {
176
+ // Inquirer joga erro no Ctrl+C se não for tratado
177
+ continue;
178
+ }
146
179
 
147
180
  const rawInput = input.trim();
148
181
  const cmd = rawInput.toLowerCase();
@@ -161,28 +194,41 @@ export async function startInteractive() {
161
194
  if (profileName && switchProfile(profileName)) {
162
195
  config = getConfig();
163
196
  provider = createProvider(config);
164
- console.log(green(`\n✓ Trocado para o perfil "${bold(profileName)}"!`));
165
- console.log(gray(` IA: ${config.provider.toUpperCase()} | Modelo: ${config.model}\n`));
166
- } else {
167
- console.log(chalk.red(`\n✖ Perfil "${profileName}" não encontrado.\n`));
197
+ console.log(green(`\n✓ Perfil "${bold(profileName)}" ativado!`));
198
+ continue;
168
199
  }
200
+ console.log(chalk.red(`\n✖ Perfil não encontrado.\n`));
169
201
  continue;
170
202
  }
171
203
 
172
- if (cmd.startsWith('/model ')) {
173
- const newModel = rawInput.split(' ')[1];
174
- if (newModel) {
175
- updateActiveModel(newModel);
176
- config.model = newModel;
177
- provider = createProvider(config);
178
- console.log(green(`\n✓ Modelo atualizado para: ${bold(newModel)}\n`));
204
+ if (cmd.startsWith('/use ')) {
205
+ const agentName = rawInput.split(' ')[1];
206
+ const agents = config.agents || {};
207
+ if (agentName === 'normal' || agentName === 'default') {
208
+ activePersona = null;
209
+ console.log(lavender(`\n✓ Voltando para o Modo Normal.\n`));
210
+ resetMessages();
211
+ continue;
212
+ }
213
+ if (agents[agentName]) {
214
+ activePersona = agentName;
215
+ const agent = agents[agentName];
216
+ if (switchProfile(agent.profile)) {
217
+ config = getConfig();
218
+ provider = createProvider(config);
219
+ }
220
+ currentMode = agent.mode || 'chat';
221
+ console.log(green(`\n✓ Agora você está falando com o Agente: ${bold(agentName)}`));
222
+ console.log(gray(` Task: ${agent.role.substring(0, 100)}...\n`));
223
+ resetMessages();
224
+ } else {
225
+ console.log(chalk.red(`\n✖ Agente "${agentName}" não encontrado.\n`));
179
226
  }
180
227
  continue;
181
228
  }
182
229
 
183
230
  if (cmd === '/clear') {
184
- messages.length = 0;
185
- messages.push({ role: 'system', content: getProjectContext() });
231
+ resetMessages();
186
232
  console.clear();
187
233
  console.log(lavender('✓ Histórico limpo, contexto preservado.\n'));
188
234
  continue;
@@ -190,14 +236,18 @@ export async function startInteractive() {
190
236
 
191
237
  if (cmd === '/help') {
192
238
  console.log(gray(`
193
- Comandos Disponíveis:
239
+ Comandos de Modo:
194
240
  /chat /plan /edit → Mudar modo de operação
195
- /switch [nome] Mudar PERFIL (IA completa)
196
- /model [nome] Mudar apenas o MODELO atual
197
- /swarm → Configurar e rodar enxames de agentes
198
- /config → Gerenciar perfis e agentes
241
+ /use [agente] Usar um Agente Especialista
242
+ /use normal Voltar para o chat normal
243
+ /swarm → Rodar fluxos complexos
244
+
245
+ Gerenciamento:
246
+ /switch [nome] → Mudar perfil de IA completo
247
+ /model [nome] → Mudar modelo atual
248
+ /config → Perfis e Agentes
199
249
  /init → Inicializar .bimmorc.json
200
- @caminhoAnexar arquivos ou imagens
250
+ @arquivoLer arquivo ou imagem
201
251
  `));
202
252
  continue;
203
253
  }
@@ -207,56 +257,32 @@ Comandos Disponíveis:
207
257
  if (cmd === '/swarm') {
208
258
  const agents = config.agents || {};
209
259
  const agentList = Object.keys(agents);
210
-
211
260
  if (agentList.length < 2) {
212
- console.log(chalk.yellow('\nVocê precisa de pelo menos 2 Agentes configurados para rodar um Enxame.\nUse /config para criar Agentes.\n'));
261
+ console.log(chalk.yellow('\nCrie pelo menos 2 Agentes em /config primeiro.\n'));
213
262
  continue;
214
263
  }
215
-
216
264
  const { swarmAction } = await inquirer.prompt([{
217
265
  type: 'list',
218
266
  name: 'swarmAction',
219
- message: 'Ação de Enxame:',
220
- choices: ['Rodar Enxame Sequencial', 'Rodar Enxame Hierárquico', 'Voltar']
267
+ message: 'Tipo de Enxame:',
268
+ choices: ['Sequencial (A → B)', 'Hierárquico (Líder + Workers)', 'Voltar']
221
269
  }]);
222
-
223
270
  if (swarmAction === 'Voltar') continue;
224
-
225
- const { goal } = await inquirer.prompt([{ type: 'input', name: 'goal', message: 'Qual o objetivo final deste enxame?' }]);
226
-
227
- if (swarmAction === 'Rodar Enxame Sequencial') {
228
- const { selectedAgents } = await inquirer.prompt([{
229
- type: 'checkbox',
230
- name: 'selectedAgents',
231
- message: 'Selecione os agentes e a ordem (mínimo 2):',
232
- choices: agentList
233
- }]);
234
-
235
- if (selectedAgents.length < 2) {
236
- console.log(chalk.red('\nSelecione pelo menos 2 agentes.\n'));
237
- continue;
238
- }
239
-
240
- try {
241
- const finalResult = await orchestrator.runSequential(selectedAgents, goal);
242
- console.log(lavender('\n=== RESULTADO FINAL DO ENXAME ===\n'));
243
- console.log(marked(finalResult));
244
- } catch (e) {
245
- console.error(chalk.red(`\nErro no Enxame: ${e.message}`));
246
- }
247
- }
248
-
249
- if (swarmAction === 'Rodar Enxame Hierárquico') {
250
- const { manager } = await inquirer.prompt([{ type: 'list', name: 'manager', message: 'Selecione o Agente Líder (Manager):', choices: agentList }]);
251
- const { workers } = await inquirer.prompt([{ type: 'checkbox', name: 'workers', message: 'Selecione os Workers:', choices: agentList.filter(a => a !== manager) }]);
252
-
253
- try {
254
- const finalResult = await orchestrator.runHierarchical(manager, workers, goal);
255
- console.log(lavender('\n=== RESULTADO FINAL DO ENXAME ===\n'));
256
- console.log(marked(finalResult));
257
- } catch (e) {
258
- console.error(chalk.red(`\nErro no Enxame: ${e.message}`));
271
+ const { goal } = await inquirer.prompt([{ type: 'input', name: 'goal', message: 'Objetivo do enxame:' }]);
272
+
273
+ try {
274
+ let result;
275
+ if (swarmAction.includes('Sequencial')) {
276
+ const { selectedAgents } = await inquirer.prompt([{ type: 'checkbox', name: 'selectedAgents', message: 'Ordem dos agentes:', choices: agentList }]);
277
+ result = await orchestrator.runSequential(selectedAgents, goal);
278
+ } else {
279
+ const { manager } = await inquirer.prompt([{ type: 'list', name: 'manager', message: 'Líder:', choices: agentList }]);
280
+ const { workers } = await inquirer.prompt([{ type: 'checkbox', name: 'workers', message: 'Workers:', choices: agentList.filter(a => a !== manager) }]);
281
+ result = await orchestrator.runHierarchical(manager, workers, goal);
259
282
  }
283
+ console.log(lavender('\n=== RESULTADO FINAL ===\n') + marked(result));
284
+ } catch (e) {
285
+ console.error(chalk.red(`\nErro: ${e.message}`));
260
286
  }
261
287
  continue;
262
288
  }
@@ -264,14 +290,19 @@ Comandos Disponíveis:
264
290
  if (rawInput === '') continue;
265
291
 
266
292
  const controller = new AbortController();
267
- const interruptHandler = () => controller.abort();
268
- const keypressHandler = (str, key) => { if (key.name === 'escape' || (key.ctrl && key.name === 'c')) interruptHandler(); };
269
- process.on('SIGINT', interruptHandler);
270
- process.stdin.on('keypress', keypressHandler);
293
+
294
+ // Handler local para SIGINT durante o processamento da IA
295
+ const localInterruptHandler = () => {
296
+ controller.abort();
297
+ };
298
+
299
+ // Remove temporariamente o handler global de saída
300
+ process.removeAllListeners('SIGINT');
301
+ process.on('SIGINT', localInterruptHandler);
271
302
 
272
303
  let modeInstr = "";
273
- if (currentMode === 'plan') modeInstr = "\n[MODO PLAN] Descreva e analise, mas NÃO altere arquivos.";
274
- else if (currentMode === 'edit') modeInstr = "\n[MODO EDIT] Você tem permissão para usar write_file e run_command AGORA.";
304
+ if (currentMode === 'plan') modeInstr = "\n[MODO PLAN] Apenas analise.";
305
+ else if (currentMode === 'edit') modeInstr = "\n[MODO EDIT] Aplique as mudanças agora.";
275
306
 
276
307
  const content = await processInput(rawInput);
277
308
  messages.push({
@@ -280,7 +311,7 @@ Comandos Disponíveis:
280
311
  });
281
312
 
282
313
  const spinner = ora({
283
- text: lavender(`bimmo (${currentMode}) pensando... (Ctrl+C para interromper)`),
314
+ text: lavender(`bimmo pensando... (Ctrl+C para interromper)`),
284
315
  color: currentMode === 'edit' ? 'red' : 'magenta'
285
316
  }).start();
286
317
 
@@ -299,11 +330,22 @@ Comandos Disponíveis:
299
330
  console.log(yellow('\n\n⚠️ Operação interrompida pelo usuário.\n'));
300
331
  messages.pop();
301
332
  } else {
302
- console.error(chalk.red('\n✖ Erro Crítico:') + ' ' + err.message + '\n');
333
+ console.error(chalk.red('\n✖ Erro:') + ' ' + err.message + '\n');
303
334
  }
304
335
  } finally {
305
- process.off('SIGINT', interruptHandler);
306
- process.stdin.off('keypress', keypressHandler);
336
+ // Restaura o handler global de saída
337
+ process.removeListener('SIGINT', localInterruptHandler);
338
+ process.on('SIGINT', () => {
339
+ exitCounter++;
340
+ if (exitCounter === 1) {
341
+ console.log(gray('\n(Pressione Ctrl+C novamente para sair)'));
342
+ if (exitTimer) clearTimeout(exitTimer);
343
+ exitTimer = setTimeout(() => { exitCounter = 0; }, 2000);
344
+ } else {
345
+ console.log(lavender('\n👋 BIMMO encerrando sessão. Até logo!\n'));
346
+ process.exit(0);
347
+ }
348
+ });
307
349
  }
308
350
  }
309
- }
351
+ }