bimmo-cli 2.2.11 → 2.2.12
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/interface.js +102 -59
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
|
|
3
|
+
"version": "2.2.12",
|
|
4
|
+
"description": "🌿 Plataforma de IA universal profissional com Agentes e Swarms. Suporte a Autocomplete real-time sólido, Diffs e Contexto Inteligente.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"bimmo": "bin/bimmo"
|
|
7
7
|
},
|
package/src/interface.js
CHANGED
|
@@ -8,7 +8,6 @@ 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';
|
|
12
11
|
|
|
13
12
|
import { getConfig, configure, updateActiveModel, switchProfile } from './config.js';
|
|
14
13
|
import { createProvider } from './providers/factory.js';
|
|
@@ -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
|
|
|
23
|
+
// CONFIGURAÇÃO DO RENDERIZADOR - ELIMINA HTML E FORMATA TERMINAL
|
|
24
24
|
marked.use(new TerminalRenderer({
|
|
25
25
|
heading: chalk.hex('#c084fc').bold,
|
|
26
26
|
code: chalk.hex('#00ff9d'),
|
|
27
27
|
strong: chalk.bold,
|
|
28
28
|
em: chalk.italic,
|
|
29
|
-
html: () => '',
|
|
29
|
+
html: () => '', // DROP TOTAL DE HTML
|
|
30
30
|
}));
|
|
31
31
|
|
|
32
32
|
const green = chalk.hex('#00ff9d');
|
|
@@ -39,32 +39,58 @@ let currentMode = 'chat';
|
|
|
39
39
|
let activePersona = null;
|
|
40
40
|
let exitCounter = 0;
|
|
41
41
|
let exitTimer = null;
|
|
42
|
+
let currentPreviewLines = 0;
|
|
42
43
|
|
|
44
|
+
const i18n = {
|
|
45
|
+
'pt-BR': {
|
|
46
|
+
welcome: 'Olá! Sou o bimmo. Como posso ajudar?',
|
|
47
|
+
thinking: 'bimmo pensando...',
|
|
48
|
+
interrupted: 'Interrompido.',
|
|
49
|
+
exitHint: '(Pressione Ctrl+C novamente para sair)',
|
|
50
|
+
switchOk: 'Perfil ativo:',
|
|
51
|
+
agentOk: 'Agente ativo:',
|
|
52
|
+
help: '\nComandos:\n /chat | /plan | /edit | /init\n /switch [perfil] | /model [modelo]\n /use [agente] | /use normal\n /config | /clear | @arquivo\n'
|
|
53
|
+
},
|
|
54
|
+
'en-US': {
|
|
55
|
+
welcome: 'Hello! I am bimmo. How can I help?',
|
|
56
|
+
thinking: 'bimmo thinking...',
|
|
57
|
+
interrupted: 'Interrupted.',
|
|
58
|
+
exitHint: '(Press Ctrl+C again to exit)',
|
|
59
|
+
switchOk: 'Profile active:',
|
|
60
|
+
agentOk: 'Agent active:',
|
|
61
|
+
help: '\nCommands:\n /chat | /plan | /edit | /init\n /switch [profile] | /model [model]\n /use [agent] | /use normal\n /config | /clear | @file\n'
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Coleta arquivos e diretórios para o preview (Lógica Gemini-CLI)
|
|
67
|
+
*/
|
|
43
68
|
function getFilesForPreview(partialPath) {
|
|
44
69
|
try {
|
|
45
70
|
let p = partialPath.startsWith('@') ? partialPath.slice(1) : partialPath;
|
|
46
71
|
let searchDir = '.';
|
|
47
72
|
let filter = '';
|
|
48
73
|
|
|
49
|
-
if (p.
|
|
74
|
+
if (p.endsWith('/')) {
|
|
75
|
+
searchDir = p;
|
|
76
|
+
filter = '';
|
|
77
|
+
} else if (p.includes('/')) {
|
|
50
78
|
const lastSlash = p.lastIndexOf('/');
|
|
51
|
-
searchDir = p.substring(0, lastSlash)
|
|
79
|
+
searchDir = p.substring(0, lastSlash);
|
|
52
80
|
filter = p.substring(lastSlash + 1);
|
|
53
81
|
} else {
|
|
54
|
-
searchDir = '.';
|
|
55
82
|
filter = p;
|
|
56
83
|
}
|
|
57
84
|
|
|
58
85
|
const absoluteSearchDir = path.resolve(process.cwd(), searchDir);
|
|
59
86
|
if (!fs.existsSync(absoluteSearchDir)) return [];
|
|
60
87
|
|
|
61
|
-
|
|
62
|
-
return files
|
|
88
|
+
return fs.readdirSync(absoluteSearchDir)
|
|
63
89
|
.filter(f => f.startsWith(filter) && !f.startsWith('.') && f !== 'node_modules')
|
|
64
90
|
.map(f => {
|
|
65
|
-
const
|
|
66
|
-
const isDir = fs.statSync(
|
|
67
|
-
return { name: rel, isDir };
|
|
91
|
+
const fullPath = path.join(absoluteSearchDir, f);
|
|
92
|
+
const isDir = fs.statSync(fullPath).isDirectory();
|
|
93
|
+
return { name: f, rel: path.join(searchDir, f), isDir };
|
|
68
94
|
});
|
|
69
95
|
} catch (e) { return []; }
|
|
70
96
|
}
|
|
@@ -81,6 +107,9 @@ function cleanAIResponse(text) {
|
|
|
81
107
|
|
|
82
108
|
export async function startInteractive() {
|
|
83
109
|
let config = getConfig();
|
|
110
|
+
const lang = config.language || 'pt-BR';
|
|
111
|
+
const t = i18n[lang] || i18n['pt-BR'];
|
|
112
|
+
|
|
84
113
|
if (!config.provider || !config.apiKey) {
|
|
85
114
|
console.log(lavender(figlet.textSync('bimmo')));
|
|
86
115
|
await configure(); return startInteractive();
|
|
@@ -102,9 +131,9 @@ export async function startInteractive() {
|
|
|
102
131
|
console.clear();
|
|
103
132
|
console.log(lavender(figlet.textSync('bimmo')));
|
|
104
133
|
console.log(lavender(` v${version} `.padStart(60, '─')));
|
|
105
|
-
console.log(green(` Perfil: ${bold(config.activeProfile || 'Padrão')} •
|
|
106
|
-
console.log(green(` Modelo: ${bold(config.model)}`));
|
|
134
|
+
console.log(green(` Perfil: ${bold(config.activeProfile || 'Padrão')} • Modelo: ${bold(config.model)}`));
|
|
107
135
|
console.log(lavender('─'.repeat(60)) + '\n');
|
|
136
|
+
console.log(lavender(`👋 ${t.welcome}\n`));
|
|
108
137
|
|
|
109
138
|
const rl = readline.createInterface({
|
|
110
139
|
input: process.stdin,
|
|
@@ -114,24 +143,21 @@ export async function startInteractive() {
|
|
|
114
143
|
const words = line.split(' ');
|
|
115
144
|
const lastWord = words[words.length - 1];
|
|
116
145
|
if (lastWord.startsWith('@')) {
|
|
117
|
-
const hits = getFilesForPreview(lastWord).map(h => `@${h.
|
|
146
|
+
const hits = getFilesForPreview(lastWord).map(h => `@${h.rel}${h.isDir ? '/' : ''}`);
|
|
118
147
|
return [hits, lastWord];
|
|
119
148
|
}
|
|
120
149
|
return [[], line];
|
|
121
150
|
}
|
|
122
151
|
});
|
|
123
152
|
|
|
124
|
-
let currentPreviewLines = 0;
|
|
125
|
-
|
|
126
153
|
const clearPreview = () => {
|
|
127
154
|
if (currentPreviewLines > 0) {
|
|
128
|
-
|
|
155
|
+
readline.cursorTo(process.stdout, 0);
|
|
129
156
|
readline.moveCursor(process.stdout, 0, 1);
|
|
130
157
|
for (let i = 0; i < currentPreviewLines; i++) {
|
|
131
158
|
readline.clearLine(process.stdout, 0);
|
|
132
159
|
readline.moveCursor(process.stdout, 0, 1);
|
|
133
160
|
}
|
|
134
|
-
// Volta o cursor para a posição original
|
|
135
161
|
readline.moveCursor(process.stdout, 0, -(currentPreviewLines + 1));
|
|
136
162
|
currentPreviewLines = 0;
|
|
137
163
|
}
|
|
@@ -145,25 +171,18 @@ export async function startInteractive() {
|
|
|
145
171
|
const files = getFilesForPreview(lastWord);
|
|
146
172
|
if (files.length > 0) {
|
|
147
173
|
clearPreview();
|
|
148
|
-
const displayFiles = files.slice(0, 10);
|
|
149
|
-
|
|
150
|
-
// Move cursor para o final da linha atual e pula linha
|
|
151
174
|
process.stdout.write('\n');
|
|
152
|
-
|
|
153
|
-
|
|
175
|
+
const display = files.slice(0, 8);
|
|
176
|
+
display.forEach(f => {
|
|
177
|
+
process.stdout.write(gray(` ${f.isDir ? '📁' : '📄'} ${f.rel}${f.isDir ? '/' : ''}\n`));
|
|
154
178
|
});
|
|
155
|
-
currentPreviewLines =
|
|
156
|
-
|
|
157
|
-
// Retorna o cursor para a posição exata de escrita
|
|
179
|
+
currentPreviewLines = display.length;
|
|
158
180
|
readline.moveCursor(process.stdout, 0, -(currentPreviewLines + 1));
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
} else {
|
|
165
|
-
clearPreview();
|
|
166
|
-
}
|
|
181
|
+
// Reposiciona o cursor de digitação
|
|
182
|
+
const promptLen = (activePersona ? activePersona.length + 2 : 0) + (currentMode.length + 8) + 3;
|
|
183
|
+
readline.cursorTo(process.stdout, (promptLen + rl.line.length) % process.stdout.columns);
|
|
184
|
+
} else { clearPreview(); }
|
|
185
|
+
} else { clearPreview(); }
|
|
167
186
|
};
|
|
168
187
|
|
|
169
188
|
const displayPrompt = () => {
|
|
@@ -176,7 +195,23 @@ export async function startInteractive() {
|
|
|
176
195
|
rl.prompt();
|
|
177
196
|
};
|
|
178
197
|
|
|
179
|
-
//
|
|
198
|
+
// MONITORAMENTO DE TECLAS (REAL-TIME @)
|
|
199
|
+
process.stdin.on('keypress', (s, key) => {
|
|
200
|
+
if (key && (key.name === 'return' || key.name === 'enter')) return;
|
|
201
|
+
setImmediate(() => showPreview());
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
rl.on('SIGINT', () => {
|
|
205
|
+
if (exitCounter === 0) {
|
|
206
|
+
exitCounter++;
|
|
207
|
+
process.stdout.write(`\n${gray(t.exitHint)}\n`);
|
|
208
|
+
exitTimer = setTimeout(() => { exitCounter = 0; }, 2000);
|
|
209
|
+
displayPrompt();
|
|
210
|
+
} else { process.exit(0); }
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
displayPrompt();
|
|
214
|
+
|
|
180
215
|
rl.on('line', async (input) => {
|
|
181
216
|
clearPreview();
|
|
182
217
|
const rawInput = input.trim();
|
|
@@ -189,15 +224,15 @@ export async function startInteractive() {
|
|
|
189
224
|
if (cmd === '/edit' || cmd === '/edit manual') { currentMode = 'edit'; editState.autoAccept = false; displayPrompt(); return; }
|
|
190
225
|
if (cmd === '/edit auto') { currentMode = 'edit'; editState.autoAccept = true; displayPrompt(); return; }
|
|
191
226
|
if (cmd === '/clear') { resetMessages(); console.clear(); displayPrompt(); return; }
|
|
192
|
-
|
|
227
|
+
if (cmd === '/help') { console.log(gray(t.help)); displayPrompt(); return; }
|
|
228
|
+
|
|
193
229
|
if (cmd === '/init') {
|
|
194
|
-
console.log(chalk.cyan('\n🚀
|
|
195
|
-
const initPrompt = `Analise
|
|
196
|
-
const spinner = ora({ text: lavender(`
|
|
230
|
+
console.log(chalk.cyan('\n🚀 Mapeando projeto para gerar .bimmorc.json...\n'));
|
|
231
|
+
const initPrompt = `Analise a estrutura deste projeto e gere um .bimmorc.json com regras de código, stack e arquitetura. Use write_file.`;
|
|
232
|
+
const spinner = ora({ text: lavender(`${t.thinking}`), color: 'red' }).start();
|
|
197
233
|
try {
|
|
198
234
|
const res = await provider.sendMessage([...messages, { role: 'user', content: initPrompt }]);
|
|
199
|
-
spinner.stop();
|
|
200
|
-
console.log(marked.parse(cleanAIResponse(res)));
|
|
235
|
+
spinner.stop(); console.log(marked.parse(cleanAIResponse(res)));
|
|
201
236
|
} catch (e) { spinner.stop(); console.error(chalk.red(e.message)); }
|
|
202
237
|
resetMessages(); displayPrompt(); return;
|
|
203
238
|
}
|
|
@@ -207,9 +242,34 @@ export async function startInteractive() {
|
|
|
207
242
|
displayPrompt(); return;
|
|
208
243
|
}
|
|
209
244
|
|
|
245
|
+
if (cmd.startsWith('/switch ')) {
|
|
246
|
+
const pName = rawInput.split(' ')[1];
|
|
247
|
+
if (switchProfile(pName)) {
|
|
248
|
+
config = getConfig(); provider = createProvider(config);
|
|
249
|
+
console.log(green(`\n✓ ${t.switchOk} ${pName}`));
|
|
250
|
+
} else { console.log(chalk.red(`\n✖ Perfil não encontrado.`)); }
|
|
251
|
+
displayPrompt(); return;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if (cmd.startsWith('/use ')) {
|
|
255
|
+
const aName = rawInput.split(' ')[1];
|
|
256
|
+
if (aName === 'normal') { activePersona = null; resetMessages(); displayPrompt(); return; }
|
|
257
|
+
const agents = config.agents || {};
|
|
258
|
+
if (agents[aName]) {
|
|
259
|
+
activePersona = aName;
|
|
260
|
+
const agent = agents[aName];
|
|
261
|
+
if (switchProfile(agent.profile)) { config = getConfig(); provider = createProvider(config); }
|
|
262
|
+
currentMode = agent.mode || 'chat';
|
|
263
|
+
console.log(green(`\n✓ ${t.agentOk} ${bold(aName)}`));
|
|
264
|
+
resetMessages();
|
|
265
|
+
} else { console.log(chalk.red(`\n✖ Agente não encontrado.`)); }
|
|
266
|
+
displayPrompt(); return;
|
|
267
|
+
}
|
|
268
|
+
|
|
210
269
|
// PROCESSAMENTO IA
|
|
211
270
|
const controller = new AbortController();
|
|
212
271
|
const abortHandler = () => controller.abort();
|
|
272
|
+
process.removeListener('SIGINT', () => {});
|
|
213
273
|
process.on('SIGINT', abortHandler);
|
|
214
274
|
|
|
215
275
|
let modeInstr = "";
|
|
@@ -228,7 +288,7 @@ export async function startInteractive() {
|
|
|
228
288
|
}
|
|
229
289
|
|
|
230
290
|
messages.push({ role: 'user', content: [...processedContent, { type: 'text', text: modeInstr }] });
|
|
231
|
-
const spinner = ora({ text: lavender(
|
|
291
|
+
const spinner = ora({ text: lavender(`${t.thinking} (Ctrl+C para parar)`), color: currentMode === 'edit' ? 'red' : 'magenta' }).start();
|
|
232
292
|
|
|
233
293
|
try {
|
|
234
294
|
let responseText = await provider.sendMessage(messages, { signal: controller.signal });
|
|
@@ -240,28 +300,11 @@ export async function startInteractive() {
|
|
|
240
300
|
messages.push({ role: 'assistant', content: responseText });
|
|
241
301
|
} catch (err) {
|
|
242
302
|
spinner.stop();
|
|
243
|
-
if (controller.signal.aborted) { console.log(yellow(`\n⚠️
|
|
303
|
+
if (controller.signal.aborted) { console.log(yellow(`\n⚠️ ${t.interrupted}`)); messages.pop(); }
|
|
244
304
|
else { console.error(chalk.red(`\n✖ Erro: ${err.message}`)); }
|
|
245
305
|
} finally {
|
|
246
306
|
process.removeListener('SIGINT', abortHandler);
|
|
247
307
|
displayPrompt();
|
|
248
308
|
}
|
|
249
309
|
});
|
|
250
|
-
|
|
251
|
-
// Listener de tecla para o preview
|
|
252
|
-
process.stdin.on('keypress', (s, key) => {
|
|
253
|
-
if (key && (key.name === 'return' || key.name === 'enter')) return;
|
|
254
|
-
setImmediate(() => showPreview());
|
|
255
|
-
});
|
|
256
|
-
|
|
257
|
-
rl.on('SIGINT', () => {
|
|
258
|
-
if (exitCounter === 0) {
|
|
259
|
-
exitCounter++;
|
|
260
|
-
process.stdout.write(`\n${gray('(Pressione novamente para sair)')}\n`);
|
|
261
|
-
exitTimer = setTimeout(() => { exitCounter = 0; }, 2000);
|
|
262
|
-
displayPrompt();
|
|
263
|
-
} else { process.exit(0); }
|
|
264
|
-
});
|
|
265
|
-
|
|
266
|
-
displayPrompt();
|
|
267
310
|
}
|