bimmo-cli 2.2.9 → 2.2.10

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 +1 -1
  2. package/src/interface.js +46 -41
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bimmo-cli",
3
- "version": "2.2.9",
3
+ "version": "2.2.10",
4
4
  "description": "🌿 Plataforma de IA universal profissional com Agentes e Swarms. Suporte a Autocomplete real-time (estilo gemini-cli), Diffs e Contexto Inteligente.",
5
5
  "bin": {
6
6
  "bimmo": "bin/bimmo"
package/src/interface.js CHANGED
@@ -8,7 +8,6 @@ import path from 'path';
8
8
  import mime from 'mime-types';
9
9
  import readline from 'readline';
10
10
  import { fileURLToPath } from 'url';
11
- import { stripVTControlCharacters } from 'util';
12
11
 
13
12
  import { getConfig, configure, updateActiveModel, switchProfile } from './config.js';
14
13
  import { createProvider } from './providers/factory.js';
@@ -21,16 +20,13 @@ const __dirname = path.dirname(__filename);
21
20
  const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, '../package.json'), 'utf-8'));
22
21
  const version = pkg.version;
23
22
 
24
- // Configuração do renderizador - DROP TOTAL DE HTML
25
- const terminalRenderer = new TerminalRenderer({
23
+ marked.use(new TerminalRenderer({
26
24
  heading: chalk.hex('#c084fc').bold,
27
25
  code: chalk.hex('#00ff9d'),
28
26
  strong: chalk.bold,
29
27
  em: chalk.italic,
30
- html: (html) => '', // Remove qualquer tag HTML que sobrar
31
- });
32
-
33
- marked.setOptions({ renderer: terminalRenderer });
28
+ html: () => '',
29
+ }));
34
30
 
35
31
  const green = chalk.hex('#00ff9d');
36
32
  const lavender = chalk.hex('#c084fc');
@@ -43,21 +39,33 @@ let activePersona = null;
43
39
  let exitCounter = 0;
44
40
  let exitTimer = null;
45
41
 
42
+ /**
43
+ * Coleta arquivos para preview e completion (Nível Gemini-CLI)
44
+ */
46
45
  function getFilesForPreview(partialPath) {
47
46
  try {
48
- const p = partialPath.startsWith('@') ? partialPath.slice(1) : partialPath;
49
- const dir = path.dirname(p) === '.' && !p.includes('/') ? '.' : path.dirname(p);
50
- const base = path.basename(p) === '.' ? '' : path.basename(p);
51
-
52
- const searchDir = path.resolve(process.cwd(), dir);
53
- if (!fs.existsSync(searchDir)) return [];
47
+ let p = partialPath.startsWith('@') ? partialPath.slice(1) : partialPath;
48
+ let searchDir = '.';
49
+ let filter = '';
50
+
51
+ if (p.includes('/')) {
52
+ const lastSlash = p.lastIndexOf('/');
53
+ searchDir = p.substring(0, lastSlash) || '.';
54
+ filter = p.substring(lastSlash + 1);
55
+ } else {
56
+ searchDir = '.';
57
+ filter = p;
58
+ }
54
59
 
55
- const files = fs.readdirSync(searchDir);
60
+ const absoluteSearchDir = path.resolve(process.cwd(), searchDir);
61
+ if (!fs.existsSync(absoluteSearchDir)) return [];
62
+
63
+ const files = fs.readdirSync(absoluteSearchDir);
56
64
  return files
57
- .filter(f => (base === '' || f.startsWith(base)) && !f.startsWith('.') && f !== 'node_modules')
65
+ .filter(f => f.startsWith(filter) && !f.startsWith('.') && f !== 'node_modules')
58
66
  .map(f => {
59
- const rel = path.join(dir === '.' ? '' : dir, f);
60
- const isDir = fs.statSync(path.join(searchDir, f)).isDirectory();
67
+ const rel = path.join(searchDir === '.' ? '' : searchDir, f);
68
+ const isDir = fs.statSync(path.join(absoluteSearchDir, f)).isDirectory();
61
69
  return { name: rel, isDir };
62
70
  });
63
71
  } catch (e) { return []; }
@@ -77,8 +85,7 @@ export async function startInteractive() {
77
85
  let config = getConfig();
78
86
  if (!config.provider || !config.apiKey) {
79
87
  console.log(lavender(figlet.textSync('bimmo')));
80
- await configure();
81
- return startInteractive();
88
+ await configure(); return startInteractive();
82
89
  }
83
90
 
84
91
  let provider = createProvider(config);
@@ -86,14 +93,12 @@ export async function startInteractive() {
86
93
  let messages = [];
87
94
 
88
95
  const resetMessages = () => {
89
- messages = [];
90
- messages.push({ role: 'system', content: getProjectContext() });
96
+ messages = [{ role: 'system', content: getProjectContext() }];
91
97
  if (activePersona) {
92
98
  const agent = (config.agents || {})[activePersona];
93
99
  if (agent) messages.push({ role: 'system', content: `Persona: ${agent.name}. Task: ${agent.role}` });
94
100
  }
95
101
  };
96
-
97
102
  resetMessages();
98
103
 
99
104
  console.clear();
@@ -122,12 +127,12 @@ export async function startInteractive() {
122
127
 
123
128
  const clearPreview = () => {
124
129
  if (currentPreviewLines > 0) {
125
- // Move o cursor para o final das linhas de preview e apaga tudo
126
- readline.moveCursor(process.stdout, 0, currentPreviewLines);
130
+ // Move para baixo, limpa cada linha e volta
127
131
  for (let i = 0; i < currentPreviewLines; i++) {
128
- readline.moveCursor(process.stdout, 0, -1);
132
+ process.stdout.write('\n');
129
133
  readline.clearLine(process.stdout, 0);
130
134
  }
135
+ readline.moveCursor(process.stdout, 0, -currentPreviewLines);
131
136
  currentPreviewLines = 0;
132
137
  }
133
138
  };
@@ -140,20 +145,18 @@ export async function startInteractive() {
140
145
  if (lastWord.startsWith('@')) {
141
146
  const files = getFilesForPreview(lastWord);
142
147
  if (files.length > 0) {
148
+ // Salva a posição do cursor
149
+ process.stdout.write('\u001b[s');
150
+
143
151
  process.stdout.write('\n');
144
- const displayFiles = files.slice(0, 8);
152
+ const displayFiles = files.slice(0, 10);
145
153
  displayFiles.forEach(f => {
146
154
  process.stdout.write(gray(` ${f.isDir ? '📁' : '📄'} ${f.name}\n`));
147
155
  });
148
156
  currentPreviewLines = displayFiles.length + 1;
149
- // Volta o cursor para a linha do prompt de forma segura
150
- readline.moveCursor(process.stdout, 0, -currentPreviewLines);
151
- // Posiciona o cursor exatamente onde o usuário estava digitando
152
- const promptText = rl.getPrompt();
153
- const visualPromptLen = stripVTControlCharacters(promptText).length;
154
- const totalLen = visualPromptLen + rl.line.length;
155
- const cols = process.stdout.columns || 80;
156
- readline.cursorTo(process.stdout, totalLen % cols);
157
+
158
+ // Restaura a posição do cursor
159
+ process.stdout.write('\u001b[u');
157
160
  }
158
161
  }
159
162
  };
@@ -163,12 +166,11 @@ export async function startInteractive() {
163
166
  let modeLabel = `[${currentMode.toUpperCase()}]`;
164
167
  if (currentMode === 'edit') modeLabel = editState.autoAccept ? '[EDIT(AUTO)]' : '[EDIT(MANUAL)]';
165
168
 
166
- console.log(`\n${gray(`📁 ${process.cwd()}`)}`);
169
+ console.log(`${gray(`📁 ${process.cwd()}`)}`);
167
170
  rl.setPrompt(lavender.bold(personaLabel) + (currentMode === 'edit' ? chalk.red.bold(modeLabel) : lavender.bold(modeLabel)) + green(' > '));
168
171
  rl.prompt();
169
172
  };
170
173
 
171
- // Escuta teclas para o preview em tempo real
172
174
  process.stdin.on('keypress', (s, key) => {
173
175
  if (key && (key.name === 'return' || key.name === 'enter')) return;
174
176
  setImmediate(() => showPreview());
@@ -177,7 +179,7 @@ export async function startInteractive() {
177
179
  rl.on('SIGINT', () => {
178
180
  if (exitCounter === 0) {
179
181
  exitCounter++;
180
- process.stdout.write(`\n${gray('(Pressione novamente para sair)')}\n`);
182
+ process.stdout.write(`\n${gray('(Pressione Ctrl+C novamente para sair)')}\n`);
181
183
  exitTimer = setTimeout(() => { exitCounter = 0; }, 2000);
182
184
  displayPrompt();
183
185
  } else { process.exit(0); }
@@ -200,7 +202,7 @@ export async function startInteractive() {
200
202
 
201
203
  if (cmd === '/init') {
202
204
  console.log(chalk.cyan('\n🚀 Gerando .bimmorc.json...\n'));
203
- const initPrompt = `Analise o projeto e crie o .bimmorc.json com regras e stack. Use write_file.`;
205
+ const initPrompt = `Analise o projeto e crie o .bimmorc.json estruturado. Use write_file.`;
204
206
  const spinner = ora({ text: lavender(`bimmo pensando...`), color: 'red' }).start();
205
207
  try {
206
208
  const res = await provider.sendMessage([...messages, { role: 'user', content: initPrompt }]);
@@ -210,12 +212,15 @@ export async function startInteractive() {
210
212
  resetMessages(); displayPrompt(); return;
211
213
  }
212
214
 
213
- if (cmd === '/config') { rl.pause(); await configure(); config = getConfig(); provider = createProvider(config); rl.resume(); displayPrompt(); return; }
215
+ if (cmd === '/config') {
216
+ rl.pause(); await configure(); config = getConfig(); provider = createProvider(config); rl.resume();
217
+ displayPrompt(); return;
218
+ }
214
219
 
215
220
  // PROCESSAMENTO IA
216
221
  const controller = new AbortController();
217
222
  const abortHandler = () => controller.abort();
218
- process.removeListener('SIGINT', () => {}); // Limpa handlers antigos
223
+ process.removeListener('SIGINT', () => {});
219
224
  process.on('SIGINT', abortHandler);
220
225
 
221
226
  let modeInstr = "";
@@ -234,7 +239,7 @@ export async function startInteractive() {
234
239
  }
235
240
 
236
241
  messages.push({ role: 'user', content: [...processedContent, { type: 'text', text: modeInstr }] });
237
- const spinner = ora({ text: lavender(`bimmo pensando... (Ctrl+C para parar)`), color: currentMode === 'edit' ? 'red' : 'magenta' }).start();
242
+ const spinner = ora({ text: lavender(`bimmo pensando...`), color: currentMode === 'edit' ? 'red' : 'magenta' }).start();
238
243
 
239
244
  try {
240
245
  let responseText = await provider.sendMessage(messages, { signal: controller.signal });