bimmo-cli 3.0.0 → 3.1.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 CHANGED
@@ -1,10 +1,10 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env -S node --import=tsx
2
2
 
3
3
  // Silencia o aviso de depreciação do punycode que polui o terminal em versões novas do Node
4
4
  process.removeAllListeners('warning');
5
5
 
6
6
  import { program } from 'commander';
7
- import { startInteractive } from '../src/interface.js';
7
+ import { startInteractive } from '../src/interface.jsx';
8
8
  import { configure } from '../src/config.js';
9
9
  import fs from 'fs';
10
10
  import path from 'path';
package/package.json CHANGED
@@ -1,13 +1,22 @@
1
1
  {
2
2
  "name": "bimmo-cli",
3
- "version": "3.0.0",
3
+ "version": "3.1.0",
4
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", "ink", "react", "openai", "anthropic", "gemini", "agent", "swarm", "terminal"
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",
@@ -25,6 +34,7 @@
25
34
  "access": "public"
26
35
  },
27
36
  "dependencies": {
37
+ "tsx": "^4.19.2",
28
38
  "@anthropic-ai/sdk": "^0.36.3",
29
39
  "@google/generative-ai": "^0.21.0",
30
40
  "@tavily/core": "^0.0.2",
@@ -39,6 +49,7 @@
39
49
  "ink-spinner": "^5.0.0",
40
50
  "ink-text-input": "^6.0.0",
41
51
  "inquirer": "^9.3.8",
52
+ "jiti": "^2.6.1",
42
53
  "marked": "^14.0.0",
43
54
  "marked-terminal": "^7.0.0",
44
55
  "mime-types": "^2.1.35",
@@ -47,5 +58,8 @@
47
58
  "ora": "^8.1.1",
48
59
  "react": "^18.2.0",
49
60
  "zod": "^3.24.1"
61
+ },
62
+ "devDependencies": {
63
+ "tsx": "^4.21.0"
50
64
  }
51
65
  }
@@ -16,10 +16,17 @@ import { getProjectContext } from './project-context.js';
16
16
  import { editState } from './agent.js';
17
17
  import { SwarmOrchestrator } from './orchestrator.js';
18
18
 
19
- // Configuração do renderizador Markdown para o terminal
19
+ // --- CONFIGURAÇÃO VISUAL ---
20
+ const green = '#00ff9d';
21
+ const lavender = '#c084fc';
22
+ const gray = '#6272a4';
23
+ const yellow = '#f1fa8c';
24
+ const red = '#ff5555';
25
+ const cyan = '#8be9fd';
26
+
20
27
  marked.use(new TerminalRenderer({
21
- heading: chalk.hex('#c084fc').bold,
22
- code: chalk.hex('#00ff9d'),
28
+ heading: chalk.hex(lavender).bold,
29
+ code: chalk.hex(green),
23
30
  strong: chalk.bold,
24
31
  em: chalk.italic,
25
32
  html: () => '',
@@ -30,12 +37,64 @@ const __dirname = path.dirname(__filename);
30
37
  const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, '../package.json'), 'utf-8'));
31
38
  const version = pkg.version;
32
39
 
33
- const green = '#00ff9d';
34
- const lavender = '#c084fc';
35
- const gray = '#6272a4';
36
- const yellow = '#f1fa8c';
37
- const red = '#ff5555';
38
- const cyan = '#8be9fd';
40
+ // --- COMPONENTES ---
41
+
42
+ const Header = ({ config }) => (
43
+ <Box flexDirection="column" marginBottom={1}>
44
+ <Text color={lavender}>{figlet.textSync('bimmo', { font: 'small' })}</Text>
45
+ <Box borderStyle="single" borderColor={lavender} paddingX={1} justifyContent="space-between">
46
+ <Text color={green} bold>v{version}</Text>
47
+ <Box>
48
+ <Text color={gray}>{config.activeProfile || 'Default'} </Text>
49
+ <Text color={lavender}>•</Text>
50
+ <Text color={gray}> {config.model}</Text>
51
+ </Box>
52
+ </Box>
53
+ </Box>
54
+ );
55
+
56
+ const MessageList = ({ messages }) => (
57
+ <Box flexDirection="column" flexGrow={1}>
58
+ {messages.filter(m => m.role !== 'system').slice(-10).map((m, i) => (
59
+ <Box key={i} flexDirection="column" marginBottom={1}>
60
+ <Box>
61
+ <Text bold color={m.role === 'user' ? green : lavender}>
62
+ {m.role === 'user' ? '› Você' : '› bimmo'}
63
+ </Text>
64
+ {m.role === 'system' && <Text color={yellow}> [SISTEMA]</Text>}
65
+ </Box>
66
+ <Box paddingLeft={2}>
67
+ <Text>
68
+ {m.role === 'assistant'
69
+ ? marked.parse(m.content).trim()
70
+ : (m.displayContent || m.content)}
71
+ </Text>
72
+ </Box>
73
+ </Box>
74
+ ))}
75
+ </Box>
76
+ );
77
+
78
+ const Autocomplete = ({ suggestions }) => (
79
+ <Box flexDirection="column" borderStyle="round" borderColor={gray} paddingX={1} marginBottom={1}>
80
+ <Text color={gray} dimColor italic>Sugestões (TAB para completar):</Text>
81
+ {suggestions.map((f, i) => (
82
+ <Text key={i} color={i === 0 ? green : gray}>
83
+ {f.isDir ? '📁' : '📄'} {f.rel}{f.isDir ? '/' : ''}
84
+ </Text>
85
+ ))}
86
+ </Box>
87
+ );
88
+
89
+ const Footer = ({ exitCounter }) => (
90
+ <Box marginTop={1} justifyContent="space-between" paddingX={1}>
91
+ <Text color={gray} dimColor>📁 {path.relative(process.env.HOME || '', process.cwd())}</Text>
92
+ {exitCounter === 1 && <Text color={yellow} bold> Pressione Ctrl+C novamente para sair</Text>}
93
+ <Box>
94
+ <Text color={gray} dimColor italic>↑↓ para histórico • /help para comandos</Text>
95
+ </Box>
96
+ </Box>
97
+ );
39
98
 
40
99
  const BimmoApp = ({ initialConfig }) => {
41
100
  const { exit } = useApp();
@@ -49,32 +108,26 @@ const BimmoApp = ({ initialConfig }) => {
49
108
  const [exitCounter, setExitCounter] = useState(0);
50
109
  const [provider, setProvider] = useState(() => createProvider(initialConfig));
51
110
 
52
- // Inicializa com o contexto do projeto
111
+ // Inicializa contexto
53
112
  useEffect(() => {
54
113
  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
- }]);
114
+ setMessages([
115
+ { role: 'system', content: ctx },
116
+ { role: 'assistant', content: `Olá! Sou o **bimmo v${version}**. Como posso ajudar hoje?\n\nDigite \`/help\` para ver os comandos.` }
117
+ ]);
62
118
  }, []);
63
119
 
64
- // Lógica de Autocomplete em tempo real para @arquivos
65
120
  const filePreview = useMemo(() => {
66
121
  if (!input.includes('@')) return [];
67
122
  const words = input.split(' ');
68
123
  const lastWord = words[words.length - 1];
69
124
  if (!lastWord.startsWith('@')) return [];
70
-
71
125
  try {
72
126
  const p = lastWord.slice(1);
73
127
  const dir = p.includes('/') ? p.substring(0, p.lastIndexOf('/')) : '.';
74
128
  const filter = p.includes('/') ? p.substring(p.lastIndexOf('/') + 1) : p;
75
129
  const absDir = path.resolve(process.cwd(), dir);
76
130
  if (!fs.existsSync(absDir)) return [];
77
-
78
131
  return fs.readdirSync(absDir)
79
132
  .filter(f => f.startsWith(filter) && !f.startsWith('.') && f !== 'node_modules')
80
133
  .slice(0, 5)
@@ -90,26 +143,19 @@ const BimmoApp = ({ initialConfig }) => {
90
143
  const rawInput = val.trim();
91
144
  if (!rawInput) return;
92
145
  setInput('');
93
-
94
- const lowerInput = rawInput.toLowerCase();
95
146
  const parts = rawInput.split(' ');
96
147
  const cmd = parts[0].toLowerCase();
97
148
 
98
- // COMANDOS INTERNOS
99
- if (cmd === '/exit' || cmd === 'sair') exit();
100
-
149
+ // Comandos de Sistema
150
+ if (cmd === '/exit') exit();
101
151
  if (cmd === '/clear') {
102
- const ctx = getProjectContext();
103
- setMessages([{ role: 'system', content: ctx }, { role: 'assistant', content: 'Chat limpo.' }]);
152
+ setMessages([{ role: 'system', content: getProjectContext() }, { role: 'assistant', content: 'Chat limpo.' }]);
104
153
  return;
105
154
  }
106
-
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;
155
+ if (['/chat', '/plan', '/edit'].includes(cmd)) {
156
+ setMode(cmd.slice(1));
157
+ if (cmd === '/edit') editState.autoAccept = parts[1] === 'auto';
158
+ return;
113
159
  }
114
160
 
115
161
  if (cmd === '/model') {
@@ -131,8 +177,6 @@ const BimmoApp = ({ initialConfig }) => {
131
177
  setConfig(newCfg);
132
178
  setProvider(createProvider(newCfg));
133
179
  setMessages(prev => [...prev, { role: 'system', content: `Perfil alterado para: ${profile}` }]);
134
- } else {
135
- setMessages(prev => [...prev, { role: 'system', content: `Perfil "${profile}" não encontrado.` }]);
136
180
  }
137
181
  return;
138
182
  }
@@ -142,79 +186,44 @@ const BimmoApp = ({ initialConfig }) => {
142
186
  const agents = config.agents || {};
143
187
  if (agentName === 'normal') {
144
188
  setActivePersona(null);
145
- setMessages(prev => [...prev, { role: 'system', content: 'Modo normal ativado.' }]);
146
189
  } else if (agents[agentName]) {
147
- const agent = agents[agentName];
148
190
  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.` }]);
191
+ setMode(agents[agentName].mode || 'chat');
159
192
  }
160
193
  return;
161
194
  }
162
195
 
163
196
  if (cmd === '/swarm') {
164
- const swarmType = parts[1];
165
197
  const orchestrator = new SwarmOrchestrator(config);
166
198
  setIsThinking(true);
167
199
  setThinkingMessage('Enxame em ação...');
168
-
169
200
  try {
170
201
  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
- }
202
+ if (parts[1] === 'seq') response = await orchestrator.runSequential(parts[2].split(','), parts.slice(3).join(' '));
203
+ if (parts[1] === 'run') response = await orchestrator.runHierarchical(parts[2], parts[3].split(','), parts.slice(4).join(' '));
181
204
  setMessages(prev => [...prev, { role: 'user', content: rawInput }, { role: 'assistant', content: response }]);
182
205
  } catch (err) {
183
206
  setMessages(prev => [...prev, { role: 'system', content: `Erro no enxame: ${err.message}` }]);
184
207
  } finally {
185
208
  setIsThinking(false);
186
- setThinkingMessage('bimmo pensando...');
187
209
  }
188
210
  return;
189
211
  }
190
212
 
191
213
  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 }]);
214
+ setMessages(prev => [...prev, { role: 'assistant', content: `**Comandos:** /chat, /plan, /edit, /switch, /model, /use, /swarm, /clear, /exit, @arquivo` }]);
205
215
  return;
206
216
  }
207
217
 
208
- // ENVIO PARA IA
218
+ // Processamento de arquivos @
209
219
  setIsThinking(true);
210
-
211
220
  let processedInput = rawInput;
212
221
  const fileMatches = rawInput.match(/@[\w\.\-\/]+/g);
213
222
  if (fileMatches) {
214
223
  for (const match of fileMatches) {
215
224
  const filePath = match.slice(1);
216
225
  try {
217
- if (fs.existsSync(filePath) && fs.statSync(filePath).isFile()) {
226
+ if (fs.existsSync(filePath)) {
218
227
  const content = fs.readFileSync(filePath, 'utf-8');
219
228
  processedInput = processedInput.replace(match, `\n\n[Arquivo: ${filePath}]\n\`\`\`\n${content}\n\`\`\`\n`);
220
229
  }
@@ -230,12 +239,8 @@ const BimmoApp = ({ initialConfig }) => {
230
239
  let finalMessages = newMessages;
231
240
  if (activePersona && config.agents[activePersona]) {
232
241
  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
- ];
242
+ finalMessages = [{ role: 'system', content: `Sua tarefa: ${agent.role}\n\n${getProjectContext()}` }, ...newMessages.filter(m => m.role !== 'system')];
237
243
  }
238
-
239
244
  const response = await provider.sendMessage(finalMessages);
240
245
  setMessages(prev => [...prev, { role: 'assistant', content: response }]);
241
246
  } catch (err) {
@@ -247,18 +252,12 @@ const BimmoApp = ({ initialConfig }) => {
247
252
 
248
253
  useInput((input, key) => {
249
254
  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
- }
255
+ if (isThinking) setIsThinking(false);
256
+ else {
257
+ if (exitCounter === 0) { setExitCounter(1); setTimeout(() => setExitCounter(0), 2000); }
258
+ else exit();
259
259
  }
260
260
  }
261
-
262
261
  if (key.tab && filePreview.length > 0) {
263
262
  const words = input.split(' ');
264
263
  words[words.length - 1] = `@${filePreview[0].rel}${filePreview[0].isDir ? '/' : ''}`;
@@ -268,41 +267,9 @@ const BimmoApp = ({ initialConfig }) => {
268
267
 
269
268
  return (
270
269
  <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 */}
270
+ <Header config={config} />
271
+ <MessageList messages={messages} />
272
+
306
273
  {isThinking && (
307
274
  <Box marginBottom={1}>
308
275
  <Text color={lavender}>
@@ -311,40 +278,17 @@ const BimmoApp = ({ initialConfig }) => {
311
278
  </Box>
312
279
  )}
313
280
 
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
- )}
281
+ {filePreview.length > 0 && <Autocomplete suggestions={filePreview} />}
325
282
 
326
- {/* PROMPT */}
327
283
  <Box borderStyle="round" borderColor={isThinking ? gray : lavender} paddingX={1}>
328
284
  <Text bold color={mode === 'edit' ? red : mode === 'plan' ? cyan : lavender}>
329
285
  {activePersona ? `[${activePersona.toUpperCase()}] ` : ''}
330
286
  [{mode.toUpperCase()}] ›{' '}
331
287
  </Text>
332
- <TextInput
333
- value={input}
334
- onChange={setInput}
335
- onSubmit={handleSubmit}
336
- placeholder="Como posso ajudar hoje?"
337
- />
288
+ <TextInput value={input} onChange={setInput} onSubmit={handleSubmit} placeholder="Como posso ajudar hoje?" />
338
289
  </Box>
339
290
 
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>
291
+ <Footer exitCounter={exitCounter} />
348
292
  </Box>
349
293
  );
350
294
  };