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