bimmo-cli 4.0.0 → 4.0.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/bin/bimmo +22 -3
- package/package.json +10 -1
- package/src/interface.js +112 -80
package/bin/bimmo
CHANGED
|
@@ -4,14 +4,33 @@
|
|
|
4
4
|
process.removeAllListeners('warning');
|
|
5
5
|
|
|
6
6
|
import { program } from 'commander';
|
|
7
|
-
import { startInteractive } from '../src/interface.js';
|
|
8
|
-
import { configure } from '../src/config.js';
|
|
9
7
|
import fs from 'fs';
|
|
10
8
|
import path from 'path';
|
|
11
|
-
import { fileURLToPath } from 'url';
|
|
9
|
+
import { fileURLToPath, pathToFileURL } from 'url';
|
|
12
10
|
|
|
13
11
|
const __filename = fileURLToPath(import.meta.url);
|
|
14
12
|
const __dirname = path.dirname(__filename);
|
|
13
|
+
|
|
14
|
+
// Tenta carregar do dist primeiro (produção), depois do src (desenvolvimento)
|
|
15
|
+
let interfaceModule;
|
|
16
|
+
const distPath = path.join(__dirname, '../dist/interface.js');
|
|
17
|
+
const srcPath = path.join(__dirname, '../src/interface.js');
|
|
18
|
+
|
|
19
|
+
if (fs.existsSync(distPath)) {
|
|
20
|
+
interfaceModule = await import(pathToFileURL(distPath).href);
|
|
21
|
+
} else {
|
|
22
|
+
interfaceModule = await import(pathToFileURL(srcPath).href);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const { startInteractive } = interfaceModule;
|
|
26
|
+
|
|
27
|
+
const configPath = fs.existsSync(path.join(__dirname, '../dist/config.js'))
|
|
28
|
+
? path.join(__dirname, '../dist/config.js')
|
|
29
|
+
: path.join(__dirname, '../src/config.js');
|
|
30
|
+
|
|
31
|
+
const configModule = await import(pathToFileURL(configPath).href);
|
|
32
|
+
|
|
33
|
+
const { configure } = configModule;
|
|
15
34
|
const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, '../package.json'), 'utf-8'));
|
|
16
35
|
|
|
17
36
|
program
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bimmo-cli",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.1",
|
|
4
4
|
"description": "🌿 Plataforma de IA universal profissional com interface React (Ink) pura.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"bimmo": "bin/bimmo"
|
|
@@ -24,6 +24,15 @@
|
|
|
24
24
|
"publishConfig": {
|
|
25
25
|
"access": "public"
|
|
26
26
|
},
|
|
27
|
+
"scripts": {
|
|
28
|
+
"build": "rimraf dist && esbuild src/*.js src/**/*.js --format=esm --outdir=dist --platform=node --target=node20",
|
|
29
|
+
"dev": "node bin/bimmo",
|
|
30
|
+
"start": "npm run build && node bin/bimmo"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"esbuild": "^0.24.0",
|
|
34
|
+
"rimraf": "^6.0.1"
|
|
35
|
+
},
|
|
27
36
|
"dependencies": {
|
|
28
37
|
"@anthropic-ai/sdk": "^0.36.3",
|
|
29
38
|
"@google/generative-ai": "^0.21.0",
|
package/src/interface.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import React, { useState, useEffect, useMemo } from 'react';
|
|
2
|
-
import { render, Box, Text, useInput, useApp } from 'ink';
|
|
2
|
+
import { render, Box, Text, useInput, useApp, Static } from 'ink';
|
|
3
3
|
import TextInput from 'ink-text-input';
|
|
4
4
|
import Spinner from 'ink-spinner';
|
|
5
|
+
import Divider from 'ink-divider';
|
|
5
6
|
import chalk from 'chalk';
|
|
6
7
|
import figlet from 'figlet';
|
|
7
8
|
import { marked } from 'marked';
|
|
@@ -16,16 +17,20 @@ import { getProjectContext } from './project-context.js';
|
|
|
16
17
|
import { editState } from './agent.js';
|
|
17
18
|
import { SwarmOrchestrator } from './orchestrator.js';
|
|
18
19
|
|
|
19
|
-
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
20
|
+
// Cores e Temas
|
|
21
|
+
const THEME = {
|
|
22
|
+
green: '#00ff9d',
|
|
23
|
+
lavender: '#c084fc',
|
|
24
|
+
gray: '#6272a4',
|
|
25
|
+
yellow: '#f1fa8c',
|
|
26
|
+
red: '#ff5555',
|
|
27
|
+
cyan: '#8be9fd',
|
|
28
|
+
border: '#44475a'
|
|
29
|
+
};
|
|
25
30
|
|
|
26
31
|
marked.use(new TerminalRenderer({
|
|
27
|
-
heading: chalk.hex(lavender).bold,
|
|
28
|
-
code: chalk.hex(green),
|
|
32
|
+
heading: chalk.hex(THEME.lavender).bold,
|
|
33
|
+
code: chalk.hex(THEME.green),
|
|
29
34
|
strong: chalk.bold,
|
|
30
35
|
em: chalk.italic,
|
|
31
36
|
html: () => '',
|
|
@@ -33,74 +38,88 @@ marked.use(new TerminalRenderer({
|
|
|
33
38
|
|
|
34
39
|
const __filename = fileURLToPath(import.meta.url);
|
|
35
40
|
const __dirname = path.dirname(__filename);
|
|
36
|
-
|
|
41
|
+
// A localização do package.json depende de onde este arquivo está (src ou dist)
|
|
42
|
+
const pkgPath = fs.existsSync(path.join(__dirname, '../package.json'))
|
|
43
|
+
? path.join(__dirname, '../package.json')
|
|
44
|
+
: path.join(__dirname, '../../package.json');
|
|
45
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
37
46
|
const version = pkg.version;
|
|
38
47
|
|
|
39
48
|
const h = React.createElement;
|
|
40
49
|
|
|
50
|
+
// --- COMPONENTES ---
|
|
51
|
+
|
|
41
52
|
const Header = ({ config }) => (
|
|
42
53
|
h(Box, { flexDirection: 'column', marginBottom: 1 },
|
|
43
|
-
h(Text, { color: lavender }, figlet.textSync('bimmo')),
|
|
44
|
-
h(Box, { borderStyle: '
|
|
45
|
-
h(Text, { color: green
|
|
54
|
+
h(Text, { color: THEME.lavender, bold: true }, figlet.textSync('bimmo', { font: 'Small' })),
|
|
55
|
+
h(Box, { borderStyle: 'round', borderColor: THEME.border, paddingX: 1, justifyContent: 'space-between' },
|
|
56
|
+
h(Text, { color: THEME.green }, `v${version}`),
|
|
46
57
|
h(Box, null,
|
|
47
|
-
h(Text, { color: gray }, `${config.activeProfile || 'Default'} `),
|
|
48
|
-
h(Text, { color: lavender }, '•'),
|
|
49
|
-
h(Text, { color:
|
|
58
|
+
h(Text, { color: THEME.gray }, `${config.activeProfile || 'Default'} `),
|
|
59
|
+
h(Text, { color: THEME.lavender }, '•'),
|
|
60
|
+
h(Text, { color: THEME.cyan }, ` ${config.model}`)
|
|
50
61
|
)
|
|
51
62
|
)
|
|
52
63
|
)
|
|
53
64
|
);
|
|
54
65
|
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
)
|
|
71
|
-
)
|
|
66
|
+
const Message = ({ role, content, displayContent }) => {
|
|
67
|
+
const isUser = role === 'user';
|
|
68
|
+
const color = isUser ? THEME.green : THEME.lavender;
|
|
69
|
+
const label = isUser ? '› VOCÊ' : '› bimmo';
|
|
70
|
+
|
|
71
|
+
return h(Box, { flexDirection: 'column', marginBottom: 1 },
|
|
72
|
+
h(Box, null,
|
|
73
|
+
h(Text, { color, bold: true }, label),
|
|
74
|
+
role === 'system' && h(Text, { color: THEME.yellow }, ' [SISTEMA]')
|
|
75
|
+
),
|
|
76
|
+
h(Box, { paddingLeft: 2 },
|
|
77
|
+
h(Text, null,
|
|
78
|
+
role === 'assistant'
|
|
79
|
+
? marked.parse(content).trim()
|
|
80
|
+
: (displayContent || content)
|
|
72
81
|
)
|
|
73
|
-
)
|
|
74
|
-
)
|
|
75
|
-
|
|
82
|
+
)
|
|
83
|
+
);
|
|
84
|
+
};
|
|
76
85
|
|
|
77
|
-
const
|
|
78
|
-
h(Box, { flexDirection: 'column', borderStyle: 'round', borderColor:
|
|
79
|
-
h(Text, { color: gray, dimColor: true, italic: true }, 'Sugestões (TAB
|
|
86
|
+
const AutocompleteSuggestions = ({ suggestions }) => (
|
|
87
|
+
h(Box, { flexDirection: 'column', borderStyle: 'round', borderColor: THEME.border, paddingX: 1, marginBottom: 1 },
|
|
88
|
+
h(Text, { color: THEME.gray, dimColor: true, italic: true }, 'Sugestões (TAB):'),
|
|
80
89
|
suggestions.map((f, i) => (
|
|
81
|
-
h(Text, { key: i, color: i === 0 ? green : gray },
|
|
90
|
+
h(Text, { key: i, color: i === 0 ? THEME.green : THEME.gray },
|
|
82
91
|
`${f.isDir ? '📁' : '📄'} ${f.rel}${f.isDir ? '/' : ''}`
|
|
83
92
|
)
|
|
84
93
|
))
|
|
85
94
|
)
|
|
86
95
|
);
|
|
87
96
|
|
|
88
|
-
const
|
|
89
|
-
h(Box, { marginTop: 1,
|
|
90
|
-
h(
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
97
|
+
const FooterStatus = ({ mode, activePersona, exitCounter }) => (
|
|
98
|
+
h(Box, { marginTop: 1, flexDirection: 'column' },
|
|
99
|
+
h(Divider, { borderColor: THEME.border }),
|
|
100
|
+
h(Box, { justifyContent: 'space-between', paddingX: 1 },
|
|
101
|
+
h(Box, null,
|
|
102
|
+
h(Text, { color: THEME.gray }, `Modo: `),
|
|
103
|
+
h(Text, { color: mode === 'edit' ? THEME.red : mode === 'plan' ? THEME.cyan : THEME.lavender, bold: true }, mode.toUpperCase()),
|
|
104
|
+
activePersona && h(Text, { color: THEME.yellow }, ` (${activePersona})`)
|
|
105
|
+
),
|
|
106
|
+
h(Text, { color: THEME.gray, dimColor: true }, `📁 ${path.basename(process.cwd())}`),
|
|
107
|
+
exitCounter === 1
|
|
108
|
+
? h(Text, { color: THEME.yellow, bold: true }, ' Pressione Ctrl+C novamente para sair ')
|
|
109
|
+
: h(Text, { color: THEME.gray, italic: true }, ' /help para comandos ')
|
|
94
110
|
)
|
|
95
111
|
)
|
|
96
112
|
);
|
|
97
113
|
|
|
114
|
+
// --- APP PRINCIPAL ---
|
|
115
|
+
|
|
98
116
|
const BimmoApp = ({ initialConfig }) => {
|
|
99
117
|
const { exit } = useApp();
|
|
100
118
|
const [config, setConfig] = useState(initialConfig);
|
|
101
119
|
const [mode, setMode] = useState('chat');
|
|
102
120
|
const [activePersona, setActivePersona] = useState(null);
|
|
103
121
|
const [messages, setMessages] = useState([]);
|
|
122
|
+
const [staticMessages, setStaticMessages] = useState([]); // Para mensagens antigas
|
|
104
123
|
const [input, setInput] = useState('');
|
|
105
124
|
const [isThinking, setIsThinking] = useState(false);
|
|
106
125
|
const [thinkingMessage, setThinkingMessage] = useState('bimmo pensando...');
|
|
@@ -144,8 +163,10 @@ const BimmoApp = ({ initialConfig }) => {
|
|
|
144
163
|
const parts = rawInput.split(' ');
|
|
145
164
|
const cmd = parts[0].toLowerCase();
|
|
146
165
|
|
|
166
|
+
// Comandos de Sistema
|
|
147
167
|
if (cmd === '/exit') exit();
|
|
148
168
|
if (cmd === '/clear') {
|
|
169
|
+
setStaticMessages([]);
|
|
149
170
|
setMessages([{ role: 'system', content: getProjectContext() }, { role: 'assistant', content: 'Chat limpo.' }]);
|
|
150
171
|
return;
|
|
151
172
|
}
|
|
@@ -190,28 +211,12 @@ const BimmoApp = ({ initialConfig }) => {
|
|
|
190
211
|
return;
|
|
191
212
|
}
|
|
192
213
|
|
|
193
|
-
if (cmd === '/swarm') {
|
|
194
|
-
const orchestrator = new SwarmOrchestrator(config);
|
|
195
|
-
setIsThinking(true);
|
|
196
|
-
setThinkingMessage('Enxame em ação...');
|
|
197
|
-
try {
|
|
198
|
-
let response;
|
|
199
|
-
if (parts[1] === 'seq') response = await orchestrator.runSequential(parts[2].split(','), parts.slice(3).join(' '));
|
|
200
|
-
if (parts[1] === 'run') response = await orchestrator.runHierarchical(parts[2], parts[3].split(','), parts.slice(4).join(' '));
|
|
201
|
-
setMessages(prev => [...prev, { role: 'user', content: rawInput }, { role: 'assistant', content: response }]);
|
|
202
|
-
} catch (err) {
|
|
203
|
-
setMessages(prev => [...prev, { role: 'system', content: `Erro no enxame: ${err.message}` }]);
|
|
204
|
-
} finally {
|
|
205
|
-
setIsThinking(false);
|
|
206
|
-
}
|
|
207
|
-
return;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
214
|
if (cmd === '/help') {
|
|
211
|
-
setMessages(prev => [...prev, { role: 'assistant', content: `**Comandos
|
|
215
|
+
setMessages(prev => [...prev, { role: 'assistant', content: `**Comandos Disponíveis:**\n\n- \`/chat\`, \`/plan\`, \`/edit\`: Alternar modos\n- \`/model <nome>\`: Trocar modelo atual\n- \`/switch <perfil>\`: Trocar perfil de API\n- \`/use <agente>\`: Usar agente especializado\n- \`/clear\`: Limpar histórico\n- \`/exit\`: Sair do bimmo\n- \`@arquivo\`: Referenciar arquivo local` }]);
|
|
212
216
|
return;
|
|
213
217
|
}
|
|
214
218
|
|
|
219
|
+
// Processamento de arquivos
|
|
215
220
|
setIsThinking(true);
|
|
216
221
|
let processedInput = rawInput;
|
|
217
222
|
const fileMatches = rawInput.match(/@[\w\.\-\/]+/g);
|
|
@@ -228,15 +233,22 @@ const BimmoApp = ({ initialConfig }) => {
|
|
|
228
233
|
}
|
|
229
234
|
|
|
230
235
|
const userMsg = { role: 'user', content: processedInput, displayContent: rawInput };
|
|
231
|
-
|
|
232
|
-
|
|
236
|
+
|
|
237
|
+
// Move mensagens antigas para static para performance
|
|
238
|
+
if (messages.length > 5) {
|
|
239
|
+
setStaticMessages(prev => [...prev, ...messages.slice(0, -5)]);
|
|
240
|
+
setMessages(prev => [...prev.slice(-5), userMsg]);
|
|
241
|
+
} else {
|
|
242
|
+
setMessages(prev => [...prev, userMsg]);
|
|
243
|
+
}
|
|
233
244
|
|
|
234
245
|
try {
|
|
235
|
-
let finalMessages =
|
|
246
|
+
let finalMessages = [...staticMessages, ...messages, userMsg];
|
|
236
247
|
if (activePersona && config.agents[activePersona]) {
|
|
237
248
|
const agent = config.agents[activePersona];
|
|
238
|
-
finalMessages = [{ role: 'system', content: `Sua tarefa: ${agent.role}\n\n${getProjectContext()}` }, ...
|
|
249
|
+
finalMessages = [{ role: 'system', content: `Sua tarefa: ${agent.role}\n\n${getProjectContext()}` }, ...finalMessages.filter(m => m.role !== 'system')];
|
|
239
250
|
}
|
|
251
|
+
|
|
240
252
|
const response = await provider.sendMessage(finalMessages);
|
|
241
253
|
setMessages(prev => [...prev, { role: 'assistant', content: response }]);
|
|
242
254
|
} catch (err) {
|
|
@@ -250,8 +262,12 @@ const BimmoApp = ({ initialConfig }) => {
|
|
|
250
262
|
if (key.ctrl && input === 'c') {
|
|
251
263
|
if (isThinking) setIsThinking(false);
|
|
252
264
|
else {
|
|
253
|
-
if (exitCounter === 0) {
|
|
254
|
-
|
|
265
|
+
if (exitCounter === 0) {
|
|
266
|
+
setExitCounter(1);
|
|
267
|
+
setTimeout(() => setExitCounter(0), 2000);
|
|
268
|
+
} else {
|
|
269
|
+
exit();
|
|
270
|
+
}
|
|
255
271
|
}
|
|
256
272
|
}
|
|
257
273
|
if (key.tab && filePreview.length > 0) {
|
|
@@ -262,23 +278,36 @@ const BimmoApp = ({ initialConfig }) => {
|
|
|
262
278
|
});
|
|
263
279
|
|
|
264
280
|
return (
|
|
265
|
-
h(Box, { flexDirection: 'column', paddingX: 1, minHeight:
|
|
281
|
+
h(Box, { flexDirection: 'column', paddingX: 2, paddingY: 1, minHeight: 15 },
|
|
266
282
|
h(Header, { config }),
|
|
267
|
-
|
|
283
|
+
|
|
284
|
+
h(Box, { flexDirection: 'column', flexGrow: 1, marginBottom: 1 },
|
|
285
|
+
h(Static, { items: staticMessages }, (m, i) => h(Message, { key: `static-${i}`, ...m })),
|
|
286
|
+
messages.filter(m => m.role !== 'system').map((m, i) => h(Message, { key: i, ...m }))
|
|
287
|
+
),
|
|
288
|
+
|
|
268
289
|
isThinking && h(Box, { marginBottom: 1 },
|
|
269
|
-
h(Text, { color: lavender },
|
|
290
|
+
h(Text, { color: THEME.lavender },
|
|
270
291
|
h(Spinner, { type: 'dots' }),
|
|
271
292
|
h(Text, { italic: true }, ` ${thinkingMessage}`)
|
|
272
293
|
)
|
|
273
294
|
),
|
|
274
|
-
|
|
275
|
-
h(
|
|
276
|
-
|
|
277
|
-
|
|
295
|
+
|
|
296
|
+
filePreview.length > 0 && h(AutocompleteSuggestions, { suggestions: filePreview }),
|
|
297
|
+
|
|
298
|
+
h(Box, { borderStyle: 'round', borderColor: isThinking ? THEME.gray : THEME.lavender, paddingX: 1 },
|
|
299
|
+
h(Text, { bold: true, color: mode === 'edit' ? THEME.red : mode === 'plan' ? THEME.cyan : THEME.lavender },
|
|
300
|
+
`${activePersona ? `[${activePersona.toUpperCase()}] ` : ''}› `
|
|
278
301
|
),
|
|
279
|
-
h(TextInput, {
|
|
302
|
+
h(TextInput, {
|
|
303
|
+
value: input,
|
|
304
|
+
onChange: setInput,
|
|
305
|
+
onSubmit: handleSubmit,
|
|
306
|
+
placeholder: 'Diga algo ou use / para comandos...'
|
|
307
|
+
})
|
|
280
308
|
),
|
|
281
|
-
|
|
309
|
+
|
|
310
|
+
h(FooterStatus, { mode, activePersona, exitCounter })
|
|
282
311
|
)
|
|
283
312
|
);
|
|
284
313
|
};
|
|
@@ -286,9 +315,12 @@ const BimmoApp = ({ initialConfig }) => {
|
|
|
286
315
|
export async function startInteractive() {
|
|
287
316
|
const config = getConfig();
|
|
288
317
|
if (!config.provider || !config.apiKey) {
|
|
289
|
-
console.log(chalk.yellow('Provedor não configurado.
|
|
318
|
+
console.log(chalk.yellow('\n⚠️ Provedor não configurado.'));
|
|
319
|
+
console.log(chalk.gray('Execute "bimmo config" para configurar sua chave de API.\n'));
|
|
290
320
|
process.exit(0);
|
|
291
321
|
}
|
|
322
|
+
|
|
323
|
+
// Limpa tela e inicia
|
|
292
324
|
process.stdout.write('\x1Bc');
|
|
293
325
|
render(h(BimmoApp, { initialConfig: config }));
|
|
294
326
|
}
|