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