bimmo-cli 3.2.1 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/bimmo CHANGED
@@ -4,14 +4,12 @@
4
4
  process.removeAllListeners('warning');
5
5
 
6
6
  import { program } from 'commander';
7
+ import { startInteractive } from '../src/interface.js';
7
8
  import { configure } from '../src/config.js';
8
9
  import fs from 'fs';
9
10
  import path from 'path';
10
11
  import { fileURLToPath } from 'url';
11
12
 
12
- // Importa a interface compilada em dist/
13
- import { startInteractive } from '../dist/interface.js';
14
-
15
13
  const __filename = fileURLToPath(import.meta.url);
16
14
  const __dirname = path.dirname(__filename);
17
15
  const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, '../package.json'), 'utf-8'));
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.0",
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,7 +18,6 @@
30
18
  "files": [
31
19
  "bin/",
32
20
  "src/",
33
- "dist/",
34
21
  "README.md",
35
22
  ".bimmo-context.md"
36
23
  ],
@@ -52,7 +39,6 @@
52
39
  "ink-spinner": "^5.0.0",
53
40
  "ink-text-input": "^6.0.0",
54
41
  "inquirer": "^9.3.8",
55
- "jiti": "^2.6.1",
56
42
  "marked": "^14.0.0",
57
43
  "marked-terminal": "^7.0.0",
58
44
  "mime-types": "^2.1.35",
@@ -60,11 +46,6 @@
60
46
  "openai": "^4.82.0",
61
47
  "ora": "^8.1.1",
62
48
  "react": "^18.2.0",
63
- "tsx": "^4.19.2",
64
49
  "zod": "^3.24.1"
65
- },
66
- "devDependencies": {
67
- "esbuild": "^0.27.4",
68
- "tsx": "^4.21.0"
69
50
  }
70
51
  }
@@ -1,4 +1,4 @@
1
- import React, { useState, useEffect, useMemo, useRef } from 'react';
1
+ import React, { useState, useEffect, useMemo } from 'react';
2
2
  import { render, Box, Text, useInput, useApp } from 'ink';
3
3
  import TextInput from 'ink-text-input';
4
4
  import Spinner from 'ink-spinner';
@@ -16,7 +16,6 @@ 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 VISUAL ---
20
19
  const green = '#00ff9d';
21
20
  const lavender = '#c084fc';
22
21
  const gray = '#6272a4';
@@ -37,63 +36,63 @@ const __dirname = path.dirname(__filename);
37
36
  const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, '../package.json'), 'utf-8'));
38
37
  const version = pkg.version;
39
38
 
40
- // --- COMPONENTES ---
39
+ const h = React.createElement;
41
40
 
42
41
  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>
42
+ h(Box, { flexDirection: 'column', marginBottom: 1 },
43
+ h(Text, { color: lavender }, figlet.textSync('bimmo')),
44
+ h(Box, { borderStyle: 'single', borderColor: lavender, paddingX: 1, justifyContent: 'space-between' },
45
+ h(Text, { color: green, bold: true }, `v${version}`),
46
+ h(Box, null,
47
+ h(Text, { color: gray }, `${config.activeProfile || 'Default'} `),
48
+ h(Text, { color: lavender }, '•'),
49
+ h(Text, { color: gray }, ` ${config.model}`)
50
+ )
51
+ )
52
+ )
54
53
  );
55
54
 
56
55
  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'
56
+ h(Box, { flexDirection: 'column', flexGrow: 1 },
57
+ messages.filter(m => m.role !== 'system').slice(-10).map((m, i) => (
58
+ h(Box, { key: i, flexDirection: 'column', marginBottom: 1 },
59
+ h(Box, null,
60
+ h(Text, { bold: true, color: m.role === 'user' ? green : lavender },
61
+ m.role === 'user' ? '› Você' : '› bimmo'
62
+ ),
63
+ m.role === 'system' && h(Text, { color: yellow }, ' [SISTEMA]')
64
+ ),
65
+ h(Box, { paddingLeft: 2 },
66
+ h(Text, null,
67
+ m.role === 'assistant'
69
68
  ? marked.parse(m.content).trim()
70
- : (m.displayContent || m.content)}
71
- </Text>
72
- </Box>
73
- </Box>
74
- ))}
75
- </Box>
69
+ : (m.displayContent || m.content)
70
+ )
71
+ )
72
+ )
73
+ ))
74
+ )
76
75
  );
77
76
 
78
77
  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>
78
+ h(Box, { flexDirection: 'column', borderStyle: 'round', borderColor: gray, paddingX: 1, marginBottom: 1 },
79
+ h(Text, { color: gray, dimColor: true, italic: true }, 'Sugestões (TAB para completar):'),
80
+ suggestions.map((f, i) => (
81
+ h(Text, { key: i, color: i === 0 ? green : gray },
82
+ `${f.isDir ? '📁' : '📄'} ${f.rel}${f.isDir ? '/' : ''}`
83
+ )
84
+ ))
85
+ )
87
86
  );
88
87
 
89
88
  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>
89
+ h(Box, { marginTop: 1, justifyContent: 'space-between', paddingX: 1 },
90
+ h(Text, { color: gray, dimColor: true }, `📁 ${path.relative(process.env.HOME || '', process.cwd())}`),
91
+ exitCounter === 1 && h(Text, { color: yellow, bold: true }, ' Pressione Ctrl+C novamente para sair'),
92
+ h(Box, null,
93
+ h(Text, { color: gray, dimColor: true, italic: true }, '↑↓ para histórico • /help para comandos')
94
+ )
95
+ )
97
96
  );
98
97
 
99
98
  const BimmoApp = ({ initialConfig }) => {
@@ -108,7 +107,6 @@ const BimmoApp = ({ initialConfig }) => {
108
107
  const [exitCounter, setExitCounter] = useState(0);
109
108
  const [provider, setProvider] = useState(() => createProvider(initialConfig));
110
109
 
111
- // Inicializa contexto
112
110
  useEffect(() => {
113
111
  const ctx = getProjectContext();
114
112
  setMessages([
@@ -146,7 +144,6 @@ const BimmoApp = ({ initialConfig }) => {
146
144
  const parts = rawInput.split(' ');
147
145
  const cmd = parts[0].toLowerCase();
148
146
 
149
- // Comandos de Sistema
150
147
  if (cmd === '/exit') exit();
151
148
  if (cmd === '/clear') {
152
149
  setMessages([{ role: 'system', content: getProjectContext() }, { role: 'assistant', content: 'Chat limpo.' }]);
@@ -215,7 +212,6 @@ const BimmoApp = ({ initialConfig }) => {
215
212
  return;
216
213
  }
217
214
 
218
- // Processamento de arquivos @
219
215
  setIsThinking(true);
220
216
  let processedInput = rawInput;
221
217
  const fileMatches = rawInput.match(/@[\w\.\-\/]+/g);
@@ -266,30 +262,24 @@ const BimmoApp = ({ initialConfig }) => {
266
262
  });
267
263
 
268
264
  return (
269
- <Box flexDirection="column" paddingX={1} minHeight={10}>
270
- <Header config={config} />
271
- <MessageList messages={messages} />
272
-
273
- {isThinking && (
274
- <Box marginBottom={1}>
275
- <Text color={lavender}>
276
- <Spinner type="dots" /> <Text italic>{thinkingMessage}</Text>
277
- </Text>
278
- </Box>
279
- )}
280
-
281
- {filePreview.length > 0 && <Autocomplete suggestions={filePreview} />}
282
-
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>
265
+ h(Box, { flexDirection: 'column', paddingX: 1, minHeight: 10 },
266
+ h(Header, { config }),
267
+ h(MessageList, { messages }),
268
+ isThinking && h(Box, { marginBottom: 1 },
269
+ h(Text, { color: lavender },
270
+ h(Spinner, { type: 'dots' }),
271
+ h(Text, { italic: true }, ` ${thinkingMessage}`)
272
+ )
273
+ ),
274
+ filePreview.length > 0 && h(Autocomplete, { suggestions: filePreview }),
275
+ h(Box, { borderStyle: 'round', borderColor: isThinking ? gray : lavender, paddingX: 1 },
276
+ h(Text, { bold: true, color: mode === 'edit' ? red : mode === 'plan' ? cyan : lavender },
277
+ `${activePersona ? `[${activePersona.toUpperCase()}] ` : ''}[${mode.toUpperCase()}] › `
278
+ ),
279
+ h(TextInput, { value: input, onChange: setInput, onSubmit: handleSubmit, placeholder: 'Como posso ajudar hoje?' })
280
+ ),
281
+ h(Footer, { exitCounter })
282
+ )
293
283
  );
294
284
  };
295
285
 
@@ -300,5 +290,5 @@ export async function startInteractive() {
300
290
  process.exit(0);
301
291
  }
302
292
  process.stdout.write('\x1Bc');
303
- render(<BimmoApp initialConfig={config} />);
293
+ render(h(BimmoApp, { initialConfig: config }));
304
294
  }
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
- };