@rabts/cli 2.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.
Files changed (2) hide show
  1. package/package.json +4 -2
  2. package/src/agent.js +82 -24
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rabts/cli",
3
- "version": "2.0.0",
3
+ "version": "3.0.0",
4
4
  "description": "Rabts Studio CLI",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -15,11 +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",
22
23
  "marked-terminal": "^7.3.0",
23
- "openai": "^4.52.0"
24
+ "openai": "^4.52.0",
25
+ "ora": "^9.4.1"
24
26
  }
25
27
  }
package/src/agent.js CHANGED
@@ -4,6 +4,9 @@ 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';
7
10
  import { Marked } from 'marked';
8
11
  import { markedTerminal } from 'marked-terminal';
9
12
  import { sendMessage } from './llm.js';
@@ -16,7 +19,7 @@ marked.use(markedTerminal());
16
19
  const execPromise = util.promisify(exec);
17
20
 
18
21
  // Tema Rabts
19
- const themeColor = chalk.cyan; // Azul claro para padronizar
22
+ const themeColor = chalk.cyan;
20
23
  const infoColor = chalk.gray;
21
24
 
22
25
  const tools = [
@@ -58,14 +61,39 @@ const tools = [
58
61
  required: ["command"]
59
62
  }
60
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
+ }
61
88
  }
62
89
  ];
63
90
 
64
- async function executeToolCall(toolCall) {
91
+ async function executeToolCall(toolCall, spinner) {
65
92
  const args = JSON.parse(toolCall.function.arguments);
66
93
  const cwd = process.cwd();
67
94
 
68
95
  if (toolCall.function.name === 'read_file') {
96
+ spinner.text = `Lendo arquivo: ${args.filepath}`;
69
97
  try {
70
98
  const fullPath = path.resolve(cwd, args.filepath);
71
99
  return fs.readFileSync(fullPath, 'utf8');
@@ -75,11 +103,11 @@ async function executeToolCall(toolCall) {
75
103
  }
76
104
 
77
105
  if (toolCall.function.name === 'write_file') {
106
+ spinner.text = `Salvando arquivo: ${args.filepath}`;
78
107
  try {
79
108
  const fullPath = path.resolve(cwd, args.filepath);
80
109
  fs.mkdirSync(path.dirname(fullPath), { recursive: true });
81
110
  fs.writeFileSync(fullPath, args.content, 'utf8');
82
- console.log(themeColor(` ↳ Arquivo salvo: `) + chalk.white(args.filepath));
83
111
  return `Arquivo salvo em ${fullPath}`;
84
112
  } catch (e) {
85
113
  return `Erro ao salvar arquivo: ${e.message}`;
@@ -87,6 +115,8 @@ async function executeToolCall(toolCall) {
87
115
  }
88
116
 
89
117
  if (toolCall.function.name === 'run_command') {
118
+ // Para perguntar ao usuário, precisamos pausar o spinner
119
+ spinner.stop();
90
120
  const { confirm } = await inquirer.prompt([{
91
121
  type: 'confirm',
92
122
  name: 'confirm',
@@ -94,10 +124,13 @@ async function executeToolCall(toolCall) {
94
124
  default: false
95
125
  }]);
96
126
 
97
- if (!confirm) return `Usuário negou o comando: ${args.command}`;
127
+ if (!confirm) {
128
+ spinner.start('Retomando...');
129
+ return `Usuário negou o comando: ${args.command}`;
130
+ }
98
131
 
132
+ spinner.start(`Executando comando: ${args.command}`);
99
133
  try {
100
- console.log(themeColor(` ↳ Executando: `) + chalk.white(args.command));
101
134
  const { stdout, stderr } = await execPromise(args.command, { cwd });
102
135
  return stdout || stderr || "Sucesso sem output.";
103
136
  } catch (e) {
@@ -105,6 +138,34 @@ async function executeToolCall(toolCall) {
105
138
  }
106
139
  }
107
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
+
108
169
  return "Função desconhecida.";
109
170
  }
110
171
 
@@ -113,7 +174,7 @@ function getProjectContext() {
113
174
  let contextStr = `\nContexto do Diretório Atual:\n- Caminho: ${cwd}\n`;
114
175
 
115
176
  try {
116
- const files = fs.readdirSync(cwd).slice(0, 50); // Limita para não estourar tokens
177
+ const files = fs.readdirSync(cwd).slice(0, 50);
117
178
  contextStr += `- Arquivos na raiz: ${files.join(', ')}\n`;
118
179
 
119
180
  if (fs.existsSync(path.join(cwd, 'package.json'))) {
@@ -125,9 +186,7 @@ function getProjectContext() {
125
186
  const comp = JSON.parse(fs.readFileSync(path.join(cwd, 'composer.json'), 'utf8'));
126
187
  contextStr += `- Projeto PHP (Composer): ${comp.name || 'Desconhecido'}\n`;
127
188
  }
128
- } catch (e) {
129
- // Ignora erros de permissão ou parse
130
- }
189
+ } catch (e) {}
131
190
  return contextStr;
132
191
  }
133
192
 
@@ -135,12 +194,11 @@ export async function startInteractiveLoop(config) {
135
194
  console.log(infoColor(`\nConectado via ${config.provider} (Modelo: ${config.model})`));
136
195
  console.log(infoColor('Dicas: Digite "sair" para encerrar. Digite "/clear" para limpar a memória desta pasta.\n'));
137
196
 
138
- // Carrega memória anterior se existir
139
197
  let messages = loadSessionMemory();
140
198
  const isNewSession = messages.length === 0;
141
199
 
142
200
  if (isNewSession) {
143
- let systemContent = 'Você é o Rabts AI, um assistente de terminal inteligente. Responda em Markdown limpo.';
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.';
144
202
  systemContent += getProjectContext();
145
203
 
146
204
  messages = [
@@ -177,11 +235,10 @@ export async function startInteractiveLoop(config) {
177
235
 
178
236
  if (!input) continue;
179
237
 
180
- // Se a memória foi limpa, recarrega o context inicial
181
238
  if (messages.length === 0) {
182
239
  messages.push({
183
240
  role: 'system',
184
- content: 'Você é o Rabts AI, um assistente de terminal inteligente.' + getProjectContext()
241
+ content: 'Você é o Rabts AI, um desenvolvedor autônomo. ' + getProjectContext()
185
242
  });
186
243
  }
187
244
 
@@ -192,14 +249,15 @@ export async function startInteractiveLoop(config) {
192
249
  let totalCompletionTokens = 0;
193
250
  let startTime = Date.now();
194
251
 
252
+ const spinner = ora({
253
+ text: 'Pensando...',
254
+ color: 'cyan',
255
+ spinner: 'dots'
256
+ }).start();
257
+
195
258
  while (isProcessingTools) {
196
- process.stdout.write(infoColor(' Pensando...'));
197
-
198
259
  const responseMessage = await sendMessage(config, messages, supportsTools ? tools : null);
199
260
 
200
- process.stdout.clearLine(0);
201
- process.stdout.cursorTo(0);
202
-
203
261
  if (responseMessage.usage) {
204
262
  totalPromptTokens += responseMessage.usage.prompt_tokens || 0;
205
263
  totalCompletionTokens += responseMessage.usage.completion_tokens || 0;
@@ -213,8 +271,9 @@ export async function startInteractiveLoop(config) {
213
271
 
214
272
  if (responseMessage.tool_calls && responseMessage.tool_calls.length > 0) {
215
273
  for (const toolCall of responseMessage.tool_calls) {
216
- console.log(themeColor(` Usando ferramenta: `) + chalk.white(toolCall.function.name));
217
- const result = await executeToolCall(toolCall);
274
+ spinner.text = `Analisando ferramenta: ${toolCall.function.name}`;
275
+ const result = await executeToolCall(toolCall, spinner);
276
+
218
277
  messages.push({
219
278
  tool_call_id: toolCall.id,
220
279
  role: "tool",
@@ -222,20 +281,19 @@ export async function startInteractiveLoop(config) {
222
281
  content: result,
223
282
  });
224
283
  }
284
+ spinner.text = 'Processando resultados...';
225
285
  } else {
286
+ spinner.stop();
226
287
  const duration = ((Date.now() - startTime) / 1000).toFixed(1);
227
288
 
228
289
  if (responseMessage.content) {
229
290
  console.log(themeColor('\nRabts:'));
230
- // Renderiza a resposta visualmente com syntax highlighting
231
291
  console.log(marked.parse(responseMessage.content).trim());
232
292
  }
233
293
 
234
- console.log(infoColor(`\n ↳ Finalizado em ${duration}s • ${totalPromptTokens + totalCompletionTokens} tokens (${totalPromptTokens} in, ${totalCompletionTokens} out)\n`));
294
+ console.log(infoColor(`\n ↳ Finalizado em ${duration}s • ${totalPromptTokens + totalCompletionTokens} tokens\n`));
235
295
 
236
- // Salva memória a cada interação
237
296
  saveSessionMemory(messages);
238
-
239
297
  isProcessingTools = false;
240
298
  }
241
299
  }