bimmo-cli 3.2.1 → 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 CHANGED
@@ -4,16 +4,33 @@
4
4
  process.removeAllListeners('warning');
5
5
 
6
6
  import { program } from 'commander';
7
- import { configure } from '../src/config.js';
8
7
  import fs from 'fs';
9
8
  import path from 'path';
10
- import { fileURLToPath } from 'url';
11
-
12
- // Importa a interface compilada em dist/
13
- import { startInteractive } from '../dist/interface.js';
9
+ import { fileURLToPath, pathToFileURL } from 'url';
14
10
 
15
11
  const __filename = fileURLToPath(import.meta.url);
16
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;
17
34
  const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, '../package.json'), 'utf-8'));
18
35
 
19
36
  program
package/package.json CHANGED
@@ -1,25 +1,13 @@
1
1
  {
2
2
  "name": "bimmo-cli",
3
- "version": "3.2.1",
4
- "description": "🌿 Plataforma de IA universal profissional com interface React (Ink). Agentes, Swarms, Autocomplete real-time e Contexto Inteligente.",
5
- "scripts": {
6
- "build": "esbuild src/interface.jsx --bundle --platform=node --format=esm --outfile=dist/interface.js --packages=external"
7
- },
3
+ "version": "4.0.1",
4
+ "description": "🌿 Plataforma de IA universal profissional com interface React (Ink) pura.",
8
5
  "bin": {
9
6
  "bimmo": "bin/bimmo"
10
7
  },
11
8
  "type": "module",
12
9
  "keywords": [
13
- "ai",
14
- "cli",
15
- "ink",
16
- "react",
17
- "openai",
18
- "anthropic",
19
- "gemini",
20
- "agent",
21
- "swarm",
22
- "terminal"
10
+ "ai", "cli", "ink", "react", "openai", "anthropic", "gemini", "agent", "swarm", "terminal"
23
11
  ],
24
12
  "author": "Judah",
25
13
  "license": "MIT",
@@ -30,13 +18,21 @@
30
18
  "files": [
31
19
  "bin/",
32
20
  "src/",
33
- "dist/",
34
21
  "README.md",
35
22
  ".bimmo-context.md"
36
23
  ],
37
24
  "publishConfig": {
38
25
  "access": "public"
39
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
+ },
40
36
  "dependencies": {
41
37
  "@anthropic-ai/sdk": "^0.36.3",
42
38
  "@google/generative-ai": "^0.21.0",
@@ -52,7 +48,6 @@
52
48
  "ink-spinner": "^5.0.0",
53
49
  "ink-text-input": "^6.0.0",
54
50
  "inquirer": "^9.3.8",
55
- "jiti": "^2.6.1",
56
51
  "marked": "^14.0.0",
57
52
  "marked-terminal": "^7.0.0",
58
53
  "mime-types": "^2.1.35",
@@ -60,11 +55,6 @@
60
55
  "openai": "^4.82.0",
61
56
  "ora": "^8.1.1",
62
57
  "react": "^18.2.0",
63
- "tsx": "^4.19.2",
64
58
  "zod": "^3.24.1"
65
- },
66
- "devDependencies": {
67
- "esbuild": "^0.27.4",
68
- "tsx": "^4.21.0"
69
59
  }
70
60
  }
@@ -1,7 +1,8 @@
1
- import React, { useState, useEffect, useMemo, useRef } from 'react';
2
- import { render, Box, Text, useInput, useApp } from 'ink';
1
+ import React, { useState, useEffect, useMemo } from 'react';
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,17 +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
- // --- 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';
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
+ };
26
30
 
27
31
  marked.use(new TerminalRenderer({
28
- heading: chalk.hex(lavender).bold,
29
- code: chalk.hex(green),
32
+ heading: chalk.hex(THEME.lavender).bold,
33
+ code: chalk.hex(THEME.green),
30
34
  strong: chalk.bold,
31
35
  em: chalk.italic,
32
36
  html: () => '',
@@ -34,81 +38,94 @@ marked.use(new TerminalRenderer({
34
38
 
35
39
  const __filename = fileURLToPath(import.meta.url);
36
40
  const __dirname = path.dirname(__filename);
37
- const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, '../package.json'), 'utf-8'));
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'));
38
46
  const version = pkg.version;
39
47
 
48
+ const h = React.createElement;
49
+
40
50
  // --- COMPONENTES ---
41
51
 
42
52
  const Header = ({ config }) => (
43
- <Box flexDirection="column" marginBottom={1}>
44
- <Text color={lavender}>{figlet.textSync('bimmo')}</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>
53
+ h(Box, { flexDirection: 'column', marginBottom: 1 },
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}`),
57
+ h(Box, null,
58
+ h(Text, { color: THEME.gray }, `${config.activeProfile || 'Default'} `),
59
+ h(Text, { color: THEME.lavender }, '•'),
60
+ h(Text, { color: THEME.cyan }, ` ${config.model}`)
61
+ )
62
+ )
63
+ )
54
64
  );
55
65
 
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
- );
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)
81
+ )
82
+ )
83
+ );
84
+ };
77
85
 
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>
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):'),
89
+ suggestions.map((f, i) => (
90
+ h(Text, { key: i, color: i === 0 ? THEME.green : THEME.gray },
91
+ `${f.isDir ? '📁' : '📄'} ${f.rel}${f.isDir ? '/' : ''}`
92
+ )
93
+ ))
94
+ )
87
95
  );
88
96
 
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
+ 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 ')
110
+ )
111
+ )
97
112
  );
98
113
 
114
+ // --- APP PRINCIPAL ---
115
+
99
116
  const BimmoApp = ({ initialConfig }) => {
100
117
  const { exit } = useApp();
101
118
  const [config, setConfig] = useState(initialConfig);
102
119
  const [mode, setMode] = useState('chat');
103
120
  const [activePersona, setActivePersona] = useState(null);
104
121
  const [messages, setMessages] = useState([]);
122
+ const [staticMessages, setStaticMessages] = useState([]); // Para mensagens antigas
105
123
  const [input, setInput] = useState('');
106
124
  const [isThinking, setIsThinking] = useState(false);
107
125
  const [thinkingMessage, setThinkingMessage] = useState('bimmo pensando...');
108
126
  const [exitCounter, setExitCounter] = useState(0);
109
127
  const [provider, setProvider] = useState(() => createProvider(initialConfig));
110
128
 
111
- // Inicializa contexto
112
129
  useEffect(() => {
113
130
  const ctx = getProjectContext();
114
131
  setMessages([
@@ -149,6 +166,7 @@ const BimmoApp = ({ initialConfig }) => {
149
166
  // Comandos de Sistema
150
167
  if (cmd === '/exit') exit();
151
168
  if (cmd === '/clear') {
169
+ setStaticMessages([]);
152
170
  setMessages([{ role: 'system', content: getProjectContext() }, { role: 'assistant', content: 'Chat limpo.' }]);
153
171
  return;
154
172
  }
@@ -193,29 +211,12 @@ const BimmoApp = ({ initialConfig }) => {
193
211
  return;
194
212
  }
195
213
 
196
- if (cmd === '/swarm') {
197
- const orchestrator = new SwarmOrchestrator(config);
198
- setIsThinking(true);
199
- setThinkingMessage('Enxame em ação...');
200
- try {
201
- let response;
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(' '));
204
- setMessages(prev => [...prev, { role: 'user', content: rawInput }, { role: 'assistant', content: response }]);
205
- } catch (err) {
206
- setMessages(prev => [...prev, { role: 'system', content: `Erro no enxame: ${err.message}` }]);
207
- } finally {
208
- setIsThinking(false);
209
- }
210
- return;
211
- }
212
-
213
214
  if (cmd === '/help') {
214
- setMessages(prev => [...prev, { role: 'assistant', content: `**Comandos:** /chat, /plan, /edit, /switch, /model, /use, /swarm, /clear, /exit, @arquivo` }]);
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` }]);
215
216
  return;
216
217
  }
217
218
 
218
- // Processamento de arquivos @
219
+ // Processamento de arquivos
219
220
  setIsThinking(true);
220
221
  let processedInput = rawInput;
221
222
  const fileMatches = rawInput.match(/@[\w\.\-\/]+/g);
@@ -232,15 +233,22 @@ const BimmoApp = ({ initialConfig }) => {
232
233
  }
233
234
 
234
235
  const userMsg = { role: 'user', content: processedInput, displayContent: rawInput };
235
- const newMessages = [...messages, userMsg];
236
- setMessages(newMessages);
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
+ }
237
244
 
238
245
  try {
239
- let finalMessages = newMessages;
246
+ let finalMessages = [...staticMessages, ...messages, userMsg];
240
247
  if (activePersona && config.agents[activePersona]) {
241
248
  const agent = config.agents[activePersona];
242
- finalMessages = [{ role: 'system', content: `Sua tarefa: ${agent.role}\n\n${getProjectContext()}` }, ...newMessages.filter(m => m.role !== 'system')];
249
+ finalMessages = [{ role: 'system', content: `Sua tarefa: ${agent.role}\n\n${getProjectContext()}` }, ...finalMessages.filter(m => m.role !== 'system')];
243
250
  }
251
+
244
252
  const response = await provider.sendMessage(finalMessages);
245
253
  setMessages(prev => [...prev, { role: 'assistant', content: response }]);
246
254
  } catch (err) {
@@ -254,8 +262,12 @@ const BimmoApp = ({ initialConfig }) => {
254
262
  if (key.ctrl && input === 'c') {
255
263
  if (isThinking) setIsThinking(false);
256
264
  else {
257
- if (exitCounter === 0) { setExitCounter(1); setTimeout(() => setExitCounter(0), 2000); }
258
- else exit();
265
+ if (exitCounter === 0) {
266
+ setExitCounter(1);
267
+ setTimeout(() => setExitCounter(0), 2000);
268
+ } else {
269
+ exit();
270
+ }
259
271
  }
260
272
  }
261
273
  if (key.tab && filePreview.length > 0) {
@@ -266,39 +278,49 @@ const BimmoApp = ({ initialConfig }) => {
266
278
  });
267
279
 
268
280
  return (
269
- <Box flexDirection="column" paddingX={1} minHeight={10}>
270
- <Header config={config} />
271
- <MessageList messages={messages} />
281
+ h(Box, { flexDirection: 'column', paddingX: 2, paddingY: 1, minHeight: 15 },
282
+ h(Header, { config }),
272
283
 
273
- {isThinking && (
274
- <Box marginBottom={1}>
275
- <Text color={lavender}>
276
- <Spinner type="dots" /> <Text italic>{thinkingMessage}</Text>
277
- </Text>
278
- </Box>
279
- )}
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
+ ),
280
288
 
281
- {filePreview.length > 0 && <Autocomplete suggestions={filePreview} />}
289
+ isThinking && h(Box, { marginBottom: 1 },
290
+ h(Text, { color: THEME.lavender },
291
+ h(Spinner, { type: 'dots' }),
292
+ h(Text, { italic: true }, ` ${thinkingMessage}`)
293
+ )
294
+ ),
282
295
 
283
- <Box borderStyle="round" borderColor={isThinking ? gray : lavender} paddingX={1}>
284
- <Text bold color={mode === 'edit' ? red : mode === 'plan' ? cyan : lavender}>
285
- {activePersona ? `[${activePersona.toUpperCase()}] ` : ''}
286
- [{mode.toUpperCase()}] ›{' '}
287
- </Text>
288
- <TextInput value={input} onChange={setInput} onSubmit={handleSubmit} placeholder="Como posso ajudar hoje?" />
289
- </Box>
290
-
291
- <Footer exitCounter={exitCounter} />
292
- </Box>
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()}] ` : ''}› `
301
+ ),
302
+ h(TextInput, {
303
+ value: input,
304
+ onChange: setInput,
305
+ onSubmit: handleSubmit,
306
+ placeholder: 'Diga algo ou use / para comandos...'
307
+ })
308
+ ),
309
+
310
+ h(FooterStatus, { mode, activePersona, exitCounter })
311
+ )
293
312
  );
294
313
  };
295
314
 
296
315
  export async function startInteractive() {
297
316
  const config = getConfig();
298
317
  if (!config.provider || !config.apiKey) {
299
- console.log(chalk.yellow('Provedor não configurado. Execute "bimmo config" primeiro.'));
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'));
300
320
  process.exit(0);
301
321
  }
322
+
323
+ // Limpa tela e inicia
302
324
  process.stdout.write('\x1Bc');
303
- render(<BimmoApp initialConfig={config} />);
325
+ render(h(BimmoApp, { initialConfig: config }));
304
326
  }
package/dist/interface.js DELETED
@@ -1,852 +0,0 @@
1
- // src/interface.jsx
2
- import React, { useState, useEffect, useMemo, useRef } from "react";
3
- import { render, Box, Text, useInput, useApp } from "ink";
4
- import TextInput from "ink-text-input";
5
- import Spinner from "ink-spinner";
6
- import chalk4 from "chalk";
7
- import figlet from "figlet";
8
- import { marked } from "marked";
9
- import TerminalRenderer from "marked-terminal";
10
- import fs3 from "fs";
11
- import path3 from "path";
12
- import { fileURLToPath } from "url";
13
-
14
- // src/config.js
15
- import Conf from "conf";
16
- import inquirer from "inquirer";
17
- import chalk from "chalk";
18
- var config = new Conf({ projectName: "bimmo-cli" });
19
- function getConfig() {
20
- return config.store;
21
- }
22
- function updateActiveModel(newModel) {
23
- config.set("model", newModel);
24
- const active = config.get("activeProfile");
25
- if (active) {
26
- const profiles = config.get("profiles");
27
- profiles[active].model = newModel;
28
- config.set("profiles", profiles);
29
- }
30
- }
31
- function switchProfile(name) {
32
- const profiles = config.get("profiles") || {};
33
- if (profiles[name]) {
34
- const p = profiles[name];
35
- config.set("provider", p.provider);
36
- config.set("apiKey", p.apiKey);
37
- config.set("model", p.model);
38
- config.set("baseURL", p.baseURL);
39
- config.set("activeProfile", name);
40
- return true;
41
- }
42
- return false;
43
- }
44
-
45
- // src/providers/openai.js
46
- import OpenAI from "openai";
47
-
48
- // src/providers/base.js
49
- var BaseProvider = class {
50
- constructor(config3) {
51
- this.config = config3;
52
- }
53
- async sendMessage(messages, options = {}) {
54
- throw new Error("M\xE9todo sendMessage deve ser implementado");
55
- }
56
- };
57
-
58
- // src/agent.js
59
- import { tavily } from "@tavily/core";
60
- import fs from "fs";
61
- import path from "path";
62
- import { execSync } from "child_process";
63
- import * as diff from "diff";
64
- import chalk2 from "chalk";
65
- import inquirer2 from "inquirer";
66
- var config2 = getConfig();
67
- var tvly = config2.tavilyKey ? tavily({ apiKey: config2.tavilyKey }) : null;
68
- var editState = {
69
- autoAccept: false
70
- };
71
- var tools = [
72
- {
73
- name: "search_internet",
74
- description: "Pesquisa informa\xE7\xF5es atualizadas na internet sobre qualquer assunto.",
75
- parameters: {
76
- type: "object",
77
- properties: {
78
- query: { type: "string", description: "O termo de busca" }
79
- },
80
- required: ["query"]
81
- },
82
- execute: async ({ query }) => {
83
- if (!tvly) return "Erro: Chave de API da Tavily n\xE3o configurada. Use /config para configurar.";
84
- console.log(chalk2.blue(`
85
- \u{1F310} Pesquisando na web: ${chalk2.bold(query)}...`));
86
- const searchResponse = await tvly.search(query, {
87
- searchDepth: "advanced",
88
- maxResults: 5
89
- });
90
- return JSON.stringify(searchResponse.results.map((r) => ({
91
- title: r.title,
92
- url: r.url,
93
- content: r.content
94
- })));
95
- }
96
- },
97
- {
98
- name: "read_file",
99
- description: "L\xEA o conte\xFAdo de um arquivo no sistema.",
100
- parameters: {
101
- type: "object",
102
- properties: {
103
- path: { type: "string", description: "Caminho do arquivo" }
104
- },
105
- required: ["path"]
106
- },
107
- execute: async ({ path: filePath }) => {
108
- try {
109
- console.log(chalk2.blue(`
110
- \u{1F4D6} Lendo arquivo: ${chalk2.bold(filePath)}...`));
111
- return fs.readFileSync(filePath, "utf-8");
112
- } catch (err) {
113
- return `Erro ao ler arquivo: ${err.message}`;
114
- }
115
- }
116
- },
117
- {
118
- name: "write_file",
119
- description: "Cria ou sobrescreve um arquivo com um conte\xFAdo espec\xEDfico.",
120
- parameters: {
121
- type: "object",
122
- properties: {
123
- path: { type: "string", description: "Caminho de destino" },
124
- content: { type: "string", description: "Conte\xFAdo do arquivo" }
125
- },
126
- required: ["path", "content"]
127
- },
128
- execute: async ({ path: filePath, content }) => {
129
- try {
130
- const absolutePath = path.resolve(filePath);
131
- const oldContent = fs.existsSync(absolutePath) ? fs.readFileSync(absolutePath, "utf-8") : "";
132
- const differences = diff.diffLines(oldContent, content);
133
- console.log(`
134
- ${chalk2.cyan("\u{1F4DD} Altera\xE7\xF5es propostas em:")} ${chalk2.bold(filePath)}`);
135
- console.log(chalk2.gray("\u2500".repeat(50)));
136
- let hasChanges = false;
137
- differences.forEach((part) => {
138
- if (part.added || part.removed) hasChanges = true;
139
- const color = part.added ? chalk2.green : part.removed ? chalk2.red : chalk2.gray;
140
- const prefix = part.added ? "+" : part.removed ? "-" : " ";
141
- if (part.added || part.removed) {
142
- const lines = part.value.split("\n");
143
- lines.forEach((line) => {
144
- if (line || part.value.endsWith("\n")) {
145
- process.stdout.write(color(`${prefix} ${line}
146
- `));
147
- }
148
- });
149
- } else {
150
- const lines = part.value.split("\n").filter((l) => l.trim() !== "");
151
- if (lines.length > 4) {
152
- process.stdout.write(color(` ${lines[0]}
153
- ...
154
- ${lines[lines.length - 1]}
155
- `));
156
- } else if (lines.length > 0) {
157
- lines.forEach((line) => process.stdout.write(color(` ${line}
158
- `)));
159
- }
160
- }
161
- });
162
- console.log(chalk2.gray("\u2500".repeat(50)));
163
- if (!hasChanges) {
164
- return "Nenhuma mudan\xE7a detectada no arquivo.";
165
- }
166
- if (!editState.autoAccept) {
167
- const { approve } = await inquirer2.prompt([{
168
- type: "list",
169
- name: "approve",
170
- message: "Deseja aplicar estas altera\xE7\xF5es?",
171
- choices: [
172
- { name: "\u2705 Sim", value: "yes" },
173
- { name: "\u274C N\xE3o", value: "no" },
174
- { name: "\u26A1 Sim para tudo (Auto-Accept)", value: "all" }
175
- ]
176
- }]);
177
- if (approve === "no") return "Altera\xE7\xE3o rejeitada pelo usu\xE1rio.";
178
- if (approve === "all") editState.autoAccept = true;
179
- }
180
- const dir = path.dirname(absolutePath);
181
- if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
182
- fs.writeFileSync(absolutePath, content);
183
- return `Arquivo ${filePath} atualizado com sucesso.`;
184
- } catch (err) {
185
- return `Erro ao escrever arquivo: ${err.message}`;
186
- }
187
- }
188
- },
189
- {
190
- name: "run_command",
191
- description: "Executa um comando shell no sistema.",
192
- parameters: {
193
- type: "object",
194
- properties: {
195
- command: { type: "string", description: "Comando shell a ser executado" }
196
- },
197
- required: ["command"]
198
- },
199
- execute: async ({ command }) => {
200
- try {
201
- console.log(chalk2.yellow(`
202
- \u26A1 Comando proposto: ${chalk2.bold(command)}`));
203
- if (!editState.autoAccept) {
204
- const { approve } = await inquirer2.prompt([{
205
- type: "list",
206
- name: "approve",
207
- message: "Executar este comando?",
208
- choices: [
209
- { name: "\u2705 Sim", value: "yes" },
210
- { name: "\u274C N\xE3o", value: "no" },
211
- { name: "\u26A1 Sim para tudo (Auto-Accept)", value: "all" }
212
- ]
213
- }]);
214
- if (approve === "no") return "Comando rejeitado pelo usu\xE1rio.";
215
- if (approve === "all") editState.autoAccept = true;
216
- }
217
- const output = execSync(command, { encoding: "utf-8", timeout: 6e4 });
218
- return output || "Comando executado com sucesso (sem retorno).";
219
- } catch (err) {
220
- return `Erro ao executar comando: ${err.stderr || err.message}`;
221
- }
222
- }
223
- }
224
- ];
225
-
226
- // src/providers/openai.js
227
- var OpenAIProvider = class extends BaseProvider {
228
- constructor(config3) {
229
- super(config3);
230
- const extraHeaders = {};
231
- if (this.config.baseURL?.includes("openrouter.ai")) {
232
- extraHeaders["HTTP-Referer"] = "https://github.com/JudahAragao/bimmo-cli";
233
- extraHeaders["X-Title"] = "bimmo-cli";
234
- }
235
- this.client = new OpenAI({
236
- apiKey: this.config.apiKey,
237
- baseURL: this.config.baseURL,
238
- defaultHeaders: extraHeaders
239
- });
240
- }
241
- formatMessages(messages) {
242
- return messages.map((msg) => {
243
- if (typeof msg.content === "string" || msg.content === null) return msg;
244
- if (Array.isArray(msg.content)) {
245
- const content = msg.content.map((part) => {
246
- if (part.type === "text") return { type: "text", text: part.text };
247
- if (part.type === "image") return {
248
- type: "image_url",
249
- image_url: { url: `data:${part.mimeType};base64,${part.data}` }
250
- };
251
- return part;
252
- });
253
- return { ...msg, content };
254
- }
255
- return msg;
256
- });
257
- }
258
- async sendMessage(messages, options = {}) {
259
- const formattedMessages = this.formatMessages(messages);
260
- const openAiTools = tools.map((t) => ({
261
- type: "function",
262
- function: {
263
- name: t.name,
264
- description: t.description,
265
- parameters: t.parameters
266
- }
267
- }));
268
- const requestOptions = {
269
- model: this.config.model,
270
- messages: formattedMessages,
271
- temperature: 0.7
272
- };
273
- if (openAiTools.length > 0) {
274
- requestOptions.tools = openAiTools;
275
- requestOptions.tool_choice = "auto";
276
- }
277
- const response = await this.client.chat.completions.create(requestOptions, { signal: options.signal });
278
- const message = response.choices[0].message;
279
- if (message.tool_calls) {
280
- const toolResults = [];
281
- for (const toolCall of message.tool_calls) {
282
- if (options.signal?.aborted) throw new Error("Abortado pelo usu\xE1rio");
283
- const tool = tools.find((t) => t.name === toolCall.function.name);
284
- if (tool) {
285
- const args = JSON.parse(toolCall.function.arguments);
286
- const result = await tool.execute(args);
287
- toolResults.push({
288
- role: "tool",
289
- tool_call_id: toolCall.id,
290
- content: String(result)
291
- });
292
- }
293
- }
294
- const nextMessages = [...formattedMessages, message, ...toolResults];
295
- return this.sendMessage(nextMessages, options);
296
- }
297
- return message.content;
298
- }
299
- };
300
-
301
- // src/providers/anthropic.js
302
- import Anthropic from "@anthropic-ai/sdk";
303
- var AnthropicProvider = class extends BaseProvider {
304
- constructor(config3) {
305
- super(config3);
306
- this.client = new Anthropic({
307
- apiKey: this.config.apiKey
308
- });
309
- }
310
- formatContent(content) {
311
- if (typeof content === "string") return content;
312
- return content.map((part) => {
313
- if (part.type === "text") return { type: "text", text: part.text };
314
- if (part.type === "image") return {
315
- type: "image",
316
- source: { type: "base64", media_type: part.mimeType, data: part.data }
317
- };
318
- return part;
319
- });
320
- }
321
- async sendMessage(messages, options = {}) {
322
- const systemMessage = messages.find((m) => m.role === "system");
323
- const userMessages = messages.filter((m) => m.role !== "system").map((m) => ({
324
- role: m.role,
325
- content: this.formatContent(m.content)
326
- }));
327
- const anthropicTools = tools.map((t) => ({
328
- name: t.name,
329
- description: t.description,
330
- input_schema: t.parameters
331
- }));
332
- const response = await this.client.messages.create({
333
- model: this.config.model,
334
- max_tokens: 4096,
335
- system: systemMessage ? systemMessage.content : void 0,
336
- messages: userMessages,
337
- tools: anthropicTools,
338
- temperature: 0.7
339
- }, { signal: options.signal });
340
- if (response.stop_reason === "tool_use") {
341
- const toolUse = response.content.find((p) => p.type === "tool_use");
342
- const tool = tools.find((t) => t.name === toolUse.name);
343
- if (tool) {
344
- if (options.signal?.aborted) throw new Error("Abortado pelo usu\xE1rio");
345
- const result = await tool.execute(toolUse.input);
346
- const nextMessages = [
347
- ...messages,
348
- { role: "assistant", content: response.content },
349
- {
350
- role: "user",
351
- content: [{
352
- type: "tool_result",
353
- tool_use_id: toolUse.id,
354
- content: String(result)
355
- }]
356
- }
357
- ];
358
- return this.sendMessage(nextMessages, options);
359
- }
360
- }
361
- return response.content[0].text;
362
- }
363
- };
364
-
365
- // src/providers/grok.js
366
- import OpenAI2 from "openai";
367
- var GrokProvider = class extends BaseProvider {
368
- constructor(config3) {
369
- super(config3);
370
- this.client = new OpenAI2({
371
- apiKey: this.config.apiKey,
372
- baseURL: this.config.baseURL || "https://api.x.ai/v1"
373
- });
374
- }
375
- formatMessages(messages) {
376
- return messages.map((msg) => {
377
- if (typeof msg.content === "string" || msg.content === null) {
378
- return msg;
379
- }
380
- if (Array.isArray(msg.content)) {
381
- const content = msg.content.map((part) => {
382
- if (part.type === "text") return { type: "text", text: part.text };
383
- if (part.type === "image") return {
384
- type: "image_url",
385
- image_url: { url: `data:${part.mimeType};base64,${part.data}` }
386
- };
387
- return part;
388
- });
389
- return { ...msg, content };
390
- }
391
- return msg;
392
- });
393
- }
394
- async sendMessage(messages, options = {}) {
395
- const formattedMessages = this.formatMessages(messages);
396
- const response = await this.client.chat.completions.create({
397
- model: this.config.model,
398
- messages: formattedMessages,
399
- temperature: 0.7
400
- }, { signal: options.signal });
401
- return response.choices[0].message.content;
402
- }
403
- };
404
-
405
- // src/providers/gemini.js
406
- import { GoogleGenerativeAI } from "@google/generative-ai";
407
- var GeminiProvider = class extends BaseProvider {
408
- constructor(config3) {
409
- super(config3);
410
- this.genAI = new GoogleGenerativeAI(this.config.apiKey);
411
- }
412
- formatContent(content) {
413
- if (typeof content === "string") return [{ text: content }];
414
- return content.map((part) => {
415
- if (part.type === "text") return { text: part.text };
416
- if (part.type === "image") return {
417
- inlineData: { mimeType: part.mimeType, data: part.data }
418
- };
419
- return part;
420
- });
421
- }
422
- async sendMessage(messages, options = {}) {
423
- const systemPrompt = messages.find((m) => m.role === "system")?.content;
424
- const history = messages.filter((m) => m.role !== "system").slice(0, -1).map((msg) => ({
425
- role: msg.role === "user" ? "user" : "model",
426
- parts: this.formatContent(msg.content)
427
- }));
428
- const lastMessageContent = this.formatContent(messages[messages.length - 1].content);
429
- const geminiTools = tools.map((t) => ({
430
- functionDeclarations: [{
431
- name: t.name,
432
- description: t.description,
433
- parameters: t.parameters
434
- }]
435
- }));
436
- const model = this.genAI.getGenerativeModel({
437
- model: this.config.model,
438
- systemInstruction: systemPrompt,
439
- tools: geminiTools
440
- });
441
- const chat = model.startChat({
442
- history,
443
- generationConfig: { temperature: 0.7, maxOutputTokens: 8192 }
444
- });
445
- const result = await chat.sendMessage(lastMessageContent);
446
- const response = await result.response;
447
- const call = response.candidates[0].content.parts.find((p) => p.functionCall);
448
- if (call) {
449
- if (options.signal?.aborted) throw new Error("Abortado pelo usu\xE1rio");
450
- const tool = tools.find((t) => t.name === call.functionCall.name);
451
- if (tool) {
452
- const toolResult = await tool.execute(call.functionCall.args);
453
- const resultResponse = await chat.sendMessage([{
454
- functionResponse: {
455
- name: call.functionCall.name,
456
- response: { content: toolResult }
457
- }
458
- }]);
459
- return resultResponse.response.text();
460
- }
461
- }
462
- return response.text();
463
- }
464
- };
465
-
466
- // src/providers/ollama.js
467
- import ollama from "ollama";
468
- var OllamaProvider = class extends BaseProvider {
469
- constructor(config3) {
470
- super(config3);
471
- this.client = ollama;
472
- }
473
- formatMessages(messages) {
474
- return messages.map((msg) => {
475
- if (typeof msg.content === "string") return msg;
476
- const images = msg.content.filter((part) => part.type === "image").map((part) => part.data);
477
- const text = msg.content.filter((part) => part.type === "text").map((part) => part.text).join(" ");
478
- return {
479
- role: msg.role,
480
- content: text,
481
- images: images.length > 0 ? images : void 0
482
- };
483
- });
484
- }
485
- async sendMessage(messages, options = {}) {
486
- const formattedMessages = this.formatMessages(messages);
487
- const response = await this.client.chat({
488
- model: this.config.model,
489
- messages: formattedMessages,
490
- stream: false,
491
- options: {
492
- temperature: 0.7
493
- }
494
- }, { signal: options.signal });
495
- const text = response.message?.content;
496
- if (!text) {
497
- throw new Error("Resposta inv\xE1lida do Ollama");
498
- }
499
- return text;
500
- }
501
- };
502
-
503
- // src/providers/factory.js
504
- function createProvider(config3) {
505
- switch (config3.provider) {
506
- case "openai":
507
- return new OpenAIProvider(config3);
508
- case "anthropic":
509
- return new AnthropicProvider(config3);
510
- case "grok":
511
- return new GrokProvider(config3);
512
- case "gemini":
513
- return new GeminiProvider(config3);
514
- case "ollama":
515
- return new OllamaProvider(config3);
516
- case "openrouter":
517
- return new OpenAIProvider(config3);
518
- case "deepseek":
519
- return new OpenAIProvider(config3);
520
- case "zai":
521
- return new OpenAIProvider(config3);
522
- default:
523
- throw new Error(`Provider ${config3.provider} n\xE3o suportado ainda.`);
524
- }
525
- }
526
-
527
- // src/project-context.js
528
- import fs2 from "fs";
529
- import path2 from "path";
530
- import { execSync as execSync2 } from "child_process";
531
- function getProjectContext() {
532
- const cwd = process.cwd();
533
- let context = "=== CONTEXTO DO PROJETO ===\n";
534
- const bimmoRcPath = path2.join(cwd, ".bimmorc.json");
535
- if (fs2.existsSync(bimmoRcPath)) {
536
- try {
537
- const rc = JSON.parse(fs2.readFileSync(bimmoRcPath, "utf-8"));
538
- context += `Regras de Projeto (.bimmorc):
539
- ${JSON.stringify(rc, null, 2)}
540
-
541
- `;
542
- } catch (e) {
543
- }
544
- }
545
- const instructionFiles = ["CLAUDE.md", "INSTRUCTIONS.md", ".bimmo-context.md", "CONTRIBUTING.md"];
546
- for (const file of instructionFiles) {
547
- const p = path2.join(cwd, file);
548
- if (fs2.existsSync(p)) {
549
- context += `Instru\xE7\xF5es de ${file}:
550
- ${fs2.readFileSync(p, "utf-8")}
551
-
552
- `;
553
- }
554
- }
555
- try {
556
- const tree = execSync2('find . -maxdepth 2 -not -path "*/.*" -not -path "./node_modules*"', { encoding: "utf-8" });
557
- context += `Estrutura de Arquivos (Resumo):
558
- ${tree}
559
- `;
560
- } catch (e) {
561
- context += "Estrutura de arquivos indispon\xEDvel.\n";
562
- }
563
- return context;
564
- }
565
-
566
- // src/orchestrator.js
567
- import chalk3 from "chalk";
568
- import ora from "ora";
569
- var SwarmOrchestrator = class {
570
- constructor(config3) {
571
- this.config = config3;
572
- this.agents = config3.agents || {};
573
- this.profiles = config3.profiles || {};
574
- }
575
- /**
576
- * Executa uma tarefa sequencialmente através de uma lista de agentes.
577
- * O output de um agente vira o contexto do próximo.
578
- */
579
- async runSequential(agentNames, goal, options = {}) {
580
- let currentContext = goal;
581
- const results = [];
582
- console.log(chalk3.cyan(`
583
- \u{1F680} Iniciando Enxame Sequencial: ${agentNames.join(" \u2192 ")}
584
- `));
585
- for (const name of agentNames) {
586
- const agent = this.agents[name];
587
- if (!agent) throw new Error(`Agente ${name} n\xE3o encontrado.`);
588
- const profile = this.profiles[agent.profile];
589
- if (!profile) throw new Error(`Perfil ${agent.profile} do agente ${name} n\xE3o encontrado.`);
590
- const agentConfig = {
591
- ...profile,
592
- model: agent.modelOverride || profile.model
593
- };
594
- const provider = createProvider(agentConfig);
595
- const spinner = ora({
596
- text: chalk3.magenta(`Agente [${name}] trabalhando...`),
597
- color: agent.mode === "edit" ? "red" : "magenta"
598
- }).start();
599
- const messages = [
600
- { role: "system", content: `Voc\xEA \xE9 o agente ${name}. Sua tarefa espec\xEDfica \xE9: ${agent.role}
601
-
602
- MODO ATUAL: ${agent.mode.toUpperCase()}
603
- ${getProjectContext()}` },
604
- { role: "user", content: `CONTEXTO ATUAL:
605
- ${currentContext}
606
-
607
- OBJETIVO DO ENXAME:
608
- ${goal}
609
-
610
- Por favor, execute sua parte e retorne o resultado final processado.` }
611
- ];
612
- try {
613
- const response = await provider.sendMessage(messages, { signal: options.signal });
614
- spinner.succeed(chalk3.green(`Agente [${name}] conclu\xEDdo.`));
615
- currentContext = response;
616
- results.push({ agent: name, output: response });
617
- } catch (err) {
618
- spinner.fail(chalk3.red(`Agente [${name}] falhou: ${err.message}`));
619
- throw err;
620
- }
621
- }
622
- return currentContext;
623
- }
624
- /**
625
- * Executa uma tarefa hierárquica.
626
- * Um Manager recebe o objetivo, define o que cada Worker deve fazer, e consolida.
627
- */
628
- async runHierarchical(managerName, workerNames, goal, options = {}) {
629
- console.log(chalk3.cyan(`
630
- \u{1F451} Iniciando Enxame Hier\xE1rquico (L\xEDder: ${managerName})
631
- `));
632
- const managerOutput = await this.runSequential([managerName], `Analise o objetivo abaixo e descreva o que cada um dos seguintes agentes deve fazer: ${workerNames.join(", ")}.
633
-
634
- OBJETIVO: ${goal}`);
635
- console.log(chalk3.blue(`
636
- \u{1F477} Workers entrando em a\xE7\xE3o...
637
- `));
638
- const workerPromises = workerNames.map(
639
- (name) => this.runSequential([name], `Baseado no plano do Manager:
640
- ${managerOutput}
641
-
642
- Execute sua tarefa para o objetivo: ${goal}`, options)
643
- );
644
- const workerResults = await Promise.all(workerPromises);
645
- const finalResult = await this.runSequential([managerName], `Aqui est\xE3o os resultados dos workers:
646
- ${workerResults.join("\n---\n")}
647
-
648
- Por favor, consolide tudo em um resultado final perfeito para o objetivo: ${goal}`, options);
649
- return finalResult;
650
- }
651
- };
652
-
653
- // src/interface.jsx
654
- var green = "#00ff9d";
655
- var lavender = "#c084fc";
656
- var gray = "#6272a4";
657
- var yellow = "#f1fa8c";
658
- var red = "#ff5555";
659
- var cyan = "#8be9fd";
660
- marked.use(new TerminalRenderer({
661
- heading: chalk4.hex(lavender).bold,
662
- code: chalk4.hex(green),
663
- strong: chalk4.bold,
664
- em: chalk4.italic,
665
- html: () => ""
666
- }));
667
- var __filename = fileURLToPath(import.meta.url);
668
- var __dirname = path3.dirname(__filename);
669
- var pkg = JSON.parse(fs3.readFileSync(path3.join(__dirname, "../package.json"), "utf-8"));
670
- var version = pkg.version;
671
- var Header = ({ config: config3 }) => /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", marginBottom: 1 }, /* @__PURE__ */ React.createElement(Text, { color: lavender }, figlet.textSync("bimmo")), /* @__PURE__ */ React.createElement(Box, { borderStyle: "single", borderColor: lavender, paddingX: 1, justifyContent: "space-between" }, /* @__PURE__ */ React.createElement(Text, { color: green, bold: true }, "v", version), /* @__PURE__ */ React.createElement(Box, null, /* @__PURE__ */ React.createElement(Text, { color: gray }, config3.activeProfile || "Default", " "), /* @__PURE__ */ React.createElement(Text, { color: lavender }, "\u2022"), /* @__PURE__ */ React.createElement(Text, { color: gray }, " ", config3.model))));
672
- var MessageList = ({ messages }) => /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", flexGrow: 1 }, messages.filter((m) => m.role !== "system").slice(-10).map((m, i) => /* @__PURE__ */ React.createElement(Box, { key: i, flexDirection: "column", marginBottom: 1 }, /* @__PURE__ */ React.createElement(Box, null, /* @__PURE__ */ React.createElement(Text, { bold: true, color: m.role === "user" ? green : lavender }, m.role === "user" ? "\u203A Voc\xEA" : "\u203A bimmo"), m.role === "system" && /* @__PURE__ */ React.createElement(Text, { color: yellow }, " [SISTEMA]")), /* @__PURE__ */ React.createElement(Box, { paddingLeft: 2 }, /* @__PURE__ */ React.createElement(Text, null, m.role === "assistant" ? marked.parse(m.content).trim() : m.displayContent || m.content)))));
673
- var Autocomplete = ({ suggestions }) => /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", borderStyle: "round", borderColor: gray, paddingX: 1, marginBottom: 1 }, /* @__PURE__ */ React.createElement(Text, { color: gray, dimColor: true, italic: true }, "Sugest\xF5es (TAB para completar):"), suggestions.map((f, i) => /* @__PURE__ */ React.createElement(Text, { key: i, color: i === 0 ? green : gray }, f.isDir ? "\u{1F4C1}" : "\u{1F4C4}", " ", f.rel, f.isDir ? "/" : "")));
674
- var Footer = ({ exitCounter }) => /* @__PURE__ */ React.createElement(Box, { marginTop: 1, justifyContent: "space-between", paddingX: 1 }, /* @__PURE__ */ React.createElement(Text, { color: gray, dimColor: true }, "\u{1F4C1} ", path3.relative(process.env.HOME || "", process.cwd())), exitCounter === 1 && /* @__PURE__ */ React.createElement(Text, { color: yellow, bold: true }, " Pressione Ctrl+C novamente para sair"), /* @__PURE__ */ React.createElement(Box, null, /* @__PURE__ */ React.createElement(Text, { color: gray, dimColor: true, italic: true }, "\u2191\u2193 para hist\xF3rico \u2022 /help para comandos")));
675
- var BimmoApp = ({ initialConfig }) => {
676
- const { exit } = useApp();
677
- const [config3, setConfig] = useState(initialConfig);
678
- const [mode, setMode] = useState("chat");
679
- const [activePersona, setActivePersona] = useState(null);
680
- const [messages, setMessages] = useState([]);
681
- const [input, setInput] = useState("");
682
- const [isThinking, setIsThinking] = useState(false);
683
- const [thinkingMessage, setThinkingMessage] = useState("bimmo pensando...");
684
- const [exitCounter, setExitCounter] = useState(0);
685
- const [provider, setProvider] = useState(() => createProvider(initialConfig));
686
- useEffect(() => {
687
- const ctx = getProjectContext();
688
- setMessages([
689
- { role: "system", content: ctx },
690
- { role: "assistant", content: `Ol\xE1! Sou o **bimmo v${version}**. Como posso ajudar hoje?
691
-
692
- Digite \`/help\` para ver os comandos.` }
693
- ]);
694
- }, []);
695
- const filePreview = useMemo(() => {
696
- if (!input.includes("@")) return [];
697
- const words = input.split(" ");
698
- const lastWord = words[words.length - 1];
699
- if (!lastWord.startsWith("@")) return [];
700
- try {
701
- const p = lastWord.slice(1);
702
- const dir = p.includes("/") ? p.substring(0, p.lastIndexOf("/")) : ".";
703
- const filter = p.includes("/") ? p.substring(p.lastIndexOf("/") + 1) : p;
704
- const absDir = path3.resolve(process.cwd(), dir);
705
- if (!fs3.existsSync(absDir)) return [];
706
- return fs3.readdirSync(absDir).filter((f) => f.startsWith(filter) && !f.startsWith(".") && f !== "node_modules").slice(0, 5).map((f) => ({
707
- name: f,
708
- isDir: fs3.statSync(path3.join(absDir, f)).isDirectory(),
709
- rel: path3.join(dir === "." ? "" : dir, f)
710
- }));
711
- } catch (e) {
712
- return [];
713
- }
714
- }, [input]);
715
- const handleSubmit = async (val) => {
716
- const rawInput = val.trim();
717
- if (!rawInput) return;
718
- setInput("");
719
- const parts = rawInput.split(" ");
720
- const cmd = parts[0].toLowerCase();
721
- if (cmd === "/exit") exit();
722
- if (cmd === "/clear") {
723
- setMessages([{ role: "system", content: getProjectContext() }, { role: "assistant", content: "Chat limpo." }]);
724
- return;
725
- }
726
- if (["/chat", "/plan", "/edit"].includes(cmd)) {
727
- setMode(cmd.slice(1));
728
- if (cmd === "/edit") editState.autoAccept = parts[1] === "auto";
729
- return;
730
- }
731
- if (cmd === "/model") {
732
- const newModel = parts[1];
733
- if (newModel) {
734
- updateActiveModel(newModel);
735
- const newCfg = getConfig();
736
- setConfig(newCfg);
737
- setProvider(createProvider(newCfg));
738
- setMessages((prev) => [...prev, { role: "system", content: `Modelo alterado para: ${newModel}` }]);
739
- }
740
- return;
741
- }
742
- if (cmd === "/switch") {
743
- const profile = parts[1];
744
- if (switchProfile(profile)) {
745
- const newCfg = getConfig();
746
- setConfig(newCfg);
747
- setProvider(createProvider(newCfg));
748
- setMessages((prev) => [...prev, { role: "system", content: `Perfil alterado para: ${profile}` }]);
749
- }
750
- return;
751
- }
752
- if (cmd === "/use") {
753
- const agentName = parts[1];
754
- const agents = config3.agents || {};
755
- if (agentName === "normal") {
756
- setActivePersona(null);
757
- } else if (agents[agentName]) {
758
- setActivePersona(agentName);
759
- setMode(agents[agentName].mode || "chat");
760
- }
761
- return;
762
- }
763
- if (cmd === "/swarm") {
764
- const orchestrator = new SwarmOrchestrator(config3);
765
- setIsThinking(true);
766
- setThinkingMessage("Enxame em a\xE7\xE3o...");
767
- try {
768
- let response;
769
- if (parts[1] === "seq") response = await orchestrator.runSequential(parts[2].split(","), parts.slice(3).join(" "));
770
- if (parts[1] === "run") response = await orchestrator.runHierarchical(parts[2], parts[3].split(","), parts.slice(4).join(" "));
771
- setMessages((prev) => [...prev, { role: "user", content: rawInput }, { role: "assistant", content: response }]);
772
- } catch (err) {
773
- setMessages((prev) => [...prev, { role: "system", content: `Erro no enxame: ${err.message}` }]);
774
- } finally {
775
- setIsThinking(false);
776
- }
777
- return;
778
- }
779
- if (cmd === "/help") {
780
- setMessages((prev) => [...prev, { role: "assistant", content: `**Comandos:** /chat, /plan, /edit, /switch, /model, /use, /swarm, /clear, /exit, @arquivo` }]);
781
- return;
782
- }
783
- setIsThinking(true);
784
- let processedInput = rawInput;
785
- const fileMatches = rawInput.match(/@[\w\.\-\/]+/g);
786
- if (fileMatches) {
787
- for (const match of fileMatches) {
788
- const filePath = match.slice(1);
789
- try {
790
- if (fs3.existsSync(filePath)) {
791
- const content = fs3.readFileSync(filePath, "utf-8");
792
- processedInput = processedInput.replace(match, `
793
-
794
- [Arquivo: ${filePath}]
795
- \`\`\`
796
- ${content}
797
- \`\`\`
798
- `);
799
- }
800
- } catch (e) {
801
- }
802
- }
803
- }
804
- const userMsg = { role: "user", content: processedInput, displayContent: rawInput };
805
- const newMessages = [...messages, userMsg];
806
- setMessages(newMessages);
807
- try {
808
- let finalMessages = newMessages;
809
- if (activePersona && config3.agents[activePersona]) {
810
- const agent = config3.agents[activePersona];
811
- finalMessages = [{ role: "system", content: `Sua tarefa: ${agent.role}
812
-
813
- ${getProjectContext()}` }, ...newMessages.filter((m) => m.role !== "system")];
814
- }
815
- const response = await provider.sendMessage(finalMessages);
816
- setMessages((prev) => [...prev, { role: "assistant", content: response }]);
817
- } catch (err) {
818
- setMessages((prev) => [...prev, { role: "system", content: `Erro: ${err.message}` }]);
819
- } finally {
820
- setIsThinking(false);
821
- }
822
- };
823
- useInput((input2, key) => {
824
- if (key.ctrl && input2 === "c") {
825
- if (isThinking) setIsThinking(false);
826
- else {
827
- if (exitCounter === 0) {
828
- setExitCounter(1);
829
- setTimeout(() => setExitCounter(0), 2e3);
830
- } else exit();
831
- }
832
- }
833
- if (key.tab && filePreview.length > 0) {
834
- const words = input2.split(" ");
835
- words[words.length - 1] = `@${filePreview[0].rel}${filePreview[0].isDir ? "/" : ""}`;
836
- setInput(words.join(" "));
837
- }
838
- });
839
- return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", paddingX: 1, minHeight: 10 }, /* @__PURE__ */ React.createElement(Header, { config: config3 }), /* @__PURE__ */ React.createElement(MessageList, { messages }), isThinking && /* @__PURE__ */ React.createElement(Box, { marginBottom: 1 }, /* @__PURE__ */ React.createElement(Text, { color: lavender }, /* @__PURE__ */ React.createElement(Spinner, { type: "dots" }), " ", /* @__PURE__ */ React.createElement(Text, { italic: true }, thinkingMessage))), filePreview.length > 0 && /* @__PURE__ */ React.createElement(Autocomplete, { suggestions: filePreview }), /* @__PURE__ */ React.createElement(Box, { borderStyle: "round", borderColor: isThinking ? gray : lavender, paddingX: 1 }, /* @__PURE__ */ React.createElement(Text, { bold: true, color: mode === "edit" ? red : mode === "plan" ? cyan : lavender }, activePersona ? `[${activePersona.toUpperCase()}] ` : "", "[", mode.toUpperCase(), "] \u203A", " "), /* @__PURE__ */ React.createElement(TextInput, { value: input, onChange: setInput, onSubmit: handleSubmit, placeholder: "Como posso ajudar hoje?" })), /* @__PURE__ */ React.createElement(Footer, { exitCounter }));
840
- };
841
- async function startInteractive() {
842
- const config3 = getConfig();
843
- if (!config3.provider || !config3.apiKey) {
844
- console.log(chalk4.yellow('Provedor n\xE3o configurado. Execute "bimmo config" primeiro.'));
845
- process.exit(0);
846
- }
847
- process.stdout.write("\x1Bc");
848
- render(/* @__PURE__ */ React.createElement(BimmoApp, { initialConfig: config3 }));
849
- }
850
- export {
851
- startInteractive
852
- };