@rabts/cli 1.0.0 → 3.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/package.json +6 -2
- package/src/agent.js +142 -24
- package/src/config.js +3 -2
- package/src/llm.js +2 -2
- package/src/memory.js +46 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rabts/cli",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"description": "Rabts Studio CLI",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -15,9 +15,13 @@
|
|
|
15
15
|
"@google/genai": "^2.9.0",
|
|
16
16
|
"axios": "^1.7.2",
|
|
17
17
|
"chalk": "^5.3.0",
|
|
18
|
+
"cheerio": "^1.2.0",
|
|
18
19
|
"commander": "^12.1.0",
|
|
19
20
|
"dotenv": "^16.4.5",
|
|
20
21
|
"inquirer": "^10.0.0",
|
|
21
|
-
"
|
|
22
|
+
"marked": "^15.0.12",
|
|
23
|
+
"marked-terminal": "^7.3.0",
|
|
24
|
+
"openai": "^4.52.0",
|
|
25
|
+
"ora": "^9.4.1"
|
|
22
26
|
}
|
|
23
27
|
}
|
package/src/agent.js
CHANGED
|
@@ -4,12 +4,22 @@ import { exec } from 'child_process';
|
|
|
4
4
|
import util from 'util';
|
|
5
5
|
import chalk from 'chalk';
|
|
6
6
|
import inquirer from 'inquirer';
|
|
7
|
+
import ora from 'ora';
|
|
8
|
+
import * as cheerio from 'cheerio';
|
|
9
|
+
import axios from 'axios';
|
|
10
|
+
import { Marked } from 'marked';
|
|
11
|
+
import { markedTerminal } from 'marked-terminal';
|
|
7
12
|
import { sendMessage } from './llm.js';
|
|
13
|
+
import { loadSessionMemory, saveSessionMemory, clearSessionMemory } from './memory.js';
|
|
14
|
+
|
|
15
|
+
// Setup Markdown Renderer
|
|
16
|
+
const marked = new Marked();
|
|
17
|
+
marked.use(markedTerminal());
|
|
8
18
|
|
|
9
19
|
const execPromise = util.promisify(exec);
|
|
10
20
|
|
|
11
21
|
// Tema Rabts
|
|
12
|
-
const themeColor = chalk.cyan;
|
|
22
|
+
const themeColor = chalk.cyan;
|
|
13
23
|
const infoColor = chalk.gray;
|
|
14
24
|
|
|
15
25
|
const tools = [
|
|
@@ -51,14 +61,39 @@ const tools = [
|
|
|
51
61
|
required: ["command"]
|
|
52
62
|
}
|
|
53
63
|
}
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
type: "function",
|
|
67
|
+
function: {
|
|
68
|
+
name: "search_code",
|
|
69
|
+
description: "Faz uma busca global por texto (Grep) nos arquivos do projeto.",
|
|
70
|
+
parameters: {
|
|
71
|
+
type: "object",
|
|
72
|
+
properties: { query: { type: "string", description: "O que pesquisar" } },
|
|
73
|
+
required: ["query"]
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
type: "function",
|
|
79
|
+
function: {
|
|
80
|
+
name: "read_url",
|
|
81
|
+
description: "Lê e extrai texto limpo de uma página web ou documentação.",
|
|
82
|
+
parameters: {
|
|
83
|
+
type: "object",
|
|
84
|
+
properties: { url: { type: "string" } },
|
|
85
|
+
required: ["url"]
|
|
86
|
+
}
|
|
87
|
+
}
|
|
54
88
|
}
|
|
55
89
|
];
|
|
56
90
|
|
|
57
|
-
async function executeToolCall(toolCall) {
|
|
91
|
+
async function executeToolCall(toolCall, spinner) {
|
|
58
92
|
const args = JSON.parse(toolCall.function.arguments);
|
|
59
93
|
const cwd = process.cwd();
|
|
60
94
|
|
|
61
95
|
if (toolCall.function.name === 'read_file') {
|
|
96
|
+
spinner.text = `Lendo arquivo: ${args.filepath}`;
|
|
62
97
|
try {
|
|
63
98
|
const fullPath = path.resolve(cwd, args.filepath);
|
|
64
99
|
return fs.readFileSync(fullPath, 'utf8');
|
|
@@ -68,11 +103,11 @@ async function executeToolCall(toolCall) {
|
|
|
68
103
|
}
|
|
69
104
|
|
|
70
105
|
if (toolCall.function.name === 'write_file') {
|
|
106
|
+
spinner.text = `Salvando arquivo: ${args.filepath}`;
|
|
71
107
|
try {
|
|
72
108
|
const fullPath = path.resolve(cwd, args.filepath);
|
|
73
109
|
fs.mkdirSync(path.dirname(fullPath), { recursive: true });
|
|
74
110
|
fs.writeFileSync(fullPath, args.content, 'utf8');
|
|
75
|
-
console.log(themeColor(` ↳ Arquivo salvo: `) + chalk.white(args.filepath));
|
|
76
111
|
return `Arquivo salvo em ${fullPath}`;
|
|
77
112
|
} catch (e) {
|
|
78
113
|
return `Erro ao salvar arquivo: ${e.message}`;
|
|
@@ -80,6 +115,8 @@ async function executeToolCall(toolCall) {
|
|
|
80
115
|
}
|
|
81
116
|
|
|
82
117
|
if (toolCall.function.name === 'run_command') {
|
|
118
|
+
// Para perguntar ao usuário, precisamos pausar o spinner
|
|
119
|
+
spinner.stop();
|
|
83
120
|
const { confirm } = await inquirer.prompt([{
|
|
84
121
|
type: 'confirm',
|
|
85
122
|
name: 'confirm',
|
|
@@ -87,10 +124,13 @@ async function executeToolCall(toolCall) {
|
|
|
87
124
|
default: false
|
|
88
125
|
}]);
|
|
89
126
|
|
|
90
|
-
if (!confirm)
|
|
127
|
+
if (!confirm) {
|
|
128
|
+
spinner.start('Retomando...');
|
|
129
|
+
return `Usuário negou o comando: ${args.command}`;
|
|
130
|
+
}
|
|
91
131
|
|
|
132
|
+
spinner.start(`Executando comando: ${args.command}`);
|
|
92
133
|
try {
|
|
93
|
-
console.log(themeColor(` ↳ Executando: `) + chalk.white(args.command));
|
|
94
134
|
const { stdout, stderr } = await execPromise(args.command, { cwd });
|
|
95
135
|
return stdout || stderr || "Sucesso sem output.";
|
|
96
136
|
} catch (e) {
|
|
@@ -98,19 +138,75 @@ async function executeToolCall(toolCall) {
|
|
|
98
138
|
}
|
|
99
139
|
}
|
|
100
140
|
|
|
141
|
+
if (toolCall.function.name === 'search_code') {
|
|
142
|
+
spinner.text = `Buscando globalmente por: ${args.query}`;
|
|
143
|
+
try {
|
|
144
|
+
// Uso do grep recursivo ignorando node_modules
|
|
145
|
+
const cmd = `grep -rn --exclude-dir=node_modules --exclude-dir=.git "${args.query}" . || true`;
|
|
146
|
+
const { stdout } = await execPromise(cmd, { cwd });
|
|
147
|
+
if (!stdout.trim()) return "Nenhum resultado encontrado.";
|
|
148
|
+
// Limita resultados para não estourar tokens
|
|
149
|
+
return stdout.substring(0, 10000);
|
|
150
|
+
} catch (e) {
|
|
151
|
+
return `Erro ao buscar: ${e.message}`;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (toolCall.function.name === 'read_url') {
|
|
156
|
+
spinner.text = `Acessando URL: ${args.url}`;
|
|
157
|
+
try {
|
|
158
|
+
const response = await axios.get(args.url);
|
|
159
|
+
const $ = cheerio.load(response.data);
|
|
160
|
+
// Remove scripts, styles
|
|
161
|
+
$('script, style, noscript, svg, img, nav, footer').remove();
|
|
162
|
+
const text = $('body').text().replace(/\s+/g, ' ').trim();
|
|
163
|
+
return text.substring(0, 15000); // Retorna até 15k chars
|
|
164
|
+
} catch (e) {
|
|
165
|
+
return `Erro ao acessar URL: ${e.message}`;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
101
169
|
return "Função desconhecida.";
|
|
102
170
|
}
|
|
103
171
|
|
|
172
|
+
function getProjectContext() {
|
|
173
|
+
const cwd = process.cwd();
|
|
174
|
+
let contextStr = `\nContexto do Diretório Atual:\n- Caminho: ${cwd}\n`;
|
|
175
|
+
|
|
176
|
+
try {
|
|
177
|
+
const files = fs.readdirSync(cwd).slice(0, 50);
|
|
178
|
+
contextStr += `- Arquivos na raiz: ${files.join(', ')}\n`;
|
|
179
|
+
|
|
180
|
+
if (fs.existsSync(path.join(cwd, 'package.json'))) {
|
|
181
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(cwd, 'package.json'), 'utf8'));
|
|
182
|
+
contextStr += `- Projeto Node.js: ${pkg.name || 'Desconhecido'}\n`;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (fs.existsSync(path.join(cwd, 'composer.json'))) {
|
|
186
|
+
const comp = JSON.parse(fs.readFileSync(path.join(cwd, 'composer.json'), 'utf8'));
|
|
187
|
+
contextStr += `- Projeto PHP (Composer): ${comp.name || 'Desconhecido'}\n`;
|
|
188
|
+
}
|
|
189
|
+
} catch (e) {}
|
|
190
|
+
return contextStr;
|
|
191
|
+
}
|
|
192
|
+
|
|
104
193
|
export async function startInteractiveLoop(config) {
|
|
105
194
|
console.log(infoColor(`\nConectado via ${config.provider} (Modelo: ${config.model})`));
|
|
106
|
-
console.log(infoColor('
|
|
195
|
+
console.log(infoColor('Dicas: Digite "sair" para encerrar. Digite "/clear" para limpar a memória desta pasta.\n'));
|
|
107
196
|
|
|
108
|
-
let messages =
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
197
|
+
let messages = loadSessionMemory();
|
|
198
|
+
const isNewSession = messages.length === 0;
|
|
199
|
+
|
|
200
|
+
if (isNewSession) {
|
|
201
|
+
let systemContent = 'Você é o Rabts AI, um desenvolvedor e assistente de terminal altamente inteligente capaz de navegar pela internet e gerenciar código autonomamente. Responda usando Markdown limpo.';
|
|
202
|
+
systemContent += getProjectContext();
|
|
203
|
+
|
|
204
|
+
messages = [
|
|
205
|
+
{ role: 'system', content: systemContent }
|
|
206
|
+
];
|
|
207
|
+
} else {
|
|
208
|
+
console.log(chalk.green('✓ Sessão anterior restaurada para este projeto.\n'));
|
|
209
|
+
}
|
|
114
210
|
|
|
115
211
|
const supportsTools = (config.provider === 'rabts' || config.provider === 'openai');
|
|
116
212
|
|
|
@@ -123,28 +219,45 @@ export async function startInteractiveLoop(config) {
|
|
|
123
219
|
}
|
|
124
220
|
]);
|
|
125
221
|
|
|
126
|
-
|
|
222
|
+
const input = userPrompt.trim();
|
|
223
|
+
|
|
224
|
+
if (input.toLowerCase() === 'sair' || input.toLowerCase() === 'exit') {
|
|
127
225
|
console.log(infoColor('Até logo!'));
|
|
128
226
|
break;
|
|
129
227
|
}
|
|
228
|
+
|
|
229
|
+
if (input.toLowerCase() === '/clear') {
|
|
230
|
+
clearSessionMemory();
|
|
231
|
+
console.log(chalk.yellow('Memória limpa! O contexto do projeto será reiniciado na próxima mensagem.\n'));
|
|
232
|
+
messages = [];
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
130
235
|
|
|
131
|
-
if (!
|
|
236
|
+
if (!input) continue;
|
|
132
237
|
|
|
133
|
-
messages.
|
|
238
|
+
if (messages.length === 0) {
|
|
239
|
+
messages.push({
|
|
240
|
+
role: 'system',
|
|
241
|
+
content: 'Você é o Rabts AI, um desenvolvedor autônomo. ' + getProjectContext()
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
messages.push({ role: 'user', content: input });
|
|
134
246
|
|
|
135
247
|
let isProcessingTools = true;
|
|
136
248
|
let totalPromptTokens = 0;
|
|
137
249
|
let totalCompletionTokens = 0;
|
|
138
250
|
let startTime = Date.now();
|
|
139
251
|
|
|
252
|
+
const spinner = ora({
|
|
253
|
+
text: 'Pensando...',
|
|
254
|
+
color: 'cyan',
|
|
255
|
+
spinner: 'dots'
|
|
256
|
+
}).start();
|
|
257
|
+
|
|
140
258
|
while (isProcessingTools) {
|
|
141
|
-
process.stdout.write(infoColor(' Pensando...'));
|
|
142
|
-
|
|
143
259
|
const responseMessage = await sendMessage(config, messages, supportsTools ? tools : null);
|
|
144
260
|
|
|
145
|
-
process.stdout.clearLine(0);
|
|
146
|
-
process.stdout.cursorTo(0);
|
|
147
|
-
|
|
148
261
|
if (responseMessage.usage) {
|
|
149
262
|
totalPromptTokens += responseMessage.usage.prompt_tokens || 0;
|
|
150
263
|
totalCompletionTokens += responseMessage.usage.completion_tokens || 0;
|
|
@@ -158,8 +271,9 @@ export async function startInteractiveLoop(config) {
|
|
|
158
271
|
|
|
159
272
|
if (responseMessage.tool_calls && responseMessage.tool_calls.length > 0) {
|
|
160
273
|
for (const toolCall of responseMessage.tool_calls) {
|
|
161
|
-
|
|
162
|
-
const result = await executeToolCall(toolCall);
|
|
274
|
+
spinner.text = `Analisando ferramenta: ${toolCall.function.name}`;
|
|
275
|
+
const result = await executeToolCall(toolCall, spinner);
|
|
276
|
+
|
|
163
277
|
messages.push({
|
|
164
278
|
tool_call_id: toolCall.id,
|
|
165
279
|
role: "tool",
|
|
@@ -167,15 +281,19 @@ export async function startInteractiveLoop(config) {
|
|
|
167
281
|
content: result,
|
|
168
282
|
});
|
|
169
283
|
}
|
|
284
|
+
spinner.text = 'Processando resultados...';
|
|
170
285
|
} else {
|
|
286
|
+
spinner.stop();
|
|
171
287
|
const duration = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
172
288
|
|
|
173
289
|
if (responseMessage.content) {
|
|
174
290
|
console.log(themeColor('\nRabts:'));
|
|
175
|
-
console.log(
|
|
291
|
+
console.log(marked.parse(responseMessage.content).trim());
|
|
176
292
|
}
|
|
177
293
|
|
|
178
|
-
console.log(infoColor(`\n ↳ Finalizado em ${duration}s • ${totalPromptTokens + totalCompletionTokens} tokens
|
|
294
|
+
console.log(infoColor(`\n ↳ Finalizado em ${duration}s • ${totalPromptTokens + totalCompletionTokens} tokens\n`));
|
|
295
|
+
|
|
296
|
+
saveSessionMemory(messages);
|
|
179
297
|
isProcessingTools = false;
|
|
180
298
|
}
|
|
181
299
|
}
|
package/src/config.js
CHANGED
|
@@ -6,7 +6,7 @@ import chalk from 'chalk';
|
|
|
6
6
|
|
|
7
7
|
const CONFIG_FILE_PATH = path.join(os.homedir(), '.rabts-config.json');
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
// A chave padrão não fica mais no código-fonte por segurança
|
|
10
10
|
|
|
11
11
|
export function getConfig() {
|
|
12
12
|
if (fs.existsSync(CONFIG_FILE_PATH)) {
|
|
@@ -47,7 +47,8 @@ export async function configureApiKey() {
|
|
|
47
47
|
let config = { provider };
|
|
48
48
|
|
|
49
49
|
if (provider === 'rabts') {
|
|
50
|
-
|
|
50
|
+
// A chave é injetada via proxy no servidor backend
|
|
51
|
+
config.apiKey = 'proxy_mode';
|
|
51
52
|
const { model } = await inquirer.prompt([
|
|
52
53
|
{
|
|
53
54
|
type: 'list',
|
package/src/llm.js
CHANGED
|
@@ -10,8 +10,8 @@ export function getClient(config) {
|
|
|
10
10
|
|
|
11
11
|
if (config.provider === 'rabts') {
|
|
12
12
|
clients[config.provider] = new OpenAI({
|
|
13
|
-
baseURL: 'https://
|
|
14
|
-
apiKey:
|
|
13
|
+
baseURL: 'https://dev.rabts.com/api',
|
|
14
|
+
apiKey: 'proxy_mode',
|
|
15
15
|
});
|
|
16
16
|
} else if (config.provider === 'openai') {
|
|
17
17
|
clients[config.provider] = new OpenAI({
|
package/src/memory.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
|
|
4
|
+
export function getMemoryPath() {
|
|
5
|
+
const cwd = process.cwd();
|
|
6
|
+
return path.join(cwd, '.rabts', 'session.json');
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function loadSessionMemory() {
|
|
10
|
+
const memPath = getMemoryPath();
|
|
11
|
+
if (fs.existsSync(memPath)) {
|
|
12
|
+
try {
|
|
13
|
+
const data = fs.readFileSync(memPath, 'utf8');
|
|
14
|
+
return JSON.parse(data);
|
|
15
|
+
} catch (e) {
|
|
16
|
+
return [];
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return [];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function saveSessionMemory(messages) {
|
|
23
|
+
const memPath = getMemoryPath();
|
|
24
|
+
const dirPath = path.dirname(memPath);
|
|
25
|
+
|
|
26
|
+
if (!fs.existsSync(dirPath)) {
|
|
27
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Ocultar a pasta no Windows/Linux de forma simples
|
|
31
|
+
// Adicionar um gitignore para que o usuário não commite acidentalmente o histórico
|
|
32
|
+
const gitignorePath = path.join(dirPath, '.gitignore');
|
|
33
|
+
if (!fs.existsSync(gitignorePath)) {
|
|
34
|
+
fs.writeFileSync(gitignorePath, "*\n");
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Filtrar o que salvar (não precisamos salvar todos os resultados longos de tools se não quisermos)
|
|
38
|
+
fs.writeFileSync(memPath, JSON.stringify(messages, null, 2), 'utf8');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function clearSessionMemory() {
|
|
42
|
+
const memPath = getMemoryPath();
|
|
43
|
+
if (fs.existsSync(memPath)) {
|
|
44
|
+
fs.unlinkSync(memPath);
|
|
45
|
+
}
|
|
46
|
+
}
|