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.
- package/package.json +1 -1
- package/src/interface.js +49 -182
package/package.json
CHANGED
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(/ /g, ' ') // Limpa espaços HTML
|
|
112
|
+
.replace(/</g, '<') // Desescapa <
|
|
113
|
+
.replace(/>/g, '>') // Desescapa >
|
|
114
|
+
.replace(/&/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
|
-
|
|
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
|
|
269
|
-
/chat
|
|
270
|
-
/
|
|
271
|
-
/
|
|
272
|
-
/
|
|
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]
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
}
|