bimmo-cli 2.1.2 → 2.1.4
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 +75 -178
package/package.json
CHANGED
package/src/interface.js
CHANGED
|
@@ -21,11 +21,18 @@ 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 - Forçamos o escape de HTML para garantir que tags não apareçam
|
|
24
25
|
marked.use(new TerminalRenderer({
|
|
25
26
|
heading: chalk.hex('#c084fc').bold,
|
|
26
27
|
code: chalk.hex('#00ff9d'),
|
|
27
28
|
}));
|
|
28
29
|
|
|
30
|
+
marked.setOptions({
|
|
31
|
+
sanitize: true, // Depreciado mas ajuda em versões antigas
|
|
32
|
+
headerIds: false,
|
|
33
|
+
mangle: false
|
|
34
|
+
});
|
|
35
|
+
|
|
29
36
|
const green = chalk.hex('#00ff9d');
|
|
30
37
|
const lavender = chalk.hex('#c084fc');
|
|
31
38
|
const gray = chalk.gray;
|
|
@@ -49,21 +56,12 @@ async function processInput(input) {
|
|
|
49
56
|
const stats = fs.statSync(filePath);
|
|
50
57
|
if (stats.isFile()) {
|
|
51
58
|
const mimeType = mime.lookup(filePath) || 'application/octet-stream';
|
|
52
|
-
|
|
53
59
|
if (mimeType.startsWith('image/')) {
|
|
54
60
|
const base64Image = fs.readFileSync(filePath, { encoding: 'base64' });
|
|
55
|
-
processedContent.push({
|
|
56
|
-
type: 'image',
|
|
57
|
-
mimeType,
|
|
58
|
-
data: base64Image,
|
|
59
|
-
fileName: path.basename(filePath)
|
|
60
|
-
});
|
|
61
|
+
processedContent.push({ type: 'image', mimeType, data: base64Image, fileName: path.basename(filePath) });
|
|
61
62
|
} else {
|
|
62
63
|
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
|
-
});
|
|
64
|
+
processedContent.push({ type: 'text', text: `\n--- Arquivo: ${path.basename(filePath)} ---\n${textContent}\n--- Fim do arquivo ---\n` });
|
|
67
65
|
}
|
|
68
66
|
}
|
|
69
67
|
} else {
|
|
@@ -78,27 +76,19 @@ async function processInput(input) {
|
|
|
78
76
|
}
|
|
79
77
|
|
|
80
78
|
const hasImage = processedContent.some(c => c.type === 'image');
|
|
81
|
-
|
|
82
|
-
if (!hasImage) {
|
|
83
|
-
return processedContent.map(c => c.text).join(' ');
|
|
84
|
-
}
|
|
79
|
+
if (!hasImage) return processedContent.map(c => c.text).join(' ');
|
|
85
80
|
|
|
86
81
|
const finalContent = [];
|
|
87
82
|
let currentText = "";
|
|
88
|
-
|
|
89
83
|
for (const item of processedContent) {
|
|
90
84
|
if (item.type === 'text') {
|
|
91
85
|
currentText += (currentText ? " " : "") + item.text;
|
|
92
86
|
} else {
|
|
93
|
-
if (currentText) {
|
|
94
|
-
finalContent.push({ type: 'text', text: currentText });
|
|
95
|
-
currentText = "";
|
|
96
|
-
}
|
|
87
|
+
if (currentText) { finalContent.push({ type: 'text', text: currentText }); currentText = ""; }
|
|
97
88
|
finalContent.push(item);
|
|
98
89
|
}
|
|
99
90
|
}
|
|
100
91
|
if (currentText) finalContent.push({ type: 'text', text: currentText });
|
|
101
|
-
|
|
102
92
|
return finalContent;
|
|
103
93
|
}
|
|
104
94
|
|
|
@@ -113,6 +103,30 @@ function getModeStyle() {
|
|
|
113
103
|
}
|
|
114
104
|
}
|
|
115
105
|
|
|
106
|
+
/**
|
|
107
|
+
* LIMPEZA ABSOLUTA DE HTML
|
|
108
|
+
* Remove tags HTML antes da renderização Markdown.
|
|
109
|
+
*/
|
|
110
|
+
function cleanAIResponse(text) {
|
|
111
|
+
if (!text) return "";
|
|
112
|
+
|
|
113
|
+
let cleaned = text
|
|
114
|
+
.replace(/<br\s*\/?>/gi, '\n')
|
|
115
|
+
.replace(/<\/p>/gi, '\n\n')
|
|
116
|
+
.replace(/<\/div>/gi, '\n')
|
|
117
|
+
.replace(/<li>/gi, '* ')
|
|
118
|
+
.replace(/<\/li>/gi, '\n');
|
|
119
|
+
|
|
120
|
+
// Regex extremamente agressiva para remover qualquer tag restante
|
|
121
|
+
cleaned = cleaned.replace(/<[^>]*>?/gm, '');
|
|
122
|
+
|
|
123
|
+
// Decodifica entidades comuns
|
|
124
|
+
const entities = {
|
|
125
|
+
' ': ' ', '<': '<', '>': '>', '&': '&', '"': '"', ''': "'"
|
|
126
|
+
};
|
|
127
|
+
return cleaned.replace(/&[a-z0-9#]+;/gi, (match) => entities[match] || match).trim();
|
|
128
|
+
}
|
|
129
|
+
|
|
116
130
|
export async function startInteractive() {
|
|
117
131
|
let config = getConfig();
|
|
118
132
|
|
|
@@ -133,9 +147,7 @@ export async function startInteractive() {
|
|
|
133
147
|
messages.push({ role: 'system', content: projectContext });
|
|
134
148
|
if (activePersona) {
|
|
135
149
|
const agent = (config.agents || {})[activePersona];
|
|
136
|
-
if (agent) {
|
|
137
|
-
messages.push({ role: 'system', content: `Sua persona atual é: ${agent.name}. Sua tarefa: ${agent.role}` });
|
|
138
|
-
}
|
|
150
|
+
if (agent) messages.push({ role: 'system', content: `Sua persona atual é: ${agent.name}. Sua tarefa: ${agent.role}` });
|
|
139
151
|
}
|
|
140
152
|
};
|
|
141
153
|
|
|
@@ -164,234 +176,119 @@ export async function startInteractive() {
|
|
|
164
176
|
};
|
|
165
177
|
|
|
166
178
|
process.on('SIGINT', globalSigIntHandler);
|
|
167
|
-
|
|
168
179
|
readline.emitKeypressEvents(process.stdin);
|
|
169
180
|
if (process.stdin.isTTY) process.stdin.setRawMode(true);
|
|
170
181
|
|
|
171
182
|
while (true) {
|
|
172
183
|
const modeIndicator = getModeStyle();
|
|
173
184
|
let input;
|
|
174
|
-
|
|
175
185
|
try {
|
|
176
|
-
const answers = await inquirer.prompt([
|
|
177
|
-
{
|
|
178
|
-
type: 'input',
|
|
179
|
-
name: 'input',
|
|
180
|
-
message: modeIndicator + green('Você'),
|
|
181
|
-
prefix: '→',
|
|
182
|
-
}
|
|
183
|
-
]);
|
|
186
|
+
const answers = await inquirer.prompt([{ type: 'input', name: 'input', message: modeIndicator + green('Você'), prefix: '→' }]);
|
|
184
187
|
input = answers.input;
|
|
185
|
-
} catch (e) {
|
|
186
|
-
continue;
|
|
187
|
-
}
|
|
188
|
+
} catch (e) { continue; }
|
|
188
189
|
|
|
189
|
-
// Mostra o diretório atual logo abaixo do input do usuário
|
|
190
190
|
console.log(gray(` 📁 ${process.cwd()}`));
|
|
191
|
-
|
|
192
191
|
const rawInput = input.trim();
|
|
193
192
|
const cmd = rawInput.toLowerCase();
|
|
194
193
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
}
|
|
199
|
-
|
|
194
|
+
// COMANDOS CLI (Tratados antes de enviar para IA)
|
|
195
|
+
if (cmd === '/exit' || cmd === 'exit' || cmd === 'sair') { process.exit(0); }
|
|
196
|
+
|
|
200
197
|
if (cmd === '/chat') { currentMode = 'chat'; console.log(lavender('✓ Modo CHAT.\n')); continue; }
|
|
201
198
|
if (cmd === '/plan') { currentMode = 'plan'; console.log(yellow('✓ Modo PLAN.\n')); continue; }
|
|
202
199
|
|
|
203
|
-
if (cmd === '/edit') {
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
200
|
+
if (cmd === '/edit') { currentMode = 'edit'; console.log(chalk.red(`⚠️ Modo EDIT ativado.\n`)); continue; }
|
|
201
|
+
if (cmd === '/edit auto') { currentMode = 'edit'; editState.autoAccept = true; console.log(chalk.red('⚠️ Modo EDIT (AUTO) ativado.\n')); continue; }
|
|
202
|
+
if (cmd === '/edit manual') { currentMode = 'edit'; editState.autoAccept = false; console.log(chalk.red('⚠️ Modo EDIT (MANUAL) ativado.\n')); continue; }
|
|
203
|
+
|
|
204
|
+
if (cmd === '/init') {
|
|
205
|
+
const bimmoRcPath = path.join(process.cwd(), '.bimmorc.json');
|
|
206
|
+
if (fs.existsSync(bimmoRcPath)) {
|
|
207
|
+
const { overwrite } = await inquirer.prompt([{ type: 'confirm', name: 'overwrite', message: 'O arquivo .bimmorc.json já existe. Sobrescrever?', default: false }]);
|
|
208
|
+
if (!overwrite) continue;
|
|
209
|
+
}
|
|
210
|
+
const initialConfig = {
|
|
211
|
+
projectName: path.basename(process.cwd()),
|
|
212
|
+
rules: ["Siga as convenções existentes.", "Prefira código modular."],
|
|
213
|
+
ignorePatterns: ["node_modules", ".git"]
|
|
214
|
+
};
|
|
215
|
+
fs.writeFileSync(bimmoRcPath, JSON.stringify(initialConfig, null, 2));
|
|
216
|
+
console.log(green(`\n✅ .bimmorc.json criado com sucesso.\n`));
|
|
217
|
+
resetMessages();
|
|
218
218
|
continue;
|
|
219
219
|
}
|
|
220
220
|
|
|
221
221
|
if (cmd.startsWith('/switch ')) {
|
|
222
222
|
const profileName = rawInput.split(' ')[1];
|
|
223
223
|
if (profileName && switchProfile(profileName)) {
|
|
224
|
-
config = getConfig();
|
|
225
|
-
provider = createProvider(config);
|
|
224
|
+
config = getConfig(); provider = createProvider(config);
|
|
226
225
|
console.log(green(`\n✓ Perfil "${bold(profileName)}" ativado!`));
|
|
227
226
|
continue;
|
|
228
227
|
}
|
|
229
|
-
console.log(chalk.red(`\n✖ Perfil não encontrado.\n`));
|
|
230
|
-
continue;
|
|
228
|
+
console.log(chalk.red(`\n✖ Perfil não encontrado.\n`)); continue;
|
|
231
229
|
}
|
|
232
230
|
|
|
233
231
|
if (cmd.startsWith('/use ')) {
|
|
234
232
|
const agentName = rawInput.split(' ')[1];
|
|
235
233
|
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
|
-
}
|
|
234
|
+
if (agentName === 'normal' || agentName === 'default') { activePersona = null; resetMessages(); continue; }
|
|
242
235
|
if (agents[agentName]) {
|
|
243
236
|
activePersona = agentName;
|
|
244
237
|
const agent = agents[agentName];
|
|
245
|
-
if (switchProfile(agent.profile)) {
|
|
246
|
-
config = getConfig();
|
|
247
|
-
provider = createProvider(config);
|
|
248
|
-
}
|
|
238
|
+
if (switchProfile(agent.profile)) { config = getConfig(); provider = createProvider(config); }
|
|
249
239
|
currentMode = agent.mode || 'chat';
|
|
250
|
-
console.log(green(`\n✓
|
|
251
|
-
console.log(gray(` Task: ${agent.role.substring(0, 100)}...\n`));
|
|
240
|
+
console.log(green(`\n✓ Ativado Agente: ${bold(agentName)}`));
|
|
252
241
|
resetMessages();
|
|
253
|
-
} else {
|
|
254
|
-
console.log(chalk.red(`\n✖ Agente "${agentName}" não encontrado.\n`));
|
|
255
|
-
}
|
|
242
|
+
} else { console.log(chalk.red(`\n✖ Agente não encontrado.\n`)); }
|
|
256
243
|
continue;
|
|
257
244
|
}
|
|
258
245
|
|
|
259
|
-
if (cmd === '/clear') {
|
|
260
|
-
resetMessages();
|
|
261
|
-
console.clear();
|
|
262
|
-
console.log(lavender('✓ Histórico limpo, contexto preservado.\n'));
|
|
263
|
-
continue;
|
|
264
|
-
}
|
|
246
|
+
if (cmd === '/clear') { resetMessages(); console.clear(); continue; }
|
|
265
247
|
|
|
266
248
|
if (cmd === '/help') {
|
|
267
|
-
console.log(gray(`
|
|
268
|
-
Comandos de Modo:
|
|
269
|
-
/chat → Modo conversa
|
|
270
|
-
/plan → Modo planejamento
|
|
271
|
-
/edit [auto/manual] → Modo edição (padrão manual)
|
|
272
|
-
/use [agente] → Usar um Agente Especialista
|
|
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
|
|
282
|
-
`));
|
|
249
|
+
console.log(gray(`\nComandos:\n /chat | /plan | /edit [auto/manual] | /init\n /switch [nome] | /model [nome] | /use [agente]\n /config | /clear | @arquivo\n`));
|
|
283
250
|
continue;
|
|
284
251
|
}
|
|
285
252
|
|
|
286
253
|
if (cmd === '/config') { await configure(); config = getConfig(); provider = createProvider(config); continue; }
|
|
287
254
|
|
|
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
255
|
if (rawInput === '') continue;
|
|
346
256
|
|
|
257
|
+
// Enviar para a IA
|
|
347
258
|
const controller = new AbortController();
|
|
348
259
|
const localInterruptHandler = () => controller.abort();
|
|
349
|
-
|
|
350
|
-
// Switch de SIGINT para modo processamento
|
|
351
260
|
process.removeListener('SIGINT', globalSigIntHandler);
|
|
352
261
|
process.on('SIGINT', localInterruptHandler);
|
|
353
262
|
|
|
354
263
|
let modeInstr = "";
|
|
355
264
|
if (currentMode === 'plan') modeInstr = "\n[MODO PLAN] Apenas analise.";
|
|
356
|
-
else if (currentMode === 'edit') modeInstr = `\n[MODO EDIT]
|
|
265
|
+
else if (currentMode === 'edit') modeInstr = `\n[MODO EDIT] Auto-Accept: ${editState.autoAccept ? 'ON' : 'OFF'}`;
|
|
357
266
|
|
|
358
267
|
const content = await processInput(rawInput);
|
|
359
|
-
messages.push({
|
|
360
|
-
role: 'user',
|
|
361
|
-
content: typeof content === 'string' ? content + modeInstr : [...content, { type: 'text', text: modeInstr }]
|
|
362
|
-
});
|
|
268
|
+
messages.push({ role: 'user', content: typeof content === 'string' ? content + modeInstr : [...content, { type: 'text', text: modeInstr }] });
|
|
363
269
|
|
|
364
|
-
const spinner = ora({
|
|
365
|
-
text: lavender(`bimmo pensando... (Ctrl+C para interromper)`),
|
|
366
|
-
color: currentMode === 'edit' ? 'red' : 'magenta'
|
|
367
|
-
}).start();
|
|
270
|
+
const spinner = ora({ text: lavender(`bimmo pensando... (Ctrl+C para interromper)`), color: currentMode === 'edit' ? 'red' : 'magenta' }).start();
|
|
368
271
|
|
|
369
272
|
try {
|
|
370
273
|
let responseText = await provider.sendMessage(messages, { signal: controller.signal });
|
|
371
274
|
spinner.stop();
|
|
372
275
|
|
|
373
|
-
|
|
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
|
|
379
|
-
|
|
276
|
+
const cleanedText = cleanAIResponse(responseText);
|
|
380
277
|
messages.push({ role: 'assistant', content: responseText });
|
|
278
|
+
|
|
381
279
|
console.log('\n' + lavender('bimmo ') + getModeStyle());
|
|
382
280
|
console.log(lavender('─'.repeat(50)));
|
|
383
|
-
console.log(marked(cleanedText
|
|
281
|
+
console.log(marked(cleanedText));
|
|
384
282
|
console.log(gray('─'.repeat(50)) + '\n');
|
|
385
283
|
} catch (err) {
|
|
386
284
|
spinner.stop();
|
|
387
285
|
if (controller.signal.aborted || err.name === 'AbortError') {
|
|
388
|
-
console.log(yellow('\n
|
|
286
|
+
console.log(yellow('\n⚠️ Interrompido.\n'));
|
|
389
287
|
messages.pop();
|
|
390
288
|
} else {
|
|
391
289
|
console.error(chalk.red('\n✖ Erro:') + ' ' + err.message + '\n');
|
|
392
290
|
}
|
|
393
291
|
} finally {
|
|
394
|
-
// Restaura o modo global de saída
|
|
395
292
|
process.removeListener('SIGINT', localInterruptHandler);
|
|
396
293
|
process.on('SIGINT', globalSigIntHandler);
|
|
397
294
|
}
|