bimmo-cli 2.2.12 → 3.0.0
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/bin/bimmo +8 -1
- package/package.json +12 -3
- package/src/interface.js +313 -263
package/bin/bimmo
CHANGED
|
@@ -6,11 +6,18 @@ process.removeAllListeners('warning');
|
|
|
6
6
|
import { program } from 'commander';
|
|
7
7
|
import { startInteractive } from '../src/interface.js';
|
|
8
8
|
import { configure } from '../src/config.js';
|
|
9
|
+
import fs from 'fs';
|
|
10
|
+
import path from 'path';
|
|
11
|
+
import { fileURLToPath } from 'url';
|
|
12
|
+
|
|
13
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
14
|
+
const __dirname = path.dirname(__filename);
|
|
15
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, '../package.json'), 'utf-8'));
|
|
9
16
|
|
|
10
17
|
program
|
|
11
18
|
.name('bimmo')
|
|
12
19
|
.description('bimmo — Sua IA universal no terminal (verde & lavanda)')
|
|
13
|
-
.version(
|
|
20
|
+
.version(pkg.version);
|
|
14
21
|
|
|
15
22
|
program
|
|
16
23
|
.command('config')
|
package/package.json
CHANGED
|
@@ -1,16 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bimmo-cli",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "🌿 Plataforma de IA universal profissional com
|
|
3
|
+
"version": "3.0.0",
|
|
4
|
+
"description": "🌿 Plataforma de IA universal profissional com interface React (Ink). Agentes, Swarms, Autocomplete real-time e Contexto Inteligente.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"bimmo": "bin/bimmo"
|
|
7
7
|
},
|
|
8
8
|
"type": "module",
|
|
9
9
|
"keywords": [
|
|
10
|
-
"ai", "cli", "
|
|
10
|
+
"ai", "cli", "ink", "react", "openai", "anthropic", "gemini", "agent", "swarm", "terminal"
|
|
11
11
|
],
|
|
12
12
|
"author": "Judah",
|
|
13
13
|
"license": "MIT",
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "git+https://github.com/JudahAragao/bimmo-cli.git"
|
|
17
|
+
},
|
|
14
18
|
"files": [
|
|
15
19
|
"bin/",
|
|
16
20
|
"src/",
|
|
@@ -30,6 +34,10 @@
|
|
|
30
34
|
"diff": "^7.0.0",
|
|
31
35
|
"figlet": "^1.7.0",
|
|
32
36
|
"fuzzy": "^0.1.3",
|
|
37
|
+
"ink": "^5.1.0",
|
|
38
|
+
"ink-divider": "^3.0.0",
|
|
39
|
+
"ink-spinner": "^5.0.0",
|
|
40
|
+
"ink-text-input": "^6.0.0",
|
|
33
41
|
"inquirer": "^9.3.8",
|
|
34
42
|
"marked": "^14.0.0",
|
|
35
43
|
"marked-terminal": "^7.0.0",
|
|
@@ -37,6 +45,7 @@
|
|
|
37
45
|
"ollama": "^0.5.12",
|
|
38
46
|
"openai": "^4.82.0",
|
|
39
47
|
"ora": "^8.1.1",
|
|
48
|
+
"react": "^18.2.0",
|
|
40
49
|
"zod": "^3.24.1"
|
|
41
50
|
}
|
|
42
51
|
}
|
package/src/interface.js
CHANGED
|
@@ -1,310 +1,360 @@
|
|
|
1
|
+
import React, { useState, useEffect, useMemo, useRef } from 'react';
|
|
2
|
+
import { render, Box, Text, useInput, useApp } from 'ink';
|
|
3
|
+
import TextInput from 'ink-text-input';
|
|
4
|
+
import Spinner from 'ink-spinner';
|
|
1
5
|
import chalk from 'chalk';
|
|
2
6
|
import figlet from 'figlet';
|
|
3
7
|
import { marked } from 'marked';
|
|
4
8
|
import TerminalRenderer from 'marked-terminal';
|
|
5
|
-
import ora from 'ora';
|
|
6
9
|
import fs from 'fs';
|
|
7
10
|
import path from 'path';
|
|
8
|
-
import mime from 'mime-types';
|
|
9
|
-
import readline from 'readline';
|
|
10
11
|
import { fileURLToPath } from 'url';
|
|
11
12
|
|
|
12
|
-
import { getConfig,
|
|
13
|
+
import { getConfig, updateActiveModel, switchProfile } from './config.js';
|
|
13
14
|
import { createProvider } from './providers/factory.js';
|
|
14
15
|
import { getProjectContext } from './project-context.js';
|
|
15
|
-
import { SwarmOrchestrator } from './orchestrator.js';
|
|
16
16
|
import { editState } from './agent.js';
|
|
17
|
+
import { SwarmOrchestrator } from './orchestrator.js';
|
|
17
18
|
|
|
18
|
-
|
|
19
|
-
const __dirname = path.dirname(__filename);
|
|
20
|
-
const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, '../package.json'), 'utf-8'));
|
|
21
|
-
const version = pkg.version;
|
|
22
|
-
|
|
23
|
-
// CONFIGURAÇÃO DO RENDERIZADOR - ELIMINA HTML E FORMATA TERMINAL
|
|
19
|
+
// Configuração do renderizador Markdown para o terminal
|
|
24
20
|
marked.use(new TerminalRenderer({
|
|
25
21
|
heading: chalk.hex('#c084fc').bold,
|
|
26
22
|
code: chalk.hex('#00ff9d'),
|
|
27
23
|
strong: chalk.bold,
|
|
28
24
|
em: chalk.italic,
|
|
29
|
-
html: () => '',
|
|
25
|
+
html: () => '',
|
|
30
26
|
}));
|
|
31
27
|
|
|
32
|
-
const
|
|
33
|
-
const
|
|
34
|
-
const
|
|
35
|
-
const
|
|
36
|
-
const yellow = chalk.yellow;
|
|
37
|
-
|
|
38
|
-
let currentMode = 'chat';
|
|
39
|
-
let activePersona = null;
|
|
40
|
-
let exitCounter = 0;
|
|
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
|
-
};
|
|
28
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
29
|
+
const __dirname = path.dirname(__filename);
|
|
30
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, '../package.json'), 'utf-8'));
|
|
31
|
+
const version = pkg.version;
|
|
64
32
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
33
|
+
const green = '#00ff9d';
|
|
34
|
+
const lavender = '#c084fc';
|
|
35
|
+
const gray = '#6272a4';
|
|
36
|
+
const yellow = '#f1fa8c';
|
|
37
|
+
const red = '#ff5555';
|
|
38
|
+
const cyan = '#8be9fd';
|
|
39
|
+
|
|
40
|
+
const BimmoApp = ({ initialConfig }) => {
|
|
41
|
+
const { exit } = useApp();
|
|
42
|
+
const [config, setConfig] = useState(initialConfig);
|
|
43
|
+
const [mode, setMode] = useState('chat');
|
|
44
|
+
const [activePersona, setActivePersona] = useState(null);
|
|
45
|
+
const [messages, setMessages] = useState([]);
|
|
46
|
+
const [input, setInput] = useState('');
|
|
47
|
+
const [isThinking, setIsThinking] = useState(false);
|
|
48
|
+
const [thinkingMessage, setThinkingMessage] = useState('bimmo pensando...');
|
|
49
|
+
const [exitCounter, setExitCounter] = useState(0);
|
|
50
|
+
const [provider, setProvider] = useState(() => createProvider(initialConfig));
|
|
51
|
+
|
|
52
|
+
// Inicializa com o contexto do projeto
|
|
53
|
+
useEffect(() => {
|
|
54
|
+
const ctx = getProjectContext();
|
|
55
|
+
setMessages([{ role: 'system', content: ctx }]);
|
|
56
|
+
|
|
57
|
+
// Welcome message
|
|
58
|
+
setMessages(prev => [...prev, {
|
|
59
|
+
role: 'assistant',
|
|
60
|
+
content: `Olá! Sou o **bimmo v${version}**. Como posso ajudar hoje?\n\nDigite \`/help\` para ver os comandos disponíveis.`
|
|
61
|
+
}]);
|
|
62
|
+
}, []);
|
|
63
|
+
|
|
64
|
+
// Lógica de Autocomplete em tempo real para @arquivos
|
|
65
|
+
const filePreview = useMemo(() => {
|
|
66
|
+
if (!input.includes('@')) return [];
|
|
67
|
+
const words = input.split(' ');
|
|
68
|
+
const lastWord = words[words.length - 1];
|
|
69
|
+
if (!lastWord.startsWith('@')) return [];
|
|
73
70
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
71
|
+
try {
|
|
72
|
+
const p = lastWord.slice(1);
|
|
73
|
+
const dir = p.includes('/') ? p.substring(0, p.lastIndexOf('/')) : '.';
|
|
74
|
+
const filter = p.includes('/') ? p.substring(p.lastIndexOf('/') + 1) : p;
|
|
75
|
+
const absDir = path.resolve(process.cwd(), dir);
|
|
76
|
+
if (!fs.existsSync(absDir)) return [];
|
|
77
|
+
|
|
78
|
+
return fs.readdirSync(absDir)
|
|
79
|
+
.filter(f => f.startsWith(filter) && !f.startsWith('.') && f !== 'node_modules')
|
|
80
|
+
.slice(0, 5)
|
|
81
|
+
.map(f => ({
|
|
82
|
+
name: f,
|
|
83
|
+
isDir: fs.statSync(path.join(absDir, f)).isDirectory(),
|
|
84
|
+
rel: path.join(dir === '.' ? '' : dir, f)
|
|
85
|
+
}));
|
|
86
|
+
} catch (e) { return []; }
|
|
87
|
+
}, [input]);
|
|
88
|
+
|
|
89
|
+
const handleSubmit = async (val) => {
|
|
90
|
+
const rawInput = val.trim();
|
|
91
|
+
if (!rawInput) return;
|
|
92
|
+
setInput('');
|
|
93
|
+
|
|
94
|
+
const lowerInput = rawInput.toLowerCase();
|
|
95
|
+
const parts = rawInput.split(' ');
|
|
96
|
+
const cmd = parts[0].toLowerCase();
|
|
97
|
+
|
|
98
|
+
// COMANDOS INTERNOS
|
|
99
|
+
if (cmd === '/exit' || cmd === 'sair') exit();
|
|
100
|
+
|
|
101
|
+
if (cmd === '/clear') {
|
|
102
|
+
const ctx = getProjectContext();
|
|
103
|
+
setMessages([{ role: 'system', content: ctx }, { role: 'assistant', content: 'Chat limpo.' }]);
|
|
104
|
+
return;
|
|
83
105
|
}
|
|
84
106
|
|
|
85
|
-
|
|
86
|
-
if (
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
.
|
|
90
|
-
|
|
91
|
-
const fullPath = path.join(absoluteSearchDir, f);
|
|
92
|
-
const isDir = fs.statSync(fullPath).isDirectory();
|
|
93
|
-
return { name: f, rel: path.join(searchDir, f), isDir };
|
|
94
|
-
});
|
|
95
|
-
} catch (e) { return []; }
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
function cleanAIResponse(text) {
|
|
99
|
-
if (!text) return "";
|
|
100
|
-
return text
|
|
101
|
-
.replace(/<br\s*\/?>/gi, '\n')
|
|
102
|
-
.replace(/<p>/gi, '')
|
|
103
|
-
.replace(/<\/p>/gi, '\n\n')
|
|
104
|
-
.replace(/<[^>]*>?/gm, '')
|
|
105
|
-
.trim();
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
export async function startInteractive() {
|
|
109
|
-
let config = getConfig();
|
|
110
|
-
const lang = config.language || 'pt-BR';
|
|
111
|
-
const t = i18n[lang] || i18n['pt-BR'];
|
|
112
|
-
|
|
113
|
-
if (!config.provider || !config.apiKey) {
|
|
114
|
-
console.log(lavender(figlet.textSync('bimmo')));
|
|
115
|
-
await configure(); return startInteractive();
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
let provider = createProvider(config);
|
|
119
|
-
const orchestrator = new SwarmOrchestrator(config);
|
|
120
|
-
let messages = [];
|
|
121
|
-
|
|
122
|
-
const resetMessages = () => {
|
|
123
|
-
messages = [{ role: 'system', content: getProjectContext() }];
|
|
124
|
-
if (activePersona) {
|
|
125
|
-
const agent = (config.agents || {})[activePersona];
|
|
126
|
-
if (agent) messages.push({ role: 'system', content: `Persona: ${agent.name}. Task: ${agent.role}` });
|
|
107
|
+
if (cmd === '/chat') { setMode('chat'); return; }
|
|
108
|
+
if (cmd === '/plan') { setMode('plan'); return; }
|
|
109
|
+
if (cmd === '/edit') {
|
|
110
|
+
setMode('edit');
|
|
111
|
+
editState.autoAccept = parts[1] === 'auto';
|
|
112
|
+
return;
|
|
127
113
|
}
|
|
128
|
-
};
|
|
129
|
-
resetMessages();
|
|
130
114
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
input: process.stdin,
|
|
140
|
-
output: process.stdout,
|
|
141
|
-
terminal: true,
|
|
142
|
-
completer: (line) => {
|
|
143
|
-
const words = line.split(' ');
|
|
144
|
-
const lastWord = words[words.length - 1];
|
|
145
|
-
if (lastWord.startsWith('@')) {
|
|
146
|
-
const hits = getFilesForPreview(lastWord).map(h => `@${h.rel}${h.isDir ? '/' : ''}`);
|
|
147
|
-
return [hits, lastWord];
|
|
115
|
+
if (cmd === '/model') {
|
|
116
|
+
const newModel = parts[1];
|
|
117
|
+
if (newModel) {
|
|
118
|
+
updateActiveModel(newModel);
|
|
119
|
+
const newCfg = getConfig();
|
|
120
|
+
setConfig(newCfg);
|
|
121
|
+
setProvider(createProvider(newCfg));
|
|
122
|
+
setMessages(prev => [...prev, { role: 'system', content: `Modelo alterado para: ${newModel}` }]);
|
|
148
123
|
}
|
|
149
|
-
return
|
|
124
|
+
return;
|
|
150
125
|
}
|
|
151
|
-
});
|
|
152
126
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
127
|
+
if (cmd === '/switch') {
|
|
128
|
+
const profile = parts[1];
|
|
129
|
+
if (switchProfile(profile)) {
|
|
130
|
+
const newCfg = getConfig();
|
|
131
|
+
setConfig(newCfg);
|
|
132
|
+
setProvider(createProvider(newCfg));
|
|
133
|
+
setMessages(prev => [...prev, { role: 'system', content: `Perfil alterado para: ${profile}` }]);
|
|
134
|
+
} else {
|
|
135
|
+
setMessages(prev => [...prev, { role: 'system', content: `Perfil "${profile}" não encontrado.` }]);
|
|
160
136
|
}
|
|
161
|
-
|
|
162
|
-
currentPreviewLines = 0;
|
|
137
|
+
return;
|
|
163
138
|
}
|
|
164
|
-
};
|
|
165
139
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
if (
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
const displayPrompt = () => {
|
|
189
|
-
const personaLabel = activePersona ? `[${activePersona.toUpperCase()}]` : '';
|
|
190
|
-
let modeLabel = `[${currentMode.toUpperCase()}]`;
|
|
191
|
-
if (currentMode === 'edit') modeLabel = editState.autoAccept ? '[EDIT(AUTO)]' : '[EDIT(MANUAL)]';
|
|
192
|
-
|
|
193
|
-
console.log(`${gray(`📁 ${process.cwd()}`)}`);
|
|
194
|
-
rl.setPrompt(lavender.bold(personaLabel) + (currentMode === 'edit' ? chalk.red.bold(modeLabel) : lavender.bold(modeLabel)) + green(' > '));
|
|
195
|
-
rl.prompt();
|
|
196
|
-
};
|
|
197
|
-
|
|
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
|
-
|
|
215
|
-
rl.on('line', async (input) => {
|
|
216
|
-
clearPreview();
|
|
217
|
-
const rawInput = input.trim();
|
|
218
|
-
if (rawInput === '') { displayPrompt(); return; }
|
|
219
|
-
|
|
220
|
-
const cmd = rawInput.toLowerCase();
|
|
221
|
-
if (cmd === '/exit' || cmd === 'sair') process.exit(0);
|
|
222
|
-
if (cmd === '/chat') { currentMode = 'chat'; displayPrompt(); return; }
|
|
223
|
-
if (cmd === '/plan') { currentMode = 'plan'; displayPrompt(); return; }
|
|
224
|
-
if (cmd === '/edit' || cmd === '/edit manual') { currentMode = 'edit'; editState.autoAccept = false; displayPrompt(); return; }
|
|
225
|
-
if (cmd === '/edit auto') { currentMode = 'edit'; editState.autoAccept = true; displayPrompt(); return; }
|
|
226
|
-
if (cmd === '/clear') { resetMessages(); console.clear(); displayPrompt(); return; }
|
|
227
|
-
if (cmd === '/help') { console.log(gray(t.help)); displayPrompt(); return; }
|
|
228
|
-
|
|
229
|
-
if (cmd === '/init') {
|
|
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();
|
|
233
|
-
try {
|
|
234
|
-
const res = await provider.sendMessage([...messages, { role: 'user', content: initPrompt }]);
|
|
235
|
-
spinner.stop(); console.log(marked.parse(cleanAIResponse(res)));
|
|
236
|
-
} catch (e) { spinner.stop(); console.error(chalk.red(e.message)); }
|
|
237
|
-
resetMessages(); displayPrompt(); return;
|
|
140
|
+
if (cmd === '/use') {
|
|
141
|
+
const agentName = parts[1];
|
|
142
|
+
const agents = config.agents || {};
|
|
143
|
+
if (agentName === 'normal') {
|
|
144
|
+
setActivePersona(null);
|
|
145
|
+
setMessages(prev => [...prev, { role: 'system', content: 'Modo normal ativado.' }]);
|
|
146
|
+
} else if (agents[agentName]) {
|
|
147
|
+
const agent = agents[agentName];
|
|
148
|
+
setActivePersona(agentName);
|
|
149
|
+
setMode(agent.mode || 'chat');
|
|
150
|
+
if (agent.profile && agent.profile !== config.activeProfile) {
|
|
151
|
+
switchProfile(agent.profile);
|
|
152
|
+
const newCfg = getConfig();
|
|
153
|
+
setConfig(newCfg);
|
|
154
|
+
setProvider(createProvider(newCfg));
|
|
155
|
+
}
|
|
156
|
+
setMessages(prev => [...prev, { role: 'system', content: `Agente "${agentName}" ativo.` }]);
|
|
157
|
+
} else {
|
|
158
|
+
setMessages(prev => [...prev, { role: 'system', content: `Agente "${agentName}" não encontrado.` }]);
|
|
159
|
+
}
|
|
160
|
+
return;
|
|
238
161
|
}
|
|
239
162
|
|
|
240
|
-
if (cmd === '/
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
163
|
+
if (cmd === '/swarm') {
|
|
164
|
+
const swarmType = parts[1];
|
|
165
|
+
const orchestrator = new SwarmOrchestrator(config);
|
|
166
|
+
setIsThinking(true);
|
|
167
|
+
setThinkingMessage('Enxame em ação...');
|
|
244
168
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
169
|
+
try {
|
|
170
|
+
let response;
|
|
171
|
+
if (swarmType === 'seq') {
|
|
172
|
+
const agents = parts[2].split(',');
|
|
173
|
+
const goal = parts.slice(3).join(' ');
|
|
174
|
+
response = await orchestrator.runSequential(agents, goal);
|
|
175
|
+
} else if (swarmType === 'run') {
|
|
176
|
+
const manager = parts[2];
|
|
177
|
+
const workers = parts[3].split(',');
|
|
178
|
+
const goal = parts.slice(4).join(' ');
|
|
179
|
+
response = await orchestrator.runHierarchical(manager, workers, goal);
|
|
180
|
+
}
|
|
181
|
+
setMessages(prev => [...prev, { role: 'user', content: rawInput }, { role: 'assistant', content: response }]);
|
|
182
|
+
} catch (err) {
|
|
183
|
+
setMessages(prev => [...prev, { role: 'system', content: `Erro no enxame: ${err.message}` }]);
|
|
184
|
+
} finally {
|
|
185
|
+
setIsThinking(false);
|
|
186
|
+
setThinkingMessage('bimmo pensando...');
|
|
187
|
+
}
|
|
188
|
+
return;
|
|
252
189
|
}
|
|
253
190
|
|
|
254
|
-
if (cmd
|
|
255
|
-
const
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
191
|
+
if (cmd === '/help') {
|
|
192
|
+
const helpText = `
|
|
193
|
+
**Comandos Disponíveis:**
|
|
194
|
+
\`/chat\` | \`/plan\` | \`/edit [auto|manual]\` - Muda o modo
|
|
195
|
+
\`/switch [perfil]\` - Alterna perfis
|
|
196
|
+
\`/model [modelo]\` - Altera o modelo
|
|
197
|
+
\`/use [agente|normal]\` - Ativa um agente
|
|
198
|
+
\`/swarm seq [agente1,agente2] [objetivo]\` - Enxame sequencial
|
|
199
|
+
\`/swarm run [líder] [worker1,worker2] [objetivo]\` - Enxame hierárquico
|
|
200
|
+
\`/clear\` - Limpa o chat
|
|
201
|
+
\`/exit\` - Encerra o bimmo
|
|
202
|
+
\`@arquivo\` - Inclui conteúdo de arquivo
|
|
203
|
+
`;
|
|
204
|
+
setMessages(prev => [...prev, { role: 'assistant', content: helpText }]);
|
|
205
|
+
return;
|
|
267
206
|
}
|
|
268
207
|
|
|
269
|
-
//
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
if (fs.existsSync(filePath) && fs.statSync(filePath).isFile()) {
|
|
285
|
-
processedContent.push({ type: 'text', text: `\n[ARQUIVO: ${filePath}]\n${fs.readFileSync(filePath, 'utf-8')}\n` });
|
|
286
|
-
} else { processedContent.push({ type: 'text', text: word }); }
|
|
287
|
-
} else { processedContent.push({ type: 'text', text: word }); }
|
|
208
|
+
// ENVIO PARA IA
|
|
209
|
+
setIsThinking(true);
|
|
210
|
+
|
|
211
|
+
let processedInput = rawInput;
|
|
212
|
+
const fileMatches = rawInput.match(/@[\w\.\-\/]+/g);
|
|
213
|
+
if (fileMatches) {
|
|
214
|
+
for (const match of fileMatches) {
|
|
215
|
+
const filePath = match.slice(1);
|
|
216
|
+
try {
|
|
217
|
+
if (fs.existsSync(filePath) && fs.statSync(filePath).isFile()) {
|
|
218
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
219
|
+
processedInput = processedInput.replace(match, `\n\n[Arquivo: ${filePath}]\n\`\`\`\n${content}\n\`\`\`\n`);
|
|
220
|
+
}
|
|
221
|
+
} catch (e) {}
|
|
222
|
+
}
|
|
288
223
|
}
|
|
289
224
|
|
|
290
|
-
|
|
291
|
-
const
|
|
225
|
+
const userMsg = { role: 'user', content: processedInput, displayContent: rawInput };
|
|
226
|
+
const newMessages = [...messages, userMsg];
|
|
227
|
+
setMessages(newMessages);
|
|
292
228
|
|
|
293
229
|
try {
|
|
294
|
-
let
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
230
|
+
let finalMessages = newMessages;
|
|
231
|
+
if (activePersona && config.agents[activePersona]) {
|
|
232
|
+
const agent = config.agents[activePersona];
|
|
233
|
+
finalMessages = [
|
|
234
|
+
{ role: 'system', content: `Sua tarefa: ${agent.role}\n\n${getProjectContext()}` },
|
|
235
|
+
...newMessages.filter(m => m.role !== 'system')
|
|
236
|
+
];
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const response = await provider.sendMessage(finalMessages);
|
|
240
|
+
setMessages(prev => [...prev, { role: 'assistant', content: response }]);
|
|
301
241
|
} catch (err) {
|
|
302
|
-
|
|
303
|
-
if (controller.signal.aborted) { console.log(yellow(`\n⚠️ ${t.interrupted}`)); messages.pop(); }
|
|
304
|
-
else { console.error(chalk.red(`\n✖ Erro: ${err.message}`)); }
|
|
242
|
+
setMessages(prev => [...prev, { role: 'system', content: `Erro: ${err.message}` }]);
|
|
305
243
|
} finally {
|
|
306
|
-
|
|
307
|
-
|
|
244
|
+
setIsThinking(false);
|
|
245
|
+
}
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
useInput((input, key) => {
|
|
249
|
+
if (key.ctrl && input === 'c') {
|
|
250
|
+
if (isThinking) {
|
|
251
|
+
setIsThinking(false);
|
|
252
|
+
} else {
|
|
253
|
+
if (exitCounter === 0) {
|
|
254
|
+
setExitCounter(1);
|
|
255
|
+
setTimeout(() => setExitCounter(0), 2000);
|
|
256
|
+
} else {
|
|
257
|
+
exit();
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (key.tab && filePreview.length > 0) {
|
|
263
|
+
const words = input.split(' ');
|
|
264
|
+
words[words.length - 1] = `@${filePreview[0].rel}${filePreview[0].isDir ? '/' : ''}`;
|
|
265
|
+
setInput(words.join(' '));
|
|
308
266
|
}
|
|
309
267
|
});
|
|
268
|
+
|
|
269
|
+
return (
|
|
270
|
+
<Box flexDirection="column" paddingX={1} minHeight={10}>
|
|
271
|
+
{/* HEADER */}
|
|
272
|
+
<Box flexDirection="column" marginBottom={1}>
|
|
273
|
+
<Text color={lavender}>{figlet.textSync('bimmo', { font: 'small' })}</Text>
|
|
274
|
+
<Box borderStyle="single" borderColor={lavender} paddingX={1} justifyContent="space-between">
|
|
275
|
+
<Text color={green} bold>v{version}</Text>
|
|
276
|
+
<Box>
|
|
277
|
+
<Text color={gray}>{config.activeProfile || 'Default'} </Text>
|
|
278
|
+
<Text color={lavender}>•</Text>
|
|
279
|
+
<Text color={gray}> {config.model}</Text>
|
|
280
|
+
</Box>
|
|
281
|
+
</Box>
|
|
282
|
+
</Box>
|
|
283
|
+
|
|
284
|
+
{/* MENSAGENS */}
|
|
285
|
+
<Box flexDirection="column" flexGrow={1}>
|
|
286
|
+
{messages.filter(m => m.role !== 'system').slice(-10).map((m, i) => (
|
|
287
|
+
<Box key={i} flexDirection="column" marginBottom={1}>
|
|
288
|
+
<Box>
|
|
289
|
+
<Text bold color={m.role === 'user' ? green : lavender}>
|
|
290
|
+
{m.role === 'user' ? '› Você' : '› bimmo'}
|
|
291
|
+
</Text>
|
|
292
|
+
{m.role === 'system' && <Text color={yellow}> [SISTEMA]</Text>}
|
|
293
|
+
</Box>
|
|
294
|
+
<Box paddingLeft={2}>
|
|
295
|
+
<Text>
|
|
296
|
+
{m.role === 'assistant'
|
|
297
|
+
? marked.parse(m.content).trim()
|
|
298
|
+
: (m.displayContent || m.content)}
|
|
299
|
+
</Text>
|
|
300
|
+
</Box>
|
|
301
|
+
</Box>
|
|
302
|
+
))}
|
|
303
|
+
</Box>
|
|
304
|
+
|
|
305
|
+
{/* STATUS / THINKING */}
|
|
306
|
+
{isThinking && (
|
|
307
|
+
<Box marginBottom={1}>
|
|
308
|
+
<Text color={lavender}>
|
|
309
|
+
<Spinner type="dots" /> <Text italic>{thinkingMessage}</Text>
|
|
310
|
+
</Text>
|
|
311
|
+
</Box>
|
|
312
|
+
)}
|
|
313
|
+
|
|
314
|
+
{/* AUTOCOMPLETE PREVIEW */}
|
|
315
|
+
{filePreview.length > 0 && (
|
|
316
|
+
<Box flexDirection="column" borderStyle="round" borderColor={gray} paddingX={1} marginBottom={1}>
|
|
317
|
+
<Text color={gray} dimColor italic>Sugestões (TAB para completar):</Text>
|
|
318
|
+
{filePreview.map((f, i) => (
|
|
319
|
+
<Text key={i} color={i === 0 ? green : gray}>
|
|
320
|
+
{f.isDir ? '📁' : '📄'} {f.rel}{f.isDir ? '/' : ''}
|
|
321
|
+
</Text>
|
|
322
|
+
))}
|
|
323
|
+
</Box>
|
|
324
|
+
)}
|
|
325
|
+
|
|
326
|
+
{/* PROMPT */}
|
|
327
|
+
<Box borderStyle="round" borderColor={isThinking ? gray : lavender} paddingX={1}>
|
|
328
|
+
<Text bold color={mode === 'edit' ? red : mode === 'plan' ? cyan : lavender}>
|
|
329
|
+
{activePersona ? `[${activePersona.toUpperCase()}] ` : ''}
|
|
330
|
+
[{mode.toUpperCase()}] ›{' '}
|
|
331
|
+
</Text>
|
|
332
|
+
<TextInput
|
|
333
|
+
value={input}
|
|
334
|
+
onChange={setInput}
|
|
335
|
+
onSubmit={handleSubmit}
|
|
336
|
+
placeholder="Como posso ajudar hoje?"
|
|
337
|
+
/>
|
|
338
|
+
</Box>
|
|
339
|
+
|
|
340
|
+
{/* FOOTER */}
|
|
341
|
+
<Box marginTop={1} justifyContent="space-between" paddingX={1}>
|
|
342
|
+
<Text color={gray} dimColor>📁 {path.relative(process.env.HOME || '', process.cwd())}</Text>
|
|
343
|
+
{exitCounter === 1 && <Text color={yellow} bold> Pressione Ctrl+C novamente para sair</Text>}
|
|
344
|
+
<Box>
|
|
345
|
+
<Text color={gray} dimColor italic>↑↓ para histórico (em breve) • /help para comandos</Text>
|
|
346
|
+
</Box>
|
|
347
|
+
</Box>
|
|
348
|
+
</Box>
|
|
349
|
+
);
|
|
350
|
+
};
|
|
351
|
+
|
|
352
|
+
export async function startInteractive() {
|
|
353
|
+
const config = getConfig();
|
|
354
|
+
if (!config.provider || !config.apiKey) {
|
|
355
|
+
console.log(chalk.yellow('Provedor não configurado. Execute "bimmo config" primeiro.'));
|
|
356
|
+
process.exit(0);
|
|
357
|
+
}
|
|
358
|
+
process.stdout.write('\x1Bc');
|
|
359
|
+
render(<BimmoApp initialConfig={config} />);
|
|
310
360
|
}
|