bimmo-cli 2.2.9 → 2.2.11

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 +2 -2
  2. package/src/interface.js +68 -57
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "bimmo-cli",
3
- "version": "2.2.9",
4
- "description": "🌿 Plataforma de IA universal profissional com Agentes e Swarms. Suporte a Autocomplete real-time (estilo gemini-cli), Diffs e Contexto Inteligente.",
3
+ "version": "2.2.11",
4
+ "description": "🌿 Plataforma de IA universal profissional com Agentes e Swarms. Suporte a Autocomplete real-time robusto, Diffs e Contexto Inteligente.",
5
5
  "bin": {
6
6
  "bimmo": "bin/bimmo"
7
7
  },
package/src/interface.js CHANGED
@@ -21,16 +21,13 @@ const __dirname = path.dirname(__filename);
21
21
  const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, '../package.json'), 'utf-8'));
22
22
  const version = pkg.version;
23
23
 
24
- // Configuração do renderizador - DROP TOTAL DE HTML
25
- const terminalRenderer = new TerminalRenderer({
24
+ marked.use(new TerminalRenderer({
26
25
  heading: chalk.hex('#c084fc').bold,
27
26
  code: chalk.hex('#00ff9d'),
28
27
  strong: chalk.bold,
29
28
  em: chalk.italic,
30
- html: (html) => '', // Remove qualquer tag HTML que sobrar
31
- });
32
-
33
- marked.setOptions({ renderer: terminalRenderer });
29
+ html: () => '',
30
+ }));
34
31
 
35
32
  const green = chalk.hex('#00ff9d');
36
33
  const lavender = chalk.hex('#c084fc');
@@ -45,19 +42,28 @@ let exitTimer = null;
45
42
 
46
43
  function getFilesForPreview(partialPath) {
47
44
  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 [];
45
+ let p = partialPath.startsWith('@') ? partialPath.slice(1) : partialPath;
46
+ let searchDir = '.';
47
+ let filter = '';
48
+
49
+ if (p.includes('/')) {
50
+ const lastSlash = p.lastIndexOf('/');
51
+ searchDir = p.substring(0, lastSlash) || '.';
52
+ filter = p.substring(lastSlash + 1);
53
+ } else {
54
+ searchDir = '.';
55
+ filter = p;
56
+ }
57
+
58
+ const absoluteSearchDir = path.resolve(process.cwd(), searchDir);
59
+ if (!fs.existsSync(absoluteSearchDir)) return [];
54
60
 
55
- const files = fs.readdirSync(searchDir);
61
+ const files = fs.readdirSync(absoluteSearchDir);
56
62
  return files
57
- .filter(f => (base === '' || f.startsWith(base)) && !f.startsWith('.') && f !== 'node_modules')
63
+ .filter(f => f.startsWith(filter) && !f.startsWith('.') && f !== 'node_modules')
58
64
  .map(f => {
59
- const rel = path.join(dir === '.' ? '' : dir, f);
60
- const isDir = fs.statSync(path.join(searchDir, f)).isDirectory();
65
+ const rel = path.join(searchDir === '.' ? '' : searchDir, f);
66
+ const isDir = fs.statSync(path.join(absoluteSearchDir, f)).isDirectory();
61
67
  return { name: rel, isDir };
62
68
  });
63
69
  } catch (e) { return []; }
@@ -77,8 +83,7 @@ export async function startInteractive() {
77
83
  let config = getConfig();
78
84
  if (!config.provider || !config.apiKey) {
79
85
  console.log(lavender(figlet.textSync('bimmo')));
80
- await configure();
81
- return startInteractive();
86
+ await configure(); return startInteractive();
82
87
  }
83
88
 
84
89
  let provider = createProvider(config);
@@ -86,14 +91,12 @@ export async function startInteractive() {
86
91
  let messages = [];
87
92
 
88
93
  const resetMessages = () => {
89
- messages = [];
90
- messages.push({ role: 'system', content: getProjectContext() });
94
+ messages = [{ role: 'system', content: getProjectContext() }];
91
95
  if (activePersona) {
92
96
  const agent = (config.agents || {})[activePersona];
93
97
  if (agent) messages.push({ role: 'system', content: `Persona: ${agent.name}. Task: ${agent.role}` });
94
98
  }
95
99
  };
96
-
97
100
  resetMessages();
98
101
 
99
102
  console.clear();
@@ -122,39 +125,44 @@ export async function startInteractive() {
122
125
 
123
126
  const clearPreview = () => {
124
127
  if (currentPreviewLines > 0) {
125
- // Move o cursor para o final das linhas de preview e apaga tudo
126
- readline.moveCursor(process.stdout, 0, currentPreviewLines);
128
+ // Move o cursor para o início da primeira linha de preview
129
+ readline.moveCursor(process.stdout, 0, 1);
127
130
  for (let i = 0; i < currentPreviewLines; i++) {
128
- readline.moveCursor(process.stdout, 0, -1);
129
131
  readline.clearLine(process.stdout, 0);
132
+ readline.moveCursor(process.stdout, 0, 1);
130
133
  }
134
+ // Volta o cursor para a posição original
135
+ readline.moveCursor(process.stdout, 0, -(currentPreviewLines + 1));
131
136
  currentPreviewLines = 0;
132
137
  }
133
138
  };
134
139
 
135
140
  const showPreview = () => {
136
- clearPreview();
137
141
  const words = rl.line.split(' ');
138
142
  const lastWord = words[words.length - 1];
139
143
 
140
144
  if (lastWord.startsWith('@')) {
141
145
  const files = getFilesForPreview(lastWord);
142
146
  if (files.length > 0) {
147
+ clearPreview();
148
+ const displayFiles = files.slice(0, 10);
149
+
150
+ // Move cursor para o final da linha atual e pula linha
143
151
  process.stdout.write('\n');
144
- const displayFiles = files.slice(0, 8);
145
152
  displayFiles.forEach(f => {
146
153
  process.stdout.write(gray(` ${f.isDir ? '📁' : '📄'} ${f.name}\n`));
147
154
  });
148
- 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);
155
+ currentPreviewLines = displayFiles.length;
156
+
157
+ // Retorna o cursor para a posição exata de escrita
158
+ readline.moveCursor(process.stdout, 0, -(currentPreviewLines + 1));
159
+ const promptWidth = stripVTControlCharacters(rl.getPrompt()).length;
160
+ readline.cursorTo(process.stdout, (promptWidth + rl.line.length) % (process.stdout.columns || 80));
161
+ } else {
162
+ clearPreview();
157
163
  }
164
+ } else {
165
+ clearPreview();
158
166
  }
159
167
  };
160
168
 
@@ -163,28 +171,12 @@ export async function startInteractive() {
163
171
  let modeLabel = `[${currentMode.toUpperCase()}]`;
164
172
  if (currentMode === 'edit') modeLabel = editState.autoAccept ? '[EDIT(AUTO)]' : '[EDIT(MANUAL)]';
165
173
 
166
- console.log(`\n${gray(`📁 ${process.cwd()}`)}`);
174
+ console.log(`${gray(`📁 ${process.cwd()}`)}`);
167
175
  rl.setPrompt(lavender.bold(personaLabel) + (currentMode === 'edit' ? chalk.red.bold(modeLabel) : lavender.bold(modeLabel)) + green(' > '));
168
176
  rl.prompt();
169
177
  };
170
178
 
171
- // Escuta teclas para o preview em tempo real
172
- process.stdin.on('keypress', (s, key) => {
173
- if (key && (key.name === 'return' || key.name === 'enter')) return;
174
- setImmediate(() => showPreview());
175
- });
176
-
177
- rl.on('SIGINT', () => {
178
- if (exitCounter === 0) {
179
- exitCounter++;
180
- process.stdout.write(`\n${gray('(Pressione novamente para sair)')}\n`);
181
- exitTimer = setTimeout(() => { exitCounter = 0; }, 2000);
182
- displayPrompt();
183
- } else { process.exit(0); }
184
- });
185
-
186
- displayPrompt();
187
-
179
+ // Escuta teclas de forma segura
188
180
  rl.on('line', async (input) => {
189
181
  clearPreview();
190
182
  const rawInput = input.trim();
@@ -200,7 +192,7 @@ export async function startInteractive() {
200
192
 
201
193
  if (cmd === '/init') {
202
194
  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.`;
195
+ const initPrompt = `Analise o projeto e crie o .bimmorc.json estruturado. Use write_file.`;
204
196
  const spinner = ora({ text: lavender(`bimmo pensando...`), color: 'red' }).start();
205
197
  try {
206
198
  const res = await provider.sendMessage([...messages, { role: 'user', content: initPrompt }]);
@@ -210,12 +202,14 @@ export async function startInteractive() {
210
202
  resetMessages(); displayPrompt(); return;
211
203
  }
212
204
 
213
- if (cmd === '/config') { rl.pause(); await configure(); config = getConfig(); provider = createProvider(config); rl.resume(); displayPrompt(); return; }
205
+ if (cmd === '/config') {
206
+ rl.pause(); await configure(); config = getConfig(); provider = createProvider(config); rl.resume();
207
+ displayPrompt(); return;
208
+ }
214
209
 
215
210
  // PROCESSAMENTO IA
216
211
  const controller = new AbortController();
217
212
  const abortHandler = () => controller.abort();
218
- process.removeListener('SIGINT', () => {}); // Limpa handlers antigos
219
213
  process.on('SIGINT', abortHandler);
220
214
 
221
215
  let modeInstr = "";
@@ -234,7 +228,7 @@ export async function startInteractive() {
234
228
  }
235
229
 
236
230
  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();
231
+ const spinner = ora({ text: lavender(`bimmo pensando...`), color: currentMode === 'edit' ? 'red' : 'magenta' }).start();
238
232
 
239
233
  try {
240
234
  let responseText = await provider.sendMessage(messages, { signal: controller.signal });
@@ -253,4 +247,21 @@ export async function startInteractive() {
253
247
  displayPrompt();
254
248
  }
255
249
  });
250
+
251
+ // Listener de tecla para o preview
252
+ process.stdin.on('keypress', (s, key) => {
253
+ if (key && (key.name === 'return' || key.name === 'enter')) return;
254
+ setImmediate(() => showPreview());
255
+ });
256
+
257
+ rl.on('SIGINT', () => {
258
+ if (exitCounter === 0) {
259
+ exitCounter++;
260
+ process.stdout.write(`\n${gray('(Pressione novamente para sair)')}\n`);
261
+ exitTimer = setTimeout(() => { exitCounter = 0; }, 2000);
262
+ displayPrompt();
263
+ } else { process.exit(0); }
264
+ });
265
+
266
+ displayPrompt();
256
267
  }