bimmo-cli 2.2.10 → 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 +89 -40
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
|
@@ -20,12 +20,13 @@ const __dirname = path.dirname(__filename);
|
|
|
20
20
|
const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, '../package.json'), 'utf-8'));
|
|
21
21
|
const version = pkg.version;
|
|
22
22
|
|
|
23
|
+
// CONFIGURAÇÃO DO RENDERIZADOR - ELIMINA HTML E FORMATA TERMINAL
|
|
23
24
|
marked.use(new TerminalRenderer({
|
|
24
25
|
heading: chalk.hex('#c084fc').bold,
|
|
25
26
|
code: chalk.hex('#00ff9d'),
|
|
26
27
|
strong: chalk.bold,
|
|
27
28
|
em: chalk.italic,
|
|
28
|
-
html: () => '',
|
|
29
|
+
html: () => '', // DROP TOTAL DE HTML
|
|
29
30
|
}));
|
|
30
31
|
|
|
31
32
|
const green = chalk.hex('#00ff9d');
|
|
@@ -38,9 +39,31 @@ let currentMode = 'chat';
|
|
|
38
39
|
let activePersona = null;
|
|
39
40
|
let exitCounter = 0;
|
|
40
41
|
let exitTimer = null;
|
|
42
|
+
let currentPreviewLines = 0;
|
|
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
|
+
};
|
|
41
64
|
|
|
42
65
|
/**
|
|
43
|
-
* Coleta arquivos para preview
|
|
66
|
+
* Coleta arquivos e diretórios para o preview (Lógica Gemini-CLI)
|
|
44
67
|
*/
|
|
45
68
|
function getFilesForPreview(partialPath) {
|
|
46
69
|
try {
|
|
@@ -48,25 +71,26 @@ function getFilesForPreview(partialPath) {
|
|
|
48
71
|
let searchDir = '.';
|
|
49
72
|
let filter = '';
|
|
50
73
|
|
|
51
|
-
if (p.
|
|
74
|
+
if (p.endsWith('/')) {
|
|
75
|
+
searchDir = p;
|
|
76
|
+
filter = '';
|
|
77
|
+
} else if (p.includes('/')) {
|
|
52
78
|
const lastSlash = p.lastIndexOf('/');
|
|
53
|
-
searchDir = p.substring(0, lastSlash)
|
|
79
|
+
searchDir = p.substring(0, lastSlash);
|
|
54
80
|
filter = p.substring(lastSlash + 1);
|
|
55
81
|
} else {
|
|
56
|
-
searchDir = '.';
|
|
57
82
|
filter = p;
|
|
58
83
|
}
|
|
59
84
|
|
|
60
85
|
const absoluteSearchDir = path.resolve(process.cwd(), searchDir);
|
|
61
86
|
if (!fs.existsSync(absoluteSearchDir)) return [];
|
|
62
87
|
|
|
63
|
-
|
|
64
|
-
return files
|
|
88
|
+
return fs.readdirSync(absoluteSearchDir)
|
|
65
89
|
.filter(f => f.startsWith(filter) && !f.startsWith('.') && f !== 'node_modules')
|
|
66
90
|
.map(f => {
|
|
67
|
-
const
|
|
68
|
-
const isDir = fs.statSync(
|
|
69
|
-
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 };
|
|
70
94
|
});
|
|
71
95
|
} catch (e) { return []; }
|
|
72
96
|
}
|
|
@@ -83,6 +107,9 @@ function cleanAIResponse(text) {
|
|
|
83
107
|
|
|
84
108
|
export async function startInteractive() {
|
|
85
109
|
let config = getConfig();
|
|
110
|
+
const lang = config.language || 'pt-BR';
|
|
111
|
+
const t = i18n[lang] || i18n['pt-BR'];
|
|
112
|
+
|
|
86
113
|
if (!config.provider || !config.apiKey) {
|
|
87
114
|
console.log(lavender(figlet.textSync('bimmo')));
|
|
88
115
|
await configure(); return startInteractive();
|
|
@@ -104,9 +131,9 @@ export async function startInteractive() {
|
|
|
104
131
|
console.clear();
|
|
105
132
|
console.log(lavender(figlet.textSync('bimmo')));
|
|
106
133
|
console.log(lavender(` v${version} `.padStart(60, '─')));
|
|
107
|
-
console.log(green(` Perfil: ${bold(config.activeProfile || 'Padrão')} •
|
|
108
|
-
console.log(green(` Modelo: ${bold(config.model)}`));
|
|
134
|
+
console.log(green(` Perfil: ${bold(config.activeProfile || 'Padrão')} • Modelo: ${bold(config.model)}`));
|
|
109
135
|
console.log(lavender('─'.repeat(60)) + '\n');
|
|
136
|
+
console.log(lavender(`👋 ${t.welcome}\n`));
|
|
110
137
|
|
|
111
138
|
const rl = readline.createInterface({
|
|
112
139
|
input: process.stdin,
|
|
@@ -116,49 +143,46 @@ export async function startInteractive() {
|
|
|
116
143
|
const words = line.split(' ');
|
|
117
144
|
const lastWord = words[words.length - 1];
|
|
118
145
|
if (lastWord.startsWith('@')) {
|
|
119
|
-
const hits = getFilesForPreview(lastWord).map(h => `@${h.
|
|
146
|
+
const hits = getFilesForPreview(lastWord).map(h => `@${h.rel}${h.isDir ? '/' : ''}`);
|
|
120
147
|
return [hits, lastWord];
|
|
121
148
|
}
|
|
122
149
|
return [[], line];
|
|
123
150
|
}
|
|
124
151
|
});
|
|
125
152
|
|
|
126
|
-
let currentPreviewLines = 0;
|
|
127
|
-
|
|
128
153
|
const clearPreview = () => {
|
|
129
154
|
if (currentPreviewLines > 0) {
|
|
130
|
-
|
|
155
|
+
readline.cursorTo(process.stdout, 0);
|
|
156
|
+
readline.moveCursor(process.stdout, 0, 1);
|
|
131
157
|
for (let i = 0; i < currentPreviewLines; i++) {
|
|
132
|
-
process.stdout.write('\n');
|
|
133
158
|
readline.clearLine(process.stdout, 0);
|
|
159
|
+
readline.moveCursor(process.stdout, 0, 1);
|
|
134
160
|
}
|
|
135
|
-
readline.moveCursor(process.stdout, 0, -currentPreviewLines);
|
|
161
|
+
readline.moveCursor(process.stdout, 0, -(currentPreviewLines + 1));
|
|
136
162
|
currentPreviewLines = 0;
|
|
137
163
|
}
|
|
138
164
|
};
|
|
139
165
|
|
|
140
166
|
const showPreview = () => {
|
|
141
|
-
clearPreview();
|
|
142
167
|
const words = rl.line.split(' ');
|
|
143
168
|
const lastWord = words[words.length - 1];
|
|
144
169
|
|
|
145
170
|
if (lastWord.startsWith('@')) {
|
|
146
171
|
const files = getFilesForPreview(lastWord);
|
|
147
172
|
if (files.length > 0) {
|
|
148
|
-
|
|
149
|
-
process.stdout.write('\u001b[s');
|
|
150
|
-
|
|
173
|
+
clearPreview();
|
|
151
174
|
process.stdout.write('\n');
|
|
152
|
-
const
|
|
153
|
-
|
|
154
|
-
process.stdout.write(gray(` ${f.isDir ? '📁' : '📄'} ${f.
|
|
175
|
+
const display = files.slice(0, 8);
|
|
176
|
+
display.forEach(f => {
|
|
177
|
+
process.stdout.write(gray(` ${f.isDir ? '📁' : '📄'} ${f.rel}${f.isDir ? '/' : ''}\n`));
|
|
155
178
|
});
|
|
156
|
-
currentPreviewLines =
|
|
157
|
-
|
|
158
|
-
//
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
179
|
+
currentPreviewLines = display.length;
|
|
180
|
+
readline.moveCursor(process.stdout, 0, -(currentPreviewLines + 1));
|
|
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(); }
|
|
162
186
|
};
|
|
163
187
|
|
|
164
188
|
const displayPrompt = () => {
|
|
@@ -171,6 +195,7 @@ export async function startInteractive() {
|
|
|
171
195
|
rl.prompt();
|
|
172
196
|
};
|
|
173
197
|
|
|
198
|
+
// MONITORAMENTO DE TECLAS (REAL-TIME @)
|
|
174
199
|
process.stdin.on('keypress', (s, key) => {
|
|
175
200
|
if (key && (key.name === 'return' || key.name === 'enter')) return;
|
|
176
201
|
setImmediate(() => showPreview());
|
|
@@ -179,7 +204,7 @@ export async function startInteractive() {
|
|
|
179
204
|
rl.on('SIGINT', () => {
|
|
180
205
|
if (exitCounter === 0) {
|
|
181
206
|
exitCounter++;
|
|
182
|
-
process.stdout.write(`\n${gray(
|
|
207
|
+
process.stdout.write(`\n${gray(t.exitHint)}\n`);
|
|
183
208
|
exitTimer = setTimeout(() => { exitCounter = 0; }, 2000);
|
|
184
209
|
displayPrompt();
|
|
185
210
|
} else { process.exit(0); }
|
|
@@ -199,15 +224,15 @@ export async function startInteractive() {
|
|
|
199
224
|
if (cmd === '/edit' || cmd === '/edit manual') { currentMode = 'edit'; editState.autoAccept = false; displayPrompt(); return; }
|
|
200
225
|
if (cmd === '/edit auto') { currentMode = 'edit'; editState.autoAccept = true; displayPrompt(); return; }
|
|
201
226
|
if (cmd === '/clear') { resetMessages(); console.clear(); displayPrompt(); return; }
|
|
202
|
-
|
|
227
|
+
if (cmd === '/help') { console.log(gray(t.help)); displayPrompt(); return; }
|
|
228
|
+
|
|
203
229
|
if (cmd === '/init') {
|
|
204
|
-
console.log(chalk.cyan('\n🚀
|
|
205
|
-
const initPrompt = `Analise
|
|
206
|
-
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();
|
|
207
233
|
try {
|
|
208
234
|
const res = await provider.sendMessage([...messages, { role: 'user', content: initPrompt }]);
|
|
209
|
-
spinner.stop();
|
|
210
|
-
console.log(marked.parse(cleanAIResponse(res)));
|
|
235
|
+
spinner.stop(); console.log(marked.parse(cleanAIResponse(res)));
|
|
211
236
|
} catch (e) { spinner.stop(); console.error(chalk.red(e.message)); }
|
|
212
237
|
resetMessages(); displayPrompt(); return;
|
|
213
238
|
}
|
|
@@ -217,6 +242,30 @@ export async function startInteractive() {
|
|
|
217
242
|
displayPrompt(); return;
|
|
218
243
|
}
|
|
219
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
|
+
|
|
220
269
|
// PROCESSAMENTO IA
|
|
221
270
|
const controller = new AbortController();
|
|
222
271
|
const abortHandler = () => controller.abort();
|
|
@@ -239,7 +288,7 @@ export async function startInteractive() {
|
|
|
239
288
|
}
|
|
240
289
|
|
|
241
290
|
messages.push({ role: 'user', content: [...processedContent, { type: 'text', text: modeInstr }] });
|
|
242
|
-
const spinner = ora({ text: lavender(
|
|
291
|
+
const spinner = ora({ text: lavender(`${t.thinking} (Ctrl+C para parar)`), color: currentMode === 'edit' ? 'red' : 'magenta' }).start();
|
|
243
292
|
|
|
244
293
|
try {
|
|
245
294
|
let responseText = await provider.sendMessage(messages, { signal: controller.signal });
|
|
@@ -251,7 +300,7 @@ export async function startInteractive() {
|
|
|
251
300
|
messages.push({ role: 'assistant', content: responseText });
|
|
252
301
|
} catch (err) {
|
|
253
302
|
spinner.stop();
|
|
254
|
-
if (controller.signal.aborted) { console.log(yellow(`\n⚠️
|
|
303
|
+
if (controller.signal.aborted) { console.log(yellow(`\n⚠️ ${t.interrupted}`)); messages.pop(); }
|
|
255
304
|
else { console.error(chalk.red(`\n✖ Erro: ${err.message}`)); }
|
|
256
305
|
} finally {
|
|
257
306
|
process.removeListener('SIGINT', abortHandler);
|