bimmo-cli 2.2.8 → 2.2.10
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 +69 -130
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.10",
|
|
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
|
@@ -20,16 +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
|
-
|
|
24
|
-
const terminalRenderer = new TerminalRenderer({
|
|
23
|
+
marked.use(new TerminalRenderer({
|
|
25
24
|
heading: chalk.hex('#c084fc').bold,
|
|
26
25
|
code: chalk.hex('#00ff9d'),
|
|
27
26
|
strong: chalk.bold,
|
|
28
27
|
em: chalk.italic,
|
|
29
|
-
html: () => '',
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
marked.setOptions({ renderer: terminalRenderer });
|
|
28
|
+
html: () => '',
|
|
29
|
+
}));
|
|
33
30
|
|
|
34
31
|
const green = chalk.hex('#00ff9d');
|
|
35
32
|
const lavender = chalk.hex('#c084fc');
|
|
@@ -42,47 +39,33 @@ let activePersona = null;
|
|
|
42
39
|
let exitCounter = 0;
|
|
43
40
|
let exitTimer = null;
|
|
44
41
|
|
|
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
42
|
/**
|
|
69
|
-
* Coleta arquivos para preview e completion
|
|
43
|
+
* Coleta arquivos para preview e completion (Nível Gemini-CLI)
|
|
70
44
|
*/
|
|
71
45
|
function getFilesForPreview(partialPath) {
|
|
72
46
|
try {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
47
|
+
let p = partialPath.startsWith('@') ? partialPath.slice(1) : partialPath;
|
|
48
|
+
let searchDir = '.';
|
|
49
|
+
let filter = '';
|
|
50
|
+
|
|
51
|
+
if (p.includes('/')) {
|
|
52
|
+
const lastSlash = p.lastIndexOf('/');
|
|
53
|
+
searchDir = p.substring(0, lastSlash) || '.';
|
|
54
|
+
filter = p.substring(lastSlash + 1);
|
|
55
|
+
} else {
|
|
56
|
+
searchDir = '.';
|
|
57
|
+
filter = p;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const absoluteSearchDir = path.resolve(process.cwd(), searchDir);
|
|
61
|
+
if (!fs.existsSync(absoluteSearchDir)) return [];
|
|
79
62
|
|
|
80
|
-
const files = fs.readdirSync(
|
|
63
|
+
const files = fs.readdirSync(absoluteSearchDir);
|
|
81
64
|
return files
|
|
82
|
-
.filter(f =>
|
|
65
|
+
.filter(f => f.startsWith(filter) && !f.startsWith('.') && f !== 'node_modules')
|
|
83
66
|
.map(f => {
|
|
84
|
-
const rel = path.join(
|
|
85
|
-
const isDir = fs.statSync(path.join(
|
|
67
|
+
const rel = path.join(searchDir === '.' ? '' : searchDir, f);
|
|
68
|
+
const isDir = fs.statSync(path.join(absoluteSearchDir, f)).isDirectory();
|
|
86
69
|
return { name: rel, isDir };
|
|
87
70
|
});
|
|
88
71
|
} catch (e) { return []; }
|
|
@@ -100,13 +83,9 @@ function cleanAIResponse(text) {
|
|
|
100
83
|
|
|
101
84
|
export async function startInteractive() {
|
|
102
85
|
let config = getConfig();
|
|
103
|
-
const lang = config.language || 'pt-BR';
|
|
104
|
-
const t = i18n[lang] || i18n['pt-BR'];
|
|
105
|
-
|
|
106
86
|
if (!config.provider || !config.apiKey) {
|
|
107
87
|
console.log(lavender(figlet.textSync('bimmo')));
|
|
108
|
-
await configure();
|
|
109
|
-
return startInteractive();
|
|
88
|
+
await configure(); return startInteractive();
|
|
110
89
|
}
|
|
111
90
|
|
|
112
91
|
let provider = createProvider(config);
|
|
@@ -114,14 +93,12 @@ export async function startInteractive() {
|
|
|
114
93
|
let messages = [];
|
|
115
94
|
|
|
116
95
|
const resetMessages = () => {
|
|
117
|
-
messages = [];
|
|
118
|
-
messages.push({ role: 'system', content: getProjectContext() });
|
|
96
|
+
messages = [{ role: 'system', content: getProjectContext() }];
|
|
119
97
|
if (activePersona) {
|
|
120
98
|
const agent = (config.agents || {})[activePersona];
|
|
121
99
|
if (agent) messages.push({ role: 'system', content: `Persona: ${agent.name}. Task: ${agent.role}` });
|
|
122
100
|
}
|
|
123
101
|
};
|
|
124
|
-
|
|
125
102
|
resetMessages();
|
|
126
103
|
|
|
127
104
|
console.clear();
|
|
@@ -131,13 +108,10 @@ export async function startInteractive() {
|
|
|
131
108
|
console.log(green(` Modelo: ${bold(config.model)}`));
|
|
132
109
|
console.log(lavender('─'.repeat(60)) + '\n');
|
|
133
110
|
|
|
134
|
-
console.log(lavender(`👋 ${t.welcome}\n`));
|
|
135
|
-
|
|
136
111
|
const rl = readline.createInterface({
|
|
137
112
|
input: process.stdin,
|
|
138
113
|
output: process.stdout,
|
|
139
114
|
terminal: true,
|
|
140
|
-
historySize: 100,
|
|
141
115
|
completer: (line) => {
|
|
142
116
|
const words = line.split(' ');
|
|
143
117
|
const lastWord = words[words.length - 1];
|
|
@@ -152,52 +126,41 @@ export async function startInteractive() {
|
|
|
152
126
|
let currentPreviewLines = 0;
|
|
153
127
|
|
|
154
128
|
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
129
|
if (currentPreviewLines > 0) {
|
|
130
|
+
// Move para baixo, limpa cada linha e volta
|
|
131
|
+
for (let i = 0; i < currentPreviewLines; i++) {
|
|
132
|
+
process.stdout.write('\n');
|
|
133
|
+
readline.clearLine(process.stdout, 0);
|
|
134
|
+
}
|
|
160
135
|
readline.moveCursor(process.stdout, 0, -currentPreviewLines);
|
|
136
|
+
currentPreviewLines = 0;
|
|
161
137
|
}
|
|
162
|
-
currentPreviewLines = 0;
|
|
163
138
|
};
|
|
164
139
|
|
|
165
|
-
const showPreview = (
|
|
140
|
+
const showPreview = () => {
|
|
166
141
|
clearPreview();
|
|
167
|
-
const words = line.split(' ');
|
|
142
|
+
const words = rl.line.split(' ');
|
|
168
143
|
const lastWord = words[words.length - 1];
|
|
169
144
|
|
|
170
145
|
if (lastWord.startsWith('@')) {
|
|
171
146
|
const files = getFilesForPreview(lastWord);
|
|
172
147
|
if (files.length > 0) {
|
|
148
|
+
// Salva a posição do cursor
|
|
149
|
+
process.stdout.write('\u001b[s');
|
|
150
|
+
|
|
173
151
|
process.stdout.write('\n');
|
|
174
|
-
files.slice(0, 10)
|
|
152
|
+
const displayFiles = files.slice(0, 10);
|
|
153
|
+
displayFiles.forEach(f => {
|
|
175
154
|
process.stdout.write(gray(` ${f.isDir ? '📁' : '📄'} ${f.name}\n`));
|
|
176
155
|
});
|
|
177
|
-
currentPreviewLines =
|
|
178
|
-
|
|
179
|
-
|
|
156
|
+
currentPreviewLines = displayFiles.length + 1;
|
|
157
|
+
|
|
158
|
+
// Restaura a posição do cursor
|
|
159
|
+
process.stdout.write('\u001b[u');
|
|
180
160
|
}
|
|
181
161
|
}
|
|
182
162
|
};
|
|
183
163
|
|
|
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
|
-
|
|
190
|
-
rl.on('SIGINT', () => {
|
|
191
|
-
if (exitCounter === 0) {
|
|
192
|
-
exitCounter++;
|
|
193
|
-
console.log(`\n${gray(t.exitHint)}`);
|
|
194
|
-
exitTimer = setTimeout(() => { exitCounter = 0; }, 2000);
|
|
195
|
-
displayPrompt();
|
|
196
|
-
} else {
|
|
197
|
-
process.exit(0);
|
|
198
|
-
}
|
|
199
|
-
});
|
|
200
|
-
|
|
201
164
|
const displayPrompt = () => {
|
|
202
165
|
const personaLabel = activePersona ? `[${activePersona.toUpperCase()}]` : '';
|
|
203
166
|
let modeLabel = `[${currentMode.toUpperCase()}]`;
|
|
@@ -208,37 +171,45 @@ export async function startInteractive() {
|
|
|
208
171
|
rl.prompt();
|
|
209
172
|
};
|
|
210
173
|
|
|
174
|
+
process.stdin.on('keypress', (s, key) => {
|
|
175
|
+
if (key && (key.name === 'return' || key.name === 'enter')) return;
|
|
176
|
+
setImmediate(() => showPreview());
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
rl.on('SIGINT', () => {
|
|
180
|
+
if (exitCounter === 0) {
|
|
181
|
+
exitCounter++;
|
|
182
|
+
process.stdout.write(`\n${gray('(Pressione Ctrl+C novamente para sair)')}\n`);
|
|
183
|
+
exitTimer = setTimeout(() => { exitCounter = 0; }, 2000);
|
|
184
|
+
displayPrompt();
|
|
185
|
+
} else { process.exit(0); }
|
|
186
|
+
});
|
|
187
|
+
|
|
211
188
|
displayPrompt();
|
|
212
189
|
|
|
213
190
|
rl.on('line', async (input) => {
|
|
214
191
|
clearPreview();
|
|
215
192
|
const rawInput = input.trim();
|
|
216
|
-
const cmd = rawInput.toLowerCase();
|
|
217
|
-
|
|
218
193
|
if (rawInput === '') { displayPrompt(); return; }
|
|
219
194
|
|
|
195
|
+
const cmd = rawInput.toLowerCase();
|
|
220
196
|
if (cmd === '/exit' || cmd === 'sair') process.exit(0);
|
|
221
197
|
if (cmd === '/chat') { currentMode = 'chat'; displayPrompt(); return; }
|
|
222
198
|
if (cmd === '/plan') { currentMode = 'plan'; displayPrompt(); return; }
|
|
223
199
|
if (cmd === '/edit' || cmd === '/edit manual') { currentMode = 'edit'; editState.autoAccept = false; displayPrompt(); return; }
|
|
224
200
|
if (cmd === '/edit auto') { currentMode = 'edit'; editState.autoAccept = true; displayPrompt(); return; }
|
|
225
201
|
if (cmd === '/clear') { resetMessages(); console.clear(); displayPrompt(); return; }
|
|
226
|
-
|
|
227
|
-
|
|
202
|
+
|
|
228
203
|
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();
|
|
204
|
+
console.log(chalk.cyan('\n🚀 Gerando .bimmorc.json...\n'));
|
|
205
|
+
const initPrompt = `Analise o projeto e crie o .bimmorc.json estruturado. Use write_file.`;
|
|
206
|
+
const spinner = ora({ text: lavender(`bimmo pensando...`), color: 'red' }).start();
|
|
234
207
|
try {
|
|
235
|
-
const res = await provider.sendMessage([...messages, { role: 'user', content: initPrompt }]
|
|
208
|
+
const res = await provider.sendMessage([...messages, { role: 'user', content: initPrompt }]);
|
|
236
209
|
spinner.stop();
|
|
237
210
|
console.log(marked.parse(cleanAIResponse(res)));
|
|
238
211
|
} catch (e) { spinner.stop(); console.error(chalk.red(e.message)); }
|
|
239
|
-
resetMessages();
|
|
240
|
-
displayPrompt();
|
|
241
|
-
return;
|
|
212
|
+
resetMessages(); displayPrompt(); return;
|
|
242
213
|
}
|
|
243
214
|
|
|
244
215
|
if (cmd === '/config') {
|
|
@@ -246,36 +217,10 @@ export async function startInteractive() {
|
|
|
246
217
|
displayPrompt(); return;
|
|
247
218
|
}
|
|
248
219
|
|
|
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;
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
if (cmd.startsWith('/use ')) {
|
|
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
|
-
}
|
|
272
|
-
|
|
273
220
|
// PROCESSAMENTO IA
|
|
274
221
|
const controller = new AbortController();
|
|
275
222
|
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');
|
|
223
|
+
process.removeListener('SIGINT', () => {});
|
|
279
224
|
process.on('SIGINT', abortHandler);
|
|
280
225
|
|
|
281
226
|
let modeInstr = "";
|
|
@@ -288,34 +233,28 @@ export async function startInteractive() {
|
|
|
288
233
|
if (word.startsWith('@')) {
|
|
289
234
|
const filePath = word.slice(1);
|
|
290
235
|
if (fs.existsSync(filePath) && fs.statSync(filePath).isFile()) {
|
|
291
|
-
|
|
292
|
-
processedContent.push({ type: 'text', text: `\n[ARQUIVO: ${filePath}]\n${content}\n` });
|
|
236
|
+
processedContent.push({ type: 'text', text: `\n[ARQUIVO: ${filePath}]\n${fs.readFileSync(filePath, 'utf-8')}\n` });
|
|
293
237
|
} else { processedContent.push({ type: 'text', text: word }); }
|
|
294
238
|
} else { processedContent.push({ type: 'text', text: word }); }
|
|
295
239
|
}
|
|
296
240
|
|
|
297
241
|
messages.push({ role: 'user', content: [...processedContent, { type: 'text', text: modeInstr }] });
|
|
298
|
-
const spinner = ora({ text: lavender(
|
|
242
|
+
const spinner = ora({ text: lavender(`bimmo pensando...`), color: currentMode === 'edit' ? 'red' : 'magenta' }).start();
|
|
299
243
|
|
|
300
244
|
try {
|
|
301
245
|
let responseText = await provider.sendMessage(messages, { signal: controller.signal });
|
|
302
246
|
spinner.stop();
|
|
303
|
-
const cleanedText = cleanAIResponse(responseText);
|
|
304
|
-
messages.push({ role: 'assistant', content: responseText });
|
|
305
247
|
console.log(`\n${lavender('bimmo ')}${currentMode.toUpperCase()}`);
|
|
306
248
|
console.log(lavender('─'.repeat(50)));
|
|
307
|
-
process.stdout.write(marked.parse(
|
|
249
|
+
process.stdout.write(marked.parse(cleanAIResponse(responseText)));
|
|
308
250
|
console.log(gray('\n' + '─'.repeat(50)));
|
|
251
|
+
messages.push({ role: 'assistant', content: responseText });
|
|
309
252
|
} catch (err) {
|
|
310
253
|
spinner.stop();
|
|
311
|
-
if (controller.signal.aborted) { console.log(yellow(`\n⚠️
|
|
254
|
+
if (controller.signal.aborted) { console.log(yellow(`\n⚠️ Interrompido.`)); messages.pop(); }
|
|
312
255
|
else { console.error(chalk.red(`\n✖ Erro: ${err.message}`)); }
|
|
313
256
|
} finally {
|
|
314
257
|
process.removeListener('SIGINT', abortHandler);
|
|
315
|
-
// Reativa o listener de preview
|
|
316
|
-
process.stdin.on('keypress', (s, key) => {
|
|
317
|
-
setImmediate(() => showPreview(rl.line));
|
|
318
|
-
});
|
|
319
258
|
displayPrompt();
|
|
320
259
|
}
|
|
321
260
|
});
|