bimmo-cli 2.2.5 → 2.2.8
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 +2 -2
- package/src/agent.js +16 -9
- package/src/interface.js +87 -39
- package/src/providers/anthropic.js +1 -2
- package/src/providers/gemini.js +1 -1
- package/src/providers/openai.js +2 -8
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bimmo-cli",
|
|
3
|
-
"version": "2.2.
|
|
4
|
-
"description": "🌿 Plataforma de IA universal profissional com Agentes e Swarms. Suporte a Autocomplete, Diffs e Contexto Inteligente.",
|
|
3
|
+
"version": "2.2.8",
|
|
4
|
+
"description": "🌿 Plataforma de IA universal profissional com Agentes e Swarms. Suporte a Autocomplete real-time, Diffs coloridos e Contexto Inteligente.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"bimmo": "bin/bimmo"
|
|
7
7
|
},
|
package/src/agent.js
CHANGED
|
@@ -28,6 +28,7 @@ export const tools = [
|
|
|
28
28
|
},
|
|
29
29
|
execute: async ({ query }) => {
|
|
30
30
|
if (!tvly) return 'Erro: Chave de API da Tavily não configurada. Use /config para configurar.';
|
|
31
|
+
console.log(chalk.blue(`\n 🌐 Pesquisando na web: ${chalk.bold(query)}...`));
|
|
31
32
|
const searchResponse = await tvly.search(query, {
|
|
32
33
|
searchDepth: 'advanced',
|
|
33
34
|
maxResults: 5
|
|
@@ -51,6 +52,7 @@ export const tools = [
|
|
|
51
52
|
},
|
|
52
53
|
execute: async ({ path: filePath }) => {
|
|
53
54
|
try {
|
|
55
|
+
console.log(chalk.blue(`\n 📖 Lendo arquivo: ${chalk.bold(filePath)}...`));
|
|
54
56
|
return fs.readFileSync(filePath, 'utf-8');
|
|
55
57
|
} catch (err) {
|
|
56
58
|
return `Erro ao ler arquivo: ${err.message}`;
|
|
@@ -77,7 +79,7 @@ export const tools = [
|
|
|
77
79
|
const differences = diff.diffLines(oldContent, content);
|
|
78
80
|
|
|
79
81
|
console.log(`\n${chalk.cyan('📝 Alterações propostas em:')} ${chalk.bold(filePath)}`);
|
|
80
|
-
console.log(chalk.gray('─'.repeat(
|
|
82
|
+
console.log(chalk.gray('─'.repeat(50)));
|
|
81
83
|
|
|
82
84
|
let hasChanges = false;
|
|
83
85
|
differences.forEach((part) => {
|
|
@@ -85,20 +87,25 @@ export const tools = [
|
|
|
85
87
|
const color = part.added ? chalk.green : part.removed ? chalk.red : chalk.gray;
|
|
86
88
|
const prefix = part.added ? '+' : part.removed ? '-' : ' ';
|
|
87
89
|
|
|
88
|
-
// Mostra apenas linhas com mudanças ou um pouco de contexto
|
|
89
90
|
if (part.added || part.removed) {
|
|
90
|
-
|
|
91
|
+
// Garante que cada linha tenha o prefixo
|
|
92
|
+
const lines = part.value.split('\n');
|
|
93
|
+
lines.forEach(line => {
|
|
94
|
+
if (line || part.value.endsWith('\n')) {
|
|
95
|
+
process.stdout.write(color(`${prefix} ${line}\n`));
|
|
96
|
+
}
|
|
97
|
+
});
|
|
91
98
|
} else {
|
|
92
99
|
// Mostra apenas as primeiras e últimas linhas de blocos sem mudança para encurtar
|
|
93
|
-
const lines = part.value.split('\n');
|
|
100
|
+
const lines = part.value.split('\n').filter(l => l.trim() !== "");
|
|
94
101
|
if (lines.length > 4) {
|
|
95
|
-
process.stdout.write(color(` ${lines[0]}\n ...\n ${lines[lines.length-
|
|
96
|
-
} else {
|
|
97
|
-
process.stdout.write(color(` ${
|
|
102
|
+
process.stdout.write(color(` ${lines[0]}\n ...\n ${lines[lines.length-1]}\n`));
|
|
103
|
+
} else if (lines.length > 0) {
|
|
104
|
+
lines.forEach(line => process.stdout.write(color(` ${line}\n`)));
|
|
98
105
|
}
|
|
99
106
|
}
|
|
100
107
|
});
|
|
101
|
-
console.log(chalk.gray('
|
|
108
|
+
console.log(chalk.gray('─'.repeat(50)));
|
|
102
109
|
|
|
103
110
|
if (!hasChanges) {
|
|
104
111
|
return "Nenhuma mudança detectada no arquivo.";
|
|
@@ -143,7 +150,7 @@ export const tools = [
|
|
|
143
150
|
},
|
|
144
151
|
execute: async ({ command }) => {
|
|
145
152
|
try {
|
|
146
|
-
console.log(
|
|
153
|
+
console.log(chalk.yellow(`\n ⚡ Comando proposto: ${chalk.bold(command)}`));
|
|
147
154
|
|
|
148
155
|
if (!editState.autoAccept) {
|
|
149
156
|
const { approve } = await inquirer.prompt([{
|
package/src/interface.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import figlet from 'figlet';
|
|
3
|
-
import inquirer from 'inquirer';
|
|
4
3
|
import { marked } from 'marked';
|
|
5
4
|
import TerminalRenderer from 'marked-terminal';
|
|
6
5
|
import ora from 'ora';
|
|
@@ -21,12 +20,13 @@ const __dirname = path.dirname(__filename);
|
|
|
21
20
|
const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, '../package.json'), 'utf-8'));
|
|
22
21
|
const version = pkg.version;
|
|
23
22
|
|
|
24
|
-
// CONFIGURAÇÃO DO RENDERIZADOR
|
|
23
|
+
// CONFIGURAÇÃO DO RENDERIZADOR - Ignora HTML e foca no Terminal
|
|
25
24
|
const terminalRenderer = new TerminalRenderer({
|
|
26
25
|
heading: chalk.hex('#c084fc').bold,
|
|
27
26
|
code: chalk.hex('#00ff9d'),
|
|
28
27
|
strong: chalk.bold,
|
|
29
28
|
em: chalk.italic,
|
|
29
|
+
html: () => '', // DROP TOTAL DE QUALQUER TAG HTML
|
|
30
30
|
});
|
|
31
31
|
|
|
32
32
|
marked.setOptions({ renderer: terminalRenderer });
|
|
@@ -65,17 +65,27 @@ const i18n = {
|
|
|
65
65
|
}
|
|
66
66
|
};
|
|
67
67
|
|
|
68
|
-
|
|
68
|
+
/**
|
|
69
|
+
* Coleta arquivos para preview e completion
|
|
70
|
+
*/
|
|
71
|
+
function getFilesForPreview(partialPath) {
|
|
69
72
|
try {
|
|
70
|
-
const
|
|
71
|
-
const
|
|
72
|
-
const
|
|
73
|
+
const cleanPartial = partialPath.startsWith('@') ? partialPath.slice(1) : partialPath;
|
|
74
|
+
const dir = path.dirname(cleanPartial) === '.' && !cleanPartial.includes('/') ? '.' : path.dirname(cleanPartial);
|
|
75
|
+
const base = path.basename(cleanPartial);
|
|
76
|
+
|
|
77
|
+
const searchDir = path.resolve(process.cwd(), dir);
|
|
78
|
+
if (!fs.existsSync(searchDir)) return [];
|
|
79
|
+
|
|
80
|
+
const files = fs.readdirSync(searchDir);
|
|
73
81
|
return files
|
|
74
|
-
.filter(f => f.startsWith(base) && !f.startsWith('.') && f !== 'node_modules')
|
|
75
|
-
.map(f =>
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
82
|
+
.filter(f => (base === '' || f.startsWith(base)) && !f.startsWith('.') && f !== 'node_modules')
|
|
83
|
+
.map(f => {
|
|
84
|
+
const rel = path.join(dir === '.' ? '' : dir, f);
|
|
85
|
+
const isDir = fs.statSync(path.join(searchDir, f)).isDirectory();
|
|
86
|
+
return { name: rel, isDir };
|
|
87
|
+
});
|
|
88
|
+
} catch (e) { return []; }
|
|
79
89
|
}
|
|
80
90
|
|
|
81
91
|
function cleanAIResponse(text) {
|
|
@@ -132,23 +142,58 @@ export async function startInteractive() {
|
|
|
132
142
|
const words = line.split(' ');
|
|
133
143
|
const lastWord = words[words.length - 1];
|
|
134
144
|
if (lastWord.startsWith('@')) {
|
|
135
|
-
const hits =
|
|
136
|
-
return [hits
|
|
145
|
+
const hits = getFilesForPreview(lastWord).map(h => `@${h.name}`);
|
|
146
|
+
return [hits, lastWord];
|
|
137
147
|
}
|
|
138
148
|
return [[], line];
|
|
139
149
|
}
|
|
140
150
|
});
|
|
141
151
|
|
|
142
|
-
|
|
152
|
+
let currentPreviewLines = 0;
|
|
153
|
+
|
|
154
|
+
const clearPreview = () => {
|
|
155
|
+
for (let i = 0; i < currentPreviewLines; i++) {
|
|
156
|
+
readline.moveCursor(process.stdout, 0, 1);
|
|
157
|
+
readline.clearLine(process.stdout, 0);
|
|
158
|
+
}
|
|
159
|
+
if (currentPreviewLines > 0) {
|
|
160
|
+
readline.moveCursor(process.stdout, 0, -currentPreviewLines);
|
|
161
|
+
}
|
|
162
|
+
currentPreviewLines = 0;
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
const showPreview = (line) => {
|
|
166
|
+
clearPreview();
|
|
167
|
+
const words = line.split(' ');
|
|
168
|
+
const lastWord = words[words.length - 1];
|
|
169
|
+
|
|
170
|
+
if (lastWord.startsWith('@')) {
|
|
171
|
+
const files = getFilesForPreview(lastWord);
|
|
172
|
+
if (files.length > 0) {
|
|
173
|
+
process.stdout.write('\n');
|
|
174
|
+
files.slice(0, 10).forEach(f => {
|
|
175
|
+
process.stdout.write(gray(` ${f.isDir ? '📁' : '📄'} ${f.name}\n`));
|
|
176
|
+
});
|
|
177
|
+
currentPreviewLines = Math.min(files.length, 10) + 1;
|
|
178
|
+
readline.moveCursor(process.stdout, 0, -currentPreviewLines);
|
|
179
|
+
readline.cursorTo(process.stdout, rl.line.length + rl.getPrompt().length);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
// Monitora digitação em tempo real
|
|
185
|
+
process.stdin.on('keypress', (s, key) => {
|
|
186
|
+
// Pequeno delay para o readline atualizar a linha interna
|
|
187
|
+
setImmediate(() => showPreview(rl.line));
|
|
188
|
+
});
|
|
189
|
+
|
|
143
190
|
rl.on('SIGINT', () => {
|
|
144
|
-
exitCounter
|
|
145
|
-
|
|
191
|
+
if (exitCounter === 0) {
|
|
192
|
+
exitCounter++;
|
|
146
193
|
console.log(`\n${gray(t.exitHint)}`);
|
|
147
|
-
if (exitTimer) clearTimeout(exitTimer);
|
|
148
194
|
exitTimer = setTimeout(() => { exitCounter = 0; }, 2000);
|
|
149
195
|
displayPrompt();
|
|
150
196
|
} else {
|
|
151
|
-
console.log(lavender('\n👋 BIMMO encerrando sessão.\n'));
|
|
152
197
|
process.exit(0);
|
|
153
198
|
}
|
|
154
199
|
});
|
|
@@ -158,7 +203,7 @@ export async function startInteractive() {
|
|
|
158
203
|
let modeLabel = `[${currentMode.toUpperCase()}]`;
|
|
159
204
|
if (currentMode === 'edit') modeLabel = editState.autoAccept ? '[EDIT(AUTO)]' : '[EDIT(MANUAL)]';
|
|
160
205
|
|
|
161
|
-
console.log(
|
|
206
|
+
console.log(`${gray(`📁 ${process.cwd()}`)}`);
|
|
162
207
|
rl.setPrompt(lavender.bold(personaLabel) + (currentMode === 'edit' ? chalk.red.bold(modeLabel) : lavender.bold(modeLabel)) + green(' > '));
|
|
163
208
|
rl.prompt();
|
|
164
209
|
};
|
|
@@ -166,39 +211,39 @@ export async function startInteractive() {
|
|
|
166
211
|
displayPrompt();
|
|
167
212
|
|
|
168
213
|
rl.on('line', async (input) => {
|
|
214
|
+
clearPreview();
|
|
169
215
|
const rawInput = input.trim();
|
|
170
216
|
const cmd = rawInput.toLowerCase();
|
|
171
217
|
|
|
172
218
|
if (rawInput === '') { displayPrompt(); return; }
|
|
173
219
|
|
|
174
|
-
// COMANDOS INTERNOS
|
|
175
220
|
if (cmd === '/exit' || cmd === 'sair') process.exit(0);
|
|
176
221
|
if (cmd === '/chat') { currentMode = 'chat'; displayPrompt(); return; }
|
|
177
222
|
if (cmd === '/plan') { currentMode = 'plan'; displayPrompt(); return; }
|
|
178
223
|
if (cmd === '/edit' || cmd === '/edit manual') { currentMode = 'edit'; editState.autoAccept = false; displayPrompt(); return; }
|
|
179
224
|
if (cmd === '/edit auto') { currentMode = 'edit'; editState.autoAccept = true; displayPrompt(); return; }
|
|
180
|
-
|
|
181
225
|
if (cmd === '/clear') { resetMessages(); console.clear(); displayPrompt(); return; }
|
|
182
226
|
if (cmd === '/help') { console.log(gray(t.help)); displayPrompt(); return; }
|
|
183
|
-
|
|
227
|
+
|
|
184
228
|
if (cmd === '/init') {
|
|
185
|
-
|
|
186
|
-
const
|
|
187
|
-
|
|
188
|
-
|
|
229
|
+
console.log(chalk.cyan('\n🚀 Analisando projeto para gerar .bimmorc.json inteligente...\n'));
|
|
230
|
+
const initPrompt = `Analise a estrutura atual deste projeto e crie um arquivo chamado .bimmorc.json na raiz com nome, regras, stack e arquitetura. Use write_file.`;
|
|
231
|
+
|
|
232
|
+
const controller = new AbortController();
|
|
233
|
+
const spinner = ora({ text: lavender(`${t.thinking}`), color: 'red' }).start();
|
|
234
|
+
try {
|
|
235
|
+
const res = await provider.sendMessage([...messages, { role: 'user', content: initPrompt }], { signal: controller.signal });
|
|
236
|
+
spinner.stop();
|
|
237
|
+
console.log(marked.parse(cleanAIResponse(res)));
|
|
238
|
+
} catch (e) { spinner.stop(); console.error(chalk.red(e.message)); }
|
|
189
239
|
resetMessages();
|
|
190
240
|
displayPrompt();
|
|
191
241
|
return;
|
|
192
242
|
}
|
|
193
243
|
|
|
194
244
|
if (cmd === '/config') {
|
|
195
|
-
rl.pause();
|
|
196
|
-
|
|
197
|
-
config = getConfig();
|
|
198
|
-
provider = createProvider(config);
|
|
199
|
-
rl.resume();
|
|
200
|
-
displayPrompt();
|
|
201
|
-
return;
|
|
245
|
+
rl.pause(); await configure(); config = getConfig(); provider = createProvider(config); rl.resume();
|
|
246
|
+
displayPrompt(); return;
|
|
202
247
|
}
|
|
203
248
|
|
|
204
249
|
if (cmd.startsWith('/switch ')) {
|
|
@@ -228,13 +273,15 @@ export async function startInteractive() {
|
|
|
228
273
|
// PROCESSAMENTO IA
|
|
229
274
|
const controller = new AbortController();
|
|
230
275
|
const abortHandler = () => controller.abort();
|
|
276
|
+
|
|
277
|
+
// Remove temporariamente o listener de preview para não conflitar com a saída da IA
|
|
278
|
+
process.stdin.removeAllListeners('keypress');
|
|
231
279
|
process.on('SIGINT', abortHandler);
|
|
232
280
|
|
|
233
281
|
let modeInstr = "";
|
|
234
282
|
if (currentMode === 'plan') modeInstr = "\n[MODO PLAN] Apenas analise.";
|
|
235
283
|
else if (currentMode === 'edit') modeInstr = `\n[MODO EDIT] Auto-Accept: ${editState.autoAccept ? 'ON' : 'OFF'}`;
|
|
236
284
|
|
|
237
|
-
// Processar anexos @
|
|
238
285
|
const processedContent = [];
|
|
239
286
|
const words = rawInput.split(' ');
|
|
240
287
|
for (const word of words) {
|
|
@@ -248,26 +295,27 @@ export async function startInteractive() {
|
|
|
248
295
|
}
|
|
249
296
|
|
|
250
297
|
messages.push({ role: 'user', content: [...processedContent, { type: 'text', text: modeInstr }] });
|
|
251
|
-
|
|
252
298
|
const spinner = ora({ text: lavender(`${t.thinking} (Ctrl+C para parar)`), color: currentMode === 'edit' ? 'red' : 'magenta' }).start();
|
|
253
299
|
|
|
254
300
|
try {
|
|
255
301
|
let responseText = await provider.sendMessage(messages, { signal: controller.signal });
|
|
256
302
|
spinner.stop();
|
|
257
|
-
|
|
258
303
|
const cleanedText = cleanAIResponse(responseText);
|
|
259
304
|
messages.push({ role: 'assistant', content: responseText });
|
|
260
|
-
|
|
261
305
|
console.log(`\n${lavender('bimmo ')}${currentMode.toUpperCase()}`);
|
|
262
306
|
console.log(lavender('─'.repeat(50)));
|
|
263
|
-
|
|
264
|
-
console.log(gray('─'.repeat(50)));
|
|
307
|
+
process.stdout.write(marked.parse(cleanedText));
|
|
308
|
+
console.log(gray('\n' + '─'.repeat(50)));
|
|
265
309
|
} catch (err) {
|
|
266
310
|
spinner.stop();
|
|
267
311
|
if (controller.signal.aborted) { console.log(yellow(`\n⚠️ ${t.interrupted}`)); messages.pop(); }
|
|
268
312
|
else { console.error(chalk.red(`\n✖ Erro: ${err.message}`)); }
|
|
269
313
|
} finally {
|
|
270
314
|
process.removeListener('SIGINT', abortHandler);
|
|
315
|
+
// Reativa o listener de preview
|
|
316
|
+
process.stdin.on('keypress', (s, key) => {
|
|
317
|
+
setImmediate(() => showPreview(rl.line));
|
|
318
|
+
});
|
|
271
319
|
displayPrompt();
|
|
272
320
|
}
|
|
273
321
|
});
|
|
@@ -18,6 +18,7 @@ export class AnthropicProvider extends BaseProvider {
|
|
|
18
18
|
type: 'image',
|
|
19
19
|
source: { type: 'base64', media_type: part.mimeType, data: part.data }
|
|
20
20
|
};
|
|
21
|
+
return part;
|
|
21
22
|
});
|
|
22
23
|
}
|
|
23
24
|
|
|
@@ -51,8 +52,6 @@ export class AnthropicProvider extends BaseProvider {
|
|
|
51
52
|
|
|
52
53
|
if (tool) {
|
|
53
54
|
if (options.signal?.aborted) throw new Error('Abortado pelo usuário');
|
|
54
|
-
|
|
55
|
-
console.log(`\n ${tool.name === 'search_internet' ? '🌐' : '🛠️'} Executando: ${tool.name}...`);
|
|
56
55
|
const result = await tool.execute(toolUse.input);
|
|
57
56
|
|
|
58
57
|
const nextMessages = [
|
package/src/providers/gemini.js
CHANGED
|
@@ -15,6 +15,7 @@ export class GeminiProvider extends BaseProvider {
|
|
|
15
15
|
if (part.type === 'image') return {
|
|
16
16
|
inlineData: { mimeType: part.mimeType, data: part.data }
|
|
17
17
|
};
|
|
18
|
+
return part;
|
|
18
19
|
});
|
|
19
20
|
}
|
|
20
21
|
|
|
@@ -58,7 +59,6 @@ export class GeminiProvider extends BaseProvider {
|
|
|
58
59
|
|
|
59
60
|
const tool = tools.find(t => t.name === call.functionCall.name);
|
|
60
61
|
if (tool) {
|
|
61
|
-
console.log(`\n ${tool.name === 'search_internet' ? '🌐' : '🛠️'} Executando: ${tool.name}...`);
|
|
62
62
|
const toolResult = await tool.execute(call.functionCall.args);
|
|
63
63
|
|
|
64
64
|
const resultResponse = await chat.sendMessage([{
|
package/src/providers/openai.js
CHANGED
|
@@ -21,12 +21,8 @@ export class OpenAIProvider extends BaseProvider {
|
|
|
21
21
|
|
|
22
22
|
formatMessages(messages) {
|
|
23
23
|
return messages.map(msg => {
|
|
24
|
-
|
|
25
|
-
if (typeof msg.content === 'string' || msg.content === null) {
|
|
26
|
-
return msg;
|
|
27
|
-
}
|
|
24
|
+
if (typeof msg.content === 'string' || msg.content === null) return msg;
|
|
28
25
|
|
|
29
|
-
// Se for um array (multimodal), processa as partes
|
|
30
26
|
if (Array.isArray(msg.content)) {
|
|
31
27
|
const content = msg.content.map(part => {
|
|
32
28
|
if (part.type === 'text') return { type: 'text', text: part.text };
|
|
@@ -34,11 +30,10 @@ export class OpenAIProvider extends BaseProvider {
|
|
|
34
30
|
type: 'image_url',
|
|
35
31
|
image_url: { url: `data:${part.mimeType};base64,${part.data}` }
|
|
36
32
|
};
|
|
37
|
-
return part;
|
|
33
|
+
return part;
|
|
38
34
|
});
|
|
39
35
|
return { ...msg, content };
|
|
40
36
|
}
|
|
41
|
-
|
|
42
37
|
return msg;
|
|
43
38
|
});
|
|
44
39
|
}
|
|
@@ -77,7 +72,6 @@ export class OpenAIProvider extends BaseProvider {
|
|
|
77
72
|
|
|
78
73
|
const tool = tools.find(t => t.name === toolCall.function.name);
|
|
79
74
|
if (tool) {
|
|
80
|
-
console.log(`\n ${tool.name === 'search_internet' ? '🌐' : '🛠️'} Executando: ${tool.name}...`);
|
|
81
75
|
const args = JSON.parse(toolCall.function.arguments);
|
|
82
76
|
const result = await tool.execute(args);
|
|
83
77
|
|