bimmo-cli 2.1.3 → 2.2.1
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 +5 -4
- package/src/config.js +17 -0
- package/src/interface.js +88 -30
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bimmo-cli",
|
|
3
|
-
"version": "2.1
|
|
4
|
-
"description": "🌿 Plataforma de IA universal com Modo Normal, Agentes e Swarms. Suporte a
|
|
3
|
+
"version": "2.2.1",
|
|
4
|
+
"description": "🌿 Plataforma de IA universal com Modo Normal, Agentes e Swarms. Suporte a Autocomplete de arquivos, Diffs coloridos e Contexto Inteligente.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"bimmo": "bin/bimmo"
|
|
7
7
|
},
|
|
@@ -18,8 +18,7 @@
|
|
|
18
18
|
"zai",
|
|
19
19
|
"agent",
|
|
20
20
|
"swarm",
|
|
21
|
-
"
|
|
22
|
-
"multimodal",
|
|
21
|
+
"autocomplete",
|
|
23
22
|
"terminal"
|
|
24
23
|
],
|
|
25
24
|
"author": "Judah",
|
|
@@ -42,7 +41,9 @@
|
|
|
42
41
|
"conf": "^13.0.0",
|
|
43
42
|
"diff": "^7.0.0",
|
|
44
43
|
"figlet": "^1.7.0",
|
|
44
|
+
"fuzzy": "^0.1.3",
|
|
45
45
|
"inquirer": "^10.1.0",
|
|
46
|
+
"inquirer-autocomplete-prompt": "^3.0.1",
|
|
46
47
|
"marked": "^14.0.0",
|
|
47
48
|
"marked-terminal": "^7.0.0",
|
|
48
49
|
"mime-types": "^2.1.35",
|
package/src/config.js
CHANGED
|
@@ -30,6 +30,7 @@ export async function configure() {
|
|
|
30
30
|
{ name: 'Criar novo perfil de IA', value: 'create' },
|
|
31
31
|
{ name: 'Selecionar perfil ativo', value: 'select' },
|
|
32
32
|
{ name: 'Gerenciar Agentes Especialistas', value: 'agents' },
|
|
33
|
+
{ name: 'Configurar Idioma', value: 'language' },
|
|
33
34
|
{ name: 'Configurar chave Tavily', value: 'tavily' },
|
|
34
35
|
{ name: 'Sair', value: 'exit' }
|
|
35
36
|
]
|
|
@@ -38,6 +39,22 @@ export async function configure() {
|
|
|
38
39
|
|
|
39
40
|
if (action === 'exit') return;
|
|
40
41
|
|
|
42
|
+
if (action === 'language') {
|
|
43
|
+
const { lang } = await inquirer.prompt([{
|
|
44
|
+
type: 'list',
|
|
45
|
+
name: 'lang',
|
|
46
|
+
message: 'Escolha o idioma do sistema:',
|
|
47
|
+
choices: [
|
|
48
|
+
{ name: 'Português (Brasil)', value: 'pt-BR' },
|
|
49
|
+
{ name: 'English', value: 'en-US' }
|
|
50
|
+
],
|
|
51
|
+
default: config.get('language') || 'pt-BR'
|
|
52
|
+
}]);
|
|
53
|
+
config.set('language', lang);
|
|
54
|
+
console.log(chalk.green(`✓ Idioma definido para: ${lang}`));
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
41
58
|
if (action === 'agents') return configureAgents();
|
|
42
59
|
|
|
43
60
|
if (action === 'tavily') {
|
package/src/interface.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import figlet from 'figlet';
|
|
3
3
|
import inquirer from 'inquirer';
|
|
4
|
+
import autocompletePrompt from 'inquirer-autocomplete-prompt';
|
|
5
|
+
import fuzzy from 'fuzzy';
|
|
4
6
|
import { marked } from 'marked';
|
|
5
7
|
import TerminalRenderer from 'marked-terminal';
|
|
6
8
|
import ora from 'ora';
|
|
@@ -16,12 +18,14 @@ import { getProjectContext } from './project-context.js';
|
|
|
16
18
|
import { SwarmOrchestrator } from './orchestrator.js';
|
|
17
19
|
import { editState } from './agent.js';
|
|
18
20
|
|
|
21
|
+
// Registrar plugin de autocomplete
|
|
22
|
+
inquirer.registerPrompt('autocomplete', autocompletePrompt);
|
|
23
|
+
|
|
19
24
|
const __filename = fileURLToPath(import.meta.url);
|
|
20
25
|
const __dirname = path.dirname(__filename);
|
|
21
26
|
const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, '../package.json'), 'utf-8'));
|
|
22
27
|
const version = pkg.version;
|
|
23
28
|
|
|
24
|
-
// Configuração do renderizador
|
|
25
29
|
marked.use(new TerminalRenderer({
|
|
26
30
|
heading: chalk.hex('#c084fc').bold,
|
|
27
31
|
code: chalk.hex('#00ff9d'),
|
|
@@ -38,6 +42,28 @@ let activePersona = null;
|
|
|
38
42
|
let exitCounter = 0;
|
|
39
43
|
let exitTimer = null;
|
|
40
44
|
|
|
45
|
+
/**
|
|
46
|
+
* Procura arquivos e diretórios recursivamente para o autocomplete do @
|
|
47
|
+
*/
|
|
48
|
+
function getFiles(dir, filter = '') {
|
|
49
|
+
const results = [];
|
|
50
|
+
try {
|
|
51
|
+
const list = fs.readdirSync(dir);
|
|
52
|
+
for (const file of list) {
|
|
53
|
+
if (file === 'node_modules' || file === '.git') continue;
|
|
54
|
+
const fullPath = path.join(dir, file);
|
|
55
|
+
const relPath = path.relative(process.cwd(), fullPath);
|
|
56
|
+
if (relPath.toLowerCase().includes(filter.toLowerCase())) {
|
|
57
|
+
results.push(relPath);
|
|
58
|
+
}
|
|
59
|
+
if (fs.statSync(fullPath).isDirectory()) {
|
|
60
|
+
// Limitamos profundidade para performance se necessário
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
} catch (e) {}
|
|
64
|
+
return results;
|
|
65
|
+
}
|
|
66
|
+
|
|
41
67
|
async function processInput(input) {
|
|
42
68
|
const parts = input.split(' ');
|
|
43
69
|
const processedContent = [];
|
|
@@ -97,21 +123,17 @@ function getModeStyle() {
|
|
|
97
123
|
}
|
|
98
124
|
}
|
|
99
125
|
|
|
100
|
-
/**
|
|
101
|
-
* LIMPEZA BRUTA DE HTML
|
|
102
|
-
* Remove tags como <p>, <br>, <div> e qualquer outra tag que venha da IA.
|
|
103
|
-
*/
|
|
104
126
|
function cleanAIResponse(text) {
|
|
105
127
|
if (!text) return "";
|
|
106
128
|
return text
|
|
107
|
-
.replace(/<br\s*\/?>/gi, '\n')
|
|
108
|
-
.replace(/<\/p>/gi, '\n\n')
|
|
109
|
-
.replace(/<\/div>/gi, '\n')
|
|
110
|
-
.replace(/<[^>]*>?/gm, '')
|
|
111
|
-
.replace(/ /g, ' ')
|
|
112
|
-
.replace(/</g, '<')
|
|
113
|
-
.replace(/>/g, '>')
|
|
114
|
-
.replace(/&/g, '&')
|
|
129
|
+
.replace(/<br\s*\/?>/gi, '\n')
|
|
130
|
+
.replace(/<\/p>/gi, '\n\n')
|
|
131
|
+
.replace(/<\/div>/gi, '\n')
|
|
132
|
+
.replace(/<[^>]*>?/gm, '')
|
|
133
|
+
.replace(/ /g, ' ')
|
|
134
|
+
.replace(/</g, '<')
|
|
135
|
+
.replace(/>/g, '>')
|
|
136
|
+
.replace(/&/g, '&')
|
|
115
137
|
.trim();
|
|
116
138
|
}
|
|
117
139
|
|
|
@@ -146,6 +168,7 @@ export async function startInteractive() {
|
|
|
146
168
|
console.log(lavender(` v${version} `.padStart(60, '─')));
|
|
147
169
|
console.log(green(` Perfil: ${bold(config.activeProfile || 'Padrão')} • IA: ${bold(config.provider.toUpperCase())}`));
|
|
148
170
|
console.log(green(` Modelo: ${bold(config.model)}`));
|
|
171
|
+
console.log(gray(` 📁 ${process.cwd()}`));
|
|
149
172
|
console.log(gray(' /chat | /plan | /edit | /swarm | /use [agente] | /help'));
|
|
150
173
|
console.log(lavender('─'.repeat(60)) + '\n');
|
|
151
174
|
|
|
@@ -170,22 +193,66 @@ export async function startInteractive() {
|
|
|
170
193
|
while (true) {
|
|
171
194
|
const modeIndicator = getModeStyle();
|
|
172
195
|
let input;
|
|
196
|
+
|
|
173
197
|
try {
|
|
174
|
-
|
|
198
|
+
// Usamos autocomplete para permitir navegação de arquivos com @
|
|
199
|
+
const answers = await inquirer.prompt([
|
|
200
|
+
{
|
|
201
|
+
type: 'autocomplete',
|
|
202
|
+
name: 'input',
|
|
203
|
+
message: modeIndicator + green('>'),
|
|
204
|
+
prefix: '',
|
|
205
|
+
source: async (answersSoFar, inputSearch) => {
|
|
206
|
+
const currentInput = inputSearch || '';
|
|
207
|
+
|
|
208
|
+
// Se o usuário digitar @, oferecemos arquivos
|
|
209
|
+
if (currentInput.includes('@')) {
|
|
210
|
+
const lastWord = currentInput.split(' ').pop();
|
|
211
|
+
if (lastWord.startsWith('@')) {
|
|
212
|
+
const searchPath = lastWord.slice(1);
|
|
213
|
+
const files = getFiles(process.cwd(), searchPath);
|
|
214
|
+
return files.map(f => ({
|
|
215
|
+
name: `@${f} ${fs.statSync(f).isDirectory() ? '(DIR)' : '(FILE)'}`,
|
|
216
|
+
value: currentInput.substring(0, currentInput.lastIndexOf('@')) + '@' + f
|
|
217
|
+
}));
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Caso contrário, apenas retorna o que ele está digitando como única opção
|
|
222
|
+
// para não atrapalhar o chat normal
|
|
223
|
+
return [currentInput];
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
]);
|
|
175
227
|
input = answers.input;
|
|
176
228
|
} catch (e) { continue; }
|
|
177
229
|
|
|
230
|
+
// Diretório constante
|
|
178
231
|
console.log(gray(` 📁 ${process.cwd()}`));
|
|
232
|
+
|
|
179
233
|
const rawInput = input.trim();
|
|
180
234
|
const cmd = rawInput.toLowerCase();
|
|
181
235
|
|
|
182
|
-
if (cmd === '/exit' || cmd === 'exit' || cmd === 'sair') {
|
|
236
|
+
if (cmd === '/exit' || cmd === 'exit' || cmd === 'sair') { process.exit(0); }
|
|
183
237
|
if (cmd === '/chat') { currentMode = 'chat'; console.log(lavender('✓ Modo CHAT.\n')); continue; }
|
|
184
238
|
if (cmd === '/plan') { currentMode = 'plan'; console.log(yellow('✓ Modo PLAN.\n')); continue; }
|
|
185
239
|
if (cmd === '/edit') { currentMode = 'edit'; console.log(chalk.red(`⚠️ Modo EDIT ativado.\n`)); continue; }
|
|
186
240
|
if (cmd === '/edit auto') { currentMode = 'edit'; editState.autoAccept = true; console.log(chalk.red('⚠️ Modo EDIT (AUTO) ativado.\n')); continue; }
|
|
187
241
|
if (cmd === '/edit manual') { currentMode = 'edit'; editState.autoAccept = false; console.log(chalk.red('⚠️ Modo EDIT (MANUAL) ativado.\n')); continue; }
|
|
188
242
|
|
|
243
|
+
if (cmd === '/init') {
|
|
244
|
+
const bimmoRcPath = path.join(process.cwd(), '.bimmorc.json');
|
|
245
|
+
if (fs.existsSync(bimmoRcPath)) {
|
|
246
|
+
const { overwrite } = await inquirer.prompt([{ type: 'confirm', name: 'overwrite', message: 'O arquivo .bimmorc.json já existe. Sobrescrever?', default: false }]);
|
|
247
|
+
if (!overwrite) continue;
|
|
248
|
+
}
|
|
249
|
+
const initialConfig = { projectName: path.basename(process.cwd()), rules: ["Siga as convenções."], ignorePatterns: ["node_modules", ".git"] };
|
|
250
|
+
fs.writeFileSync(bimmoRcPath, JSON.stringify(initialConfig, null, 2));
|
|
251
|
+
console.log(green(`\n✅ .bimmorc.json criado com sucesso.\n`));
|
|
252
|
+
resetMessages();
|
|
253
|
+
continue;
|
|
254
|
+
}
|
|
255
|
+
|
|
189
256
|
if (cmd.startsWith('/switch ')) {
|
|
190
257
|
const profileName = rawInput.split(' ')[1];
|
|
191
258
|
if (profileName && switchProfile(profileName)) {
|
|
@@ -199,28 +266,22 @@ export async function startInteractive() {
|
|
|
199
266
|
if (cmd.startsWith('/use ')) {
|
|
200
267
|
const agentName = rawInput.split(' ')[1];
|
|
201
268
|
const agents = config.agents || {};
|
|
202
|
-
if (agentName === 'normal' || agentName === 'default') { activePersona = null;
|
|
269
|
+
if (agentName === 'normal' || agentName === 'default') { activePersona = null; resetMessages(); continue; }
|
|
203
270
|
if (agents[agentName]) {
|
|
204
271
|
activePersona = agentName;
|
|
205
272
|
const agent = agents[agentName];
|
|
206
273
|
if (switchProfile(agent.profile)) { config = getConfig(); provider = createProvider(config); }
|
|
207
274
|
currentMode = agent.mode || 'chat';
|
|
208
|
-
console.log(green(`\n✓
|
|
275
|
+
console.log(green(`\n✓ Ativado Agente: ${bold(agentName)}`));
|
|
209
276
|
resetMessages();
|
|
210
|
-
} else { console.log(chalk.red(`\n✖ Agente
|
|
277
|
+
} else { console.log(chalk.red(`\n✖ Agente não encontrado.\n`)); }
|
|
211
278
|
continue;
|
|
212
279
|
}
|
|
213
280
|
|
|
214
|
-
if (cmd === '/clear') { resetMessages(); console.clear();
|
|
281
|
+
if (cmd === '/clear') { resetMessages(); console.clear(); continue; }
|
|
215
282
|
|
|
216
283
|
if (cmd === '/help') {
|
|
217
|
-
console.log(gray(`
|
|
218
|
-
Comandos:
|
|
219
|
-
/chat /plan /edit [auto/manual] → Mudar modo
|
|
220
|
-
/use [agente] | /use normal → Usar Agentes
|
|
221
|
-
/switch [nome] | /model [nome] → Mudar IA/Modelo
|
|
222
|
-
/config | /init | @arquivo → Configurações
|
|
223
|
-
`));
|
|
284
|
+
console.log(gray(`\nComandos:\n /chat | /plan | /edit [auto/manual] | /init\n /switch [nome] | /model [nome] | /use [agente]\n /config | /clear | @arquivo\n`));
|
|
224
285
|
continue;
|
|
225
286
|
}
|
|
226
287
|
|
|
@@ -245,10 +306,7 @@ Comandos:
|
|
|
245
306
|
try {
|
|
246
307
|
let responseText = await provider.sendMessage(messages, { signal: controller.signal });
|
|
247
308
|
spinner.stop();
|
|
248
|
-
|
|
249
|
-
// EXECUTA LIMPEZA BRUTA
|
|
250
309
|
const cleanedText = cleanAIResponse(responseText);
|
|
251
|
-
|
|
252
310
|
messages.push({ role: 'assistant', content: responseText });
|
|
253
311
|
console.log('\n' + lavender('bimmo ') + getModeStyle());
|
|
254
312
|
console.log(lavender('─'.repeat(50)));
|
|
@@ -256,7 +314,7 @@ Comandos:
|
|
|
256
314
|
console.log(gray('─'.repeat(50)) + '\n');
|
|
257
315
|
} catch (err) {
|
|
258
316
|
spinner.stop();
|
|
259
|
-
if (controller.signal.aborted || err.name === 'AbortError') { console.log(yellow('\n
|
|
317
|
+
if (controller.signal.aborted || err.name === 'AbortError') { console.log(yellow('\n⚠️ Interrompido.\n')); messages.pop(); }
|
|
260
318
|
else { console.error(chalk.red('\n✖ Erro:') + ' ' + err.message + '\n'); }
|
|
261
319
|
} finally {
|
|
262
320
|
process.removeListener('SIGINT', localInterruptHandler);
|