bimmo-cli 2.2.8 → 2.2.9
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 +3 -2
- package/src/interface.js +54 -120
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 real-time, Diffs
|
|
3
|
+
"version": "2.2.9",
|
|
4
|
+
"description": "🌿 Plataforma de IA universal profissional com Agentes e Swarms. Suporte a Autocomplete real-time (estilo gemini-cli), Diffs e Contexto Inteligente.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"bimmo": "bin/bimmo"
|
|
7
7
|
},
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
"conf": "^13.0.0",
|
|
30
30
|
"diff": "^7.0.0",
|
|
31
31
|
"figlet": "^1.7.0",
|
|
32
|
+
"fuzzy": "^0.1.3",
|
|
32
33
|
"inquirer": "^9.3.8",
|
|
33
34
|
"marked": "^14.0.0",
|
|
34
35
|
"marked-terminal": "^7.0.0",
|
package/src/interface.js
CHANGED
|
@@ -8,6 +8,7 @@ import path from 'path';
|
|
|
8
8
|
import mime from 'mime-types';
|
|
9
9
|
import readline from 'readline';
|
|
10
10
|
import { fileURLToPath } from 'url';
|
|
11
|
+
import { stripVTControlCharacters } from 'util';
|
|
11
12
|
|
|
12
13
|
import { getConfig, configure, updateActiveModel, switchProfile } from './config.js';
|
|
13
14
|
import { createProvider } from './providers/factory.js';
|
|
@@ -20,13 +21,13 @@ const __dirname = path.dirname(__filename);
|
|
|
20
21
|
const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, '../package.json'), 'utf-8'));
|
|
21
22
|
const version = pkg.version;
|
|
22
23
|
|
|
23
|
-
//
|
|
24
|
+
// Configuração do renderizador - DROP TOTAL DE HTML
|
|
24
25
|
const terminalRenderer = new TerminalRenderer({
|
|
25
26
|
heading: chalk.hex('#c084fc').bold,
|
|
26
27
|
code: chalk.hex('#00ff9d'),
|
|
27
28
|
strong: chalk.bold,
|
|
28
29
|
em: chalk.italic,
|
|
29
|
-
html: () => '', //
|
|
30
|
+
html: (html) => '', // Remove qualquer tag HTML que sobrar
|
|
30
31
|
});
|
|
31
32
|
|
|
32
33
|
marked.setOptions({ renderer: terminalRenderer });
|
|
@@ -42,37 +43,11 @@ let activePersona = null;
|
|
|
42
43
|
let exitCounter = 0;
|
|
43
44
|
let exitTimer = null;
|
|
44
45
|
|
|
45
|
-
const i18n = {
|
|
46
|
-
'pt-BR': {
|
|
47
|
-
welcome: 'Olá! Estou pronto. No que posso ajudar?',
|
|
48
|
-
thinking: 'bimmo pensando...',
|
|
49
|
-
interrupted: 'Operação interrompida.',
|
|
50
|
-
exitHint: '(Pressione Ctrl+C novamente para sair)',
|
|
51
|
-
switchOk: 'Perfil ativado!',
|
|
52
|
-
agentOk: 'Agente ativado:',
|
|
53
|
-
modeEdit: 'Modo EDIT ativado.',
|
|
54
|
-
help: '\nComandos:\n /chat | /plan | /edit | /init\n /switch [nome] | /model [nome]\n /use [agente] | /use normal\n /config | /clear | @arquivo\n'
|
|
55
|
-
},
|
|
56
|
-
'en-US': {
|
|
57
|
-
welcome: 'Hello! I am ready. How can I help you?',
|
|
58
|
-
thinking: 'bimmo thinking...',
|
|
59
|
-
interrupted: 'Operation interrupted.',
|
|
60
|
-
exitHint: '(Press Ctrl+C again to exit)',
|
|
61
|
-
switchOk: 'Profile activated!',
|
|
62
|
-
agentOk: 'Agent activated:',
|
|
63
|
-
modeEdit: 'EDIT mode activated.',
|
|
64
|
-
help: '\nCommands:\n /chat | /plan | /edit | /init\n /switch [name] | /model [name]\n /use [agent] | /use normal\n /config | /clear | @file\n'
|
|
65
|
-
}
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Coleta arquivos para preview e completion
|
|
70
|
-
*/
|
|
71
46
|
function getFilesForPreview(partialPath) {
|
|
72
47
|
try {
|
|
73
|
-
const
|
|
74
|
-
const dir = path.dirname(
|
|
75
|
-
const base = path.basename(
|
|
48
|
+
const p = partialPath.startsWith('@') ? partialPath.slice(1) : partialPath;
|
|
49
|
+
const dir = path.dirname(p) === '.' && !p.includes('/') ? '.' : path.dirname(p);
|
|
50
|
+
const base = path.basename(p) === '.' ? '' : path.basename(p);
|
|
76
51
|
|
|
77
52
|
const searchDir = path.resolve(process.cwd(), dir);
|
|
78
53
|
if (!fs.existsSync(searchDir)) return [];
|
|
@@ -100,9 +75,6 @@ function cleanAIResponse(text) {
|
|
|
100
75
|
|
|
101
76
|
export async function startInteractive() {
|
|
102
77
|
let config = getConfig();
|
|
103
|
-
const lang = config.language || 'pt-BR';
|
|
104
|
-
const t = i18n[lang] || i18n['pt-BR'];
|
|
105
|
-
|
|
106
78
|
if (!config.provider || !config.apiKey) {
|
|
107
79
|
console.log(lavender(figlet.textSync('bimmo')));
|
|
108
80
|
await configure();
|
|
@@ -131,13 +103,10 @@ export async function startInteractive() {
|
|
|
131
103
|
console.log(green(` Modelo: ${bold(config.model)}`));
|
|
132
104
|
console.log(lavender('─'.repeat(60)) + '\n');
|
|
133
105
|
|
|
134
|
-
console.log(lavender(`👋 ${t.welcome}\n`));
|
|
135
|
-
|
|
136
106
|
const rl = readline.createInterface({
|
|
137
107
|
input: process.stdin,
|
|
138
108
|
output: process.stdout,
|
|
139
109
|
terminal: true,
|
|
140
|
-
historySize: 100,
|
|
141
110
|
completer: (line) => {
|
|
142
111
|
const words = line.split(' ');
|
|
143
112
|
const lastWord = words[words.length - 1];
|
|
@@ -152,130 +121,101 @@ export async function startInteractive() {
|
|
|
152
121
|
let currentPreviewLines = 0;
|
|
153
122
|
|
|
154
123
|
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
124
|
if (currentPreviewLines > 0) {
|
|
160
|
-
|
|
125
|
+
// Move o cursor para o final das linhas de preview e apaga tudo
|
|
126
|
+
readline.moveCursor(process.stdout, 0, currentPreviewLines);
|
|
127
|
+
for (let i = 0; i < currentPreviewLines; i++) {
|
|
128
|
+
readline.moveCursor(process.stdout, 0, -1);
|
|
129
|
+
readline.clearLine(process.stdout, 0);
|
|
130
|
+
}
|
|
131
|
+
currentPreviewLines = 0;
|
|
161
132
|
}
|
|
162
|
-
currentPreviewLines = 0;
|
|
163
133
|
};
|
|
164
134
|
|
|
165
|
-
const showPreview = (
|
|
135
|
+
const showPreview = () => {
|
|
166
136
|
clearPreview();
|
|
167
|
-
const words = line.split(' ');
|
|
137
|
+
const words = rl.line.split(' ');
|
|
168
138
|
const lastWord = words[words.length - 1];
|
|
169
139
|
|
|
170
140
|
if (lastWord.startsWith('@')) {
|
|
171
141
|
const files = getFilesForPreview(lastWord);
|
|
172
142
|
if (files.length > 0) {
|
|
173
143
|
process.stdout.write('\n');
|
|
174
|
-
files.slice(0,
|
|
144
|
+
const displayFiles = files.slice(0, 8);
|
|
145
|
+
displayFiles.forEach(f => {
|
|
175
146
|
process.stdout.write(gray(` ${f.isDir ? '📁' : '📄'} ${f.name}\n`));
|
|
176
147
|
});
|
|
177
|
-
currentPreviewLines =
|
|
148
|
+
currentPreviewLines = displayFiles.length + 1;
|
|
149
|
+
// Volta o cursor para a linha do prompt de forma segura
|
|
178
150
|
readline.moveCursor(process.stdout, 0, -currentPreviewLines);
|
|
179
|
-
|
|
151
|
+
// Posiciona o cursor exatamente onde o usuário estava digitando
|
|
152
|
+
const promptText = rl.getPrompt();
|
|
153
|
+
const visualPromptLen = stripVTControlCharacters(promptText).length;
|
|
154
|
+
const totalLen = visualPromptLen + rl.line.length;
|
|
155
|
+
const cols = process.stdout.columns || 80;
|
|
156
|
+
readline.cursorTo(process.stdout, totalLen % cols);
|
|
180
157
|
}
|
|
181
158
|
}
|
|
182
159
|
};
|
|
183
160
|
|
|
184
|
-
|
|
161
|
+
const displayPrompt = () => {
|
|
162
|
+
const personaLabel = activePersona ? `[${activePersona.toUpperCase()}]` : '';
|
|
163
|
+
let modeLabel = `[${currentMode.toUpperCase()}]`;
|
|
164
|
+
if (currentMode === 'edit') modeLabel = editState.autoAccept ? '[EDIT(AUTO)]' : '[EDIT(MANUAL)]';
|
|
165
|
+
|
|
166
|
+
console.log(`\n${gray(`📁 ${process.cwd()}`)}`);
|
|
167
|
+
rl.setPrompt(lavender.bold(personaLabel) + (currentMode === 'edit' ? chalk.red.bold(modeLabel) : lavender.bold(modeLabel)) + green(' > '));
|
|
168
|
+
rl.prompt();
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
// Escuta teclas para o preview em tempo real
|
|
185
172
|
process.stdin.on('keypress', (s, key) => {
|
|
186
|
-
|
|
187
|
-
setImmediate(() => showPreview(
|
|
173
|
+
if (key && (key.name === 'return' || key.name === 'enter')) return;
|
|
174
|
+
setImmediate(() => showPreview());
|
|
188
175
|
});
|
|
189
176
|
|
|
190
177
|
rl.on('SIGINT', () => {
|
|
191
178
|
if (exitCounter === 0) {
|
|
192
179
|
exitCounter++;
|
|
193
|
-
|
|
180
|
+
process.stdout.write(`\n${gray('(Pressione novamente para sair)')}\n`);
|
|
194
181
|
exitTimer = setTimeout(() => { exitCounter = 0; }, 2000);
|
|
195
182
|
displayPrompt();
|
|
196
|
-
} else {
|
|
197
|
-
process.exit(0);
|
|
198
|
-
}
|
|
183
|
+
} else { process.exit(0); }
|
|
199
184
|
});
|
|
200
185
|
|
|
201
|
-
const displayPrompt = () => {
|
|
202
|
-
const personaLabel = activePersona ? `[${activePersona.toUpperCase()}]` : '';
|
|
203
|
-
let modeLabel = `[${currentMode.toUpperCase()}]`;
|
|
204
|
-
if (currentMode === 'edit') modeLabel = editState.autoAccept ? '[EDIT(AUTO)]' : '[EDIT(MANUAL)]';
|
|
205
|
-
|
|
206
|
-
console.log(`${gray(`📁 ${process.cwd()}`)}`);
|
|
207
|
-
rl.setPrompt(lavender.bold(personaLabel) + (currentMode === 'edit' ? chalk.red.bold(modeLabel) : lavender.bold(modeLabel)) + green(' > '));
|
|
208
|
-
rl.prompt();
|
|
209
|
-
};
|
|
210
|
-
|
|
211
186
|
displayPrompt();
|
|
212
187
|
|
|
213
188
|
rl.on('line', async (input) => {
|
|
214
189
|
clearPreview();
|
|
215
190
|
const rawInput = input.trim();
|
|
216
|
-
const cmd = rawInput.toLowerCase();
|
|
217
|
-
|
|
218
191
|
if (rawInput === '') { displayPrompt(); return; }
|
|
219
192
|
|
|
193
|
+
const cmd = rawInput.toLowerCase();
|
|
220
194
|
if (cmd === '/exit' || cmd === 'sair') process.exit(0);
|
|
221
195
|
if (cmd === '/chat') { currentMode = 'chat'; displayPrompt(); return; }
|
|
222
196
|
if (cmd === '/plan') { currentMode = 'plan'; displayPrompt(); return; }
|
|
223
197
|
if (cmd === '/edit' || cmd === '/edit manual') { currentMode = 'edit'; editState.autoAccept = false; displayPrompt(); return; }
|
|
224
198
|
if (cmd === '/edit auto') { currentMode = 'edit'; editState.autoAccept = true; displayPrompt(); return; }
|
|
225
199
|
if (cmd === '/clear') { resetMessages(); console.clear(); displayPrompt(); return; }
|
|
226
|
-
|
|
227
|
-
|
|
200
|
+
|
|
228
201
|
if (cmd === '/init') {
|
|
229
|
-
console.log(chalk.cyan('\n🚀
|
|
230
|
-
const initPrompt = `Analise
|
|
231
|
-
|
|
232
|
-
const controller = new AbortController();
|
|
233
|
-
const spinner = ora({ text: lavender(`${t.thinking}`), color: 'red' }).start();
|
|
202
|
+
console.log(chalk.cyan('\n🚀 Gerando .bimmorc.json...\n'));
|
|
203
|
+
const initPrompt = `Analise o projeto e crie o .bimmorc.json com regras e stack. Use write_file.`;
|
|
204
|
+
const spinner = ora({ text: lavender(`bimmo pensando...`), color: 'red' }).start();
|
|
234
205
|
try {
|
|
235
|
-
const res = await provider.sendMessage([...messages, { role: 'user', content: initPrompt }]
|
|
206
|
+
const res = await provider.sendMessage([...messages, { role: 'user', content: initPrompt }]);
|
|
236
207
|
spinner.stop();
|
|
237
208
|
console.log(marked.parse(cleanAIResponse(res)));
|
|
238
209
|
} catch (e) { spinner.stop(); console.error(chalk.red(e.message)); }
|
|
239
|
-
resetMessages();
|
|
240
|
-
displayPrompt();
|
|
241
|
-
return;
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
if (cmd === '/config') {
|
|
245
|
-
rl.pause(); await configure(); config = getConfig(); provider = createProvider(config); rl.resume();
|
|
246
|
-
displayPrompt(); return;
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
if (cmd.startsWith('/switch ')) {
|
|
250
|
-
const pName = rawInput.split(' ')[1];
|
|
251
|
-
if (switchProfile(pName)) {
|
|
252
|
-
config = getConfig(); provider = createProvider(config);
|
|
253
|
-
console.log(green(`\n✓ ${t.switchOk}`));
|
|
254
|
-
} else { console.log(chalk.red(`\n✖ Perfil não encontrado.`)); }
|
|
255
|
-
displayPrompt(); return;
|
|
210
|
+
resetMessages(); displayPrompt(); return;
|
|
256
211
|
}
|
|
257
212
|
|
|
258
|
-
if (cmd
|
|
259
|
-
const aName = rawInput.split(' ')[1];
|
|
260
|
-
if (aName === 'normal') { activePersona = null; resetMessages(); displayPrompt(); return; }
|
|
261
|
-
const agents = config.agents || {};
|
|
262
|
-
if (agents[aName]) {
|
|
263
|
-
activePersona = aName;
|
|
264
|
-
const agent = agents[aName];
|
|
265
|
-
if (switchProfile(agent.profile)) { config = getConfig(); provider = createProvider(config); }
|
|
266
|
-
currentMode = agent.mode || 'chat';
|
|
267
|
-
console.log(green(`\n✓ ${t.agentOk} ${bold(aName)}`));
|
|
268
|
-
resetMessages();
|
|
269
|
-
} else { console.log(chalk.red(`\n✖ Agente não encontrado.`)); }
|
|
270
|
-
displayPrompt(); return;
|
|
271
|
-
}
|
|
213
|
+
if (cmd === '/config') { rl.pause(); await configure(); config = getConfig(); provider = createProvider(config); rl.resume(); displayPrompt(); return; }
|
|
272
214
|
|
|
273
215
|
// PROCESSAMENTO IA
|
|
274
216
|
const controller = new AbortController();
|
|
275
217
|
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');
|
|
218
|
+
process.removeListener('SIGINT', () => {}); // Limpa handlers antigos
|
|
279
219
|
process.on('SIGINT', abortHandler);
|
|
280
220
|
|
|
281
221
|
let modeInstr = "";
|
|
@@ -288,34 +228,28 @@ export async function startInteractive() {
|
|
|
288
228
|
if (word.startsWith('@')) {
|
|
289
229
|
const filePath = word.slice(1);
|
|
290
230
|
if (fs.existsSync(filePath) && fs.statSync(filePath).isFile()) {
|
|
291
|
-
|
|
292
|
-
processedContent.push({ type: 'text', text: `\n[ARQUIVO: ${filePath}]\n${content}\n` });
|
|
231
|
+
processedContent.push({ type: 'text', text: `\n[ARQUIVO: ${filePath}]\n${fs.readFileSync(filePath, 'utf-8')}\n` });
|
|
293
232
|
} else { processedContent.push({ type: 'text', text: word }); }
|
|
294
233
|
} else { processedContent.push({ type: 'text', text: word }); }
|
|
295
234
|
}
|
|
296
235
|
|
|
297
236
|
messages.push({ role: 'user', content: [...processedContent, { type: 'text', text: modeInstr }] });
|
|
298
|
-
const spinner = ora({ text: lavender(
|
|
237
|
+
const spinner = ora({ text: lavender(`bimmo pensando... (Ctrl+C para parar)`), color: currentMode === 'edit' ? 'red' : 'magenta' }).start();
|
|
299
238
|
|
|
300
239
|
try {
|
|
301
240
|
let responseText = await provider.sendMessage(messages, { signal: controller.signal });
|
|
302
241
|
spinner.stop();
|
|
303
|
-
const cleanedText = cleanAIResponse(responseText);
|
|
304
|
-
messages.push({ role: 'assistant', content: responseText });
|
|
305
242
|
console.log(`\n${lavender('bimmo ')}${currentMode.toUpperCase()}`);
|
|
306
243
|
console.log(lavender('─'.repeat(50)));
|
|
307
|
-
process.stdout.write(marked.parse(
|
|
244
|
+
process.stdout.write(marked.parse(cleanAIResponse(responseText)));
|
|
308
245
|
console.log(gray('\n' + '─'.repeat(50)));
|
|
246
|
+
messages.push({ role: 'assistant', content: responseText });
|
|
309
247
|
} catch (err) {
|
|
310
248
|
spinner.stop();
|
|
311
|
-
if (controller.signal.aborted) { console.log(yellow(`\n⚠️
|
|
249
|
+
if (controller.signal.aborted) { console.log(yellow(`\n⚠️ Interrompido.`)); messages.pop(); }
|
|
312
250
|
else { console.error(chalk.red(`\n✖ Erro: ${err.message}`)); }
|
|
313
251
|
} finally {
|
|
314
252
|
process.removeListener('SIGINT', abortHandler);
|
|
315
|
-
// Reativa o listener de preview
|
|
316
|
-
process.stdin.on('keypress', (s, key) => {
|
|
317
|
-
setImmediate(() => showPreview(rl.line));
|
|
318
|
-
});
|
|
319
253
|
displayPrompt();
|
|
320
254
|
}
|
|
321
255
|
});
|