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 +1 -3
- package/package.json +3 -22
- package/src/{interface.jsx → interface.js} +65 -75
- package/dist/interface.js +0 -852
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": "
|
|
4
|
-
"description": "🌿 Plataforma de IA universal profissional com interface React (Ink)
|
|
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
|
|
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
|
-
|
|
39
|
+
const h = React.createElement;
|
|
41
40
|
|
|
42
41
|
const Header = ({ config }) => (
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
))
|
|
75
|
-
|
|
69
|
+
: (m.displayContent || m.content)
|
|
70
|
+
)
|
|
71
|
+
)
|
|
72
|
+
)
|
|
73
|
+
))
|
|
74
|
+
)
|
|
76
75
|
);
|
|
77
76
|
|
|
78
77
|
const Autocomplete = ({ suggestions }) => (
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
{f.isDir ? '📁' : '📄'} {f.rel}{f.isDir ? '/' : ''}
|
|
84
|
-
|
|
85
|
-
))
|
|
86
|
-
|
|
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
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
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(
|
|
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
|
-
};
|