@banaxi/banana-code 1.6.0 → 1.7.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@banaxi/banana-code",
3
- "version": "1.6.0",
3
+ "version": "1.7.0",
4
4
  "description": "šŸŒ BananaCode",
5
5
  "keywords": [
6
6
  "banana",
package/src/config.js CHANGED
@@ -186,6 +186,7 @@ async function runSetupWizard() {
186
186
  });
187
187
 
188
188
  const config = await setupProvider(provider);
189
+ config.useMemory = true;
189
190
 
190
191
  await saveConfig(config);
191
192
  console.log(chalk.yellow.bold("\nYou're all peeled and ready. Type your first message!\n"));
package/src/constants.js CHANGED
@@ -24,10 +24,11 @@ export const OLLAMA_CLOUD_MODELS = [
24
24
  { name: 'Kimi K2.5 (Cloud)', value: 'kimi-k2.5:cloud' },
25
25
  { name: 'Qwen 3.5 397B (Cloud)', value: 'qwen3.5:397b-cloud' },
26
26
  { name: 'DeepSeek V3.2 (Cloud)', value: 'deepseek-v3.2:cloud' },
27
- { name: 'GLM-5 (Cloud)', value: 'glm-5:cloud' },
27
+ { name: 'GLM-5.1 (Cloud)', value: 'glm-5.1:cloud' },
28
28
  { name: 'MiniMax M2.7 (Cloud)', value: 'minimax-m2.7:cloud' },
29
29
  { name: 'Llama 3.3 70B (Cloud)', value: 'llama3.3:cloud' },
30
- { name: 'Llama 3.1 405B (Cloud)', value: 'llama3.1:405b-cloud' }
30
+ { name: 'Llama 3.1 405B (Cloud)', value: 'llama3.1:405b-cloud' },
31
+ { name: 'Gemma 4 31B (Cheapest, Very Good Code)', value: 'gemma4:31b-cloud' }
31
32
  ];
32
33
 
33
34
  export const MISTRAL_MODELS = [
package/src/index.js CHANGED
@@ -3,7 +3,7 @@ import chalk from 'chalk';
3
3
  import ora from 'ora';
4
4
  import { loadConfig, saveConfig, setupProvider } from './config.js';
5
5
  import { runStartup } from './startup.js';
6
- import { getSessionPermissions } from './permissions.js';
6
+ import { getSessionPermissions, setYoloMode } from './permissions.js';
7
7
 
8
8
  import { GeminiProvider } from './providers/gemini.js';
9
9
  import { ClaudeProvider } from './providers/claude.js';
@@ -325,7 +325,7 @@ async function handleSlashCommand(command) {
325
325
  {
326
326
  name: 'Enable Global AI Memory (Allows AI to save facts persistently)',
327
327
  value: 'useMemory',
328
- checked: config.useMemory || false
328
+ checked: config.useMemory !== false
329
329
  }
330
330
  ]
331
331
  });
@@ -375,6 +375,8 @@ async function handleSlashCommand(command) {
375
375
  break;
376
376
  case '/plan':
377
377
  config.planMode = true;
378
+ config.askMode = false;
379
+ config.securityMode = false;
378
380
  await saveConfig(config);
379
381
  if (providerInstance) {
380
382
  const savedMessages = providerInstance.messages;
@@ -385,8 +387,52 @@ async function handleSlashCommand(command) {
385
387
  }
386
388
  console.log(chalk.magenta(`Plan mode enabled. For significant changes, the AI will now propose an implementation plan before writing code.`));
387
389
  break;
390
+ case '/ask':
391
+ config.askMode = true;
392
+ config.planMode = false;
393
+ config.securityMode = false;
394
+ await saveConfig(config);
395
+ if (providerInstance) {
396
+ const savedMessages = providerInstance.messages;
397
+ providerInstance = createProvider();
398
+ providerInstance.messages = savedMessages;
399
+ } else {
400
+ providerInstance = createProvider();
401
+ }
402
+ console.log(chalk.blue(`Ask mode enabled. The AI will only answer questions and cannot edit files.`));
403
+ break;
404
+ case '/security':
405
+ config.securityMode = true;
406
+ config.askMode = false;
407
+ config.planMode = false;
408
+ await saveConfig(config);
409
+ if (providerInstance) {
410
+ const savedMessages = providerInstance.messages;
411
+ providerInstance = createProvider();
412
+ providerInstance.messages = savedMessages;
413
+ } else {
414
+ providerInstance = createProvider();
415
+ }
416
+ console.log(chalk.red(`Security mode enabled. The AI will look for and help fix vulnerabilities.`));
417
+ console.log(chalk.yellow(`Disclaimer: Please only use this mode for defensive purposes to secure your own code, and do not use the identified vulnerabilities maliciously.`));
418
+ break;
419
+ case '/yolo':
420
+ config.yolo = !config.yolo;
421
+ setYoloMode(config.yolo);
422
+ await saveConfig(config);
423
+ if (providerInstance) {
424
+ const savedMessages = providerInstance.messages;
425
+ providerInstance = createProvider();
426
+ providerInstance.messages = savedMessages;
427
+ } else {
428
+ providerInstance = createProvider();
429
+ }
430
+ console.log(config.yolo ? chalk.bgRed.white.bold('\n āš ļø YOLO MODE ENABLED - All permission requests will be auto-accepted! \n') : chalk.green('\nYOLO mode disabled.\n'));
431
+ break;
388
432
  case '/agent':
389
433
  config.planMode = false;
434
+ config.askMode = false;
435
+ config.securityMode = false;
390
436
  await saveConfig(config);
391
437
  if (providerInstance) {
392
438
  const savedMessages = providerInstance.messages;
@@ -438,7 +484,7 @@ async function handleSlashCommand(command) {
438
484
  }
439
485
  break;
440
486
  case '/memory':
441
- if (!config.useMemory) {
487
+ if (config.useMemory === false) {
442
488
  console.log(chalk.yellow("Global AI Memory is disabled. Enable it in /settings first."));
443
489
  break;
444
490
  }
@@ -539,6 +585,7 @@ Available commands:
539
585
  /init - Generate a BANANA.md project summary file
540
586
  /plan - Enable Plan Mode (AI proposes a plan for big changes)
541
587
  /agent - Enable Agent Mode (default, AI edits directly)
588
+ /yolo - Toggle YOLO mode (skip all permission requests)
542
589
  /debug - Toggle debug mode (show tool results)
543
590
  /help - Show all commands
544
591
  /exit - Quit Banana Code
@@ -667,7 +714,10 @@ function drawPromptBox(inputText, cursorPos) {
667
714
  // Redraw status bar and separator (they are always below the prompt)
668
715
  const modelDisplay = providerInstance ? providerInstance.modelName : (config.model || 'unknown');
669
716
  const providerDisplay = config.provider.toUpperCase();
670
- const modeDisplay = config.planMode ? chalk.magenta('PLAN MODE') : chalk.green('AGENT MODE');
717
+ let modeDisplay = chalk.green('AGENT MODE');
718
+ if (config.askMode) modeDisplay = chalk.blue('ASK MODE');
719
+ else if (config.securityMode) modeDisplay = chalk.red('SECURITY MODE');
720
+ else if (config.planMode) modeDisplay = chalk.magenta('PLAN MODE');
671
721
 
672
722
  let tokenDisplay = '';
673
723
  if (config.showTokenCount && providerInstance) {
@@ -685,7 +735,8 @@ function drawPromptBox(inputText, cursorPos) {
685
735
  tokenDisplay = ` / Tokens: ${color(tokens.toLocaleString())}`;
686
736
  }
687
737
 
688
- const leftText = ` Provider: ${chalk.cyan(providerDisplay)} / Model: ${chalk.yellow(modelDisplay)} / ${modeDisplay}${tokenDisplay}`;
738
+ const yoloDisplay = config.yolo ? chalk.bgRed.white.bold(' āš ļø YOLO ') : '';
739
+ const leftText = ` Provider: ${chalk.cyan(providerDisplay)} / Model: ${chalk.yellow(modelDisplay)} / ${modeDisplay}${tokenDisplay}${yoloDisplay ? ' / ' + yoloDisplay : ''}`;
689
740
  const rightText = '/help for shortcuts ';
690
741
  const leftStripped = leftText.replace(/\x1b\[[0-9;]*m/g, '');
691
742
  const midPad = Math.max(0, width - leftStripped.length - rightText.length);
@@ -728,7 +779,10 @@ function drawPromptBoxInitial(inputText) {
728
779
  // Status bar: Current Provider / Model + right-aligned "/help for shortcuts"
729
780
  const modelDisplay = providerInstance ? providerInstance.modelName : (config.model || 'unknown');
730
781
  const providerDisplay = config.provider.toUpperCase();
731
- const modeDisplay = config.planMode ? chalk.magenta('PLAN MODE') : chalk.green('AGENT MODE');
782
+ let modeDisplay = chalk.green('AGENT MODE');
783
+ if (config.askMode) modeDisplay = chalk.blue('ASK MODE');
784
+ else if (config.securityMode) modeDisplay = chalk.red('SECURITY MODE');
785
+ else if (config.planMode) modeDisplay = chalk.magenta('PLAN MODE');
732
786
 
733
787
  let tokenDisplay = '';
734
788
  if (config.showTokenCount && providerInstance) {
@@ -746,7 +800,8 @@ function drawPromptBoxInitial(inputText) {
746
800
  tokenDisplay = ` / Tokens: ${color(tokens.toLocaleString())}`;
747
801
  }
748
802
 
749
- const leftText = ` Provider: ${chalk.cyan(providerDisplay)} / Model: ${chalk.yellow(modelDisplay)} / ${modeDisplay}${tokenDisplay}`;
803
+ const yoloDisplay = config.yolo ? chalk.bgRed.white.bold(' āš ļø YOLO ') : '';
804
+ const leftText = ` Provider: ${chalk.cyan(providerDisplay)} / Model: ${chalk.yellow(modelDisplay)} / ${modeDisplay}${tokenDisplay}${yoloDisplay ? ' / ' + yoloDisplay : ''}`;
750
805
  const rightText = '/help for shortcuts ';
751
806
 
752
807
  const leftStripped = leftText.replace(/\x1b\[[0-9;]*m/g, '');
@@ -923,6 +978,12 @@ function promptUser() {
923
978
  async function main() {
924
979
  try {
925
980
  config = await loadConfig();
981
+
982
+ if (process.argv.includes('--yolo')) {
983
+ config.yolo = true;
984
+ }
985
+ setYoloMode(config.yolo);
986
+
926
987
  await runStartup();
927
988
 
928
989
  if (config.betaTools && config.betaTools.includes('mcp_support')) {
@@ -933,7 +994,16 @@ async function main() {
933
994
  if (apiIdx !== -1) {
934
995
  const portStr = process.argv[apiIdx + 1];
935
996
  const port = portStr && !portStr.startsWith('-') ? parseInt(portStr) : 3000;
936
- await startApiServer(port, createProvider);
997
+
998
+ let host = '127.0.0.1';
999
+ const hostIdx = process.argv.indexOf('--host');
1000
+ if (hostIdx !== -1 && process.argv[hostIdx + 1] && !process.argv[hostIdx + 1].startsWith('-')) {
1001
+ host = process.argv[hostIdx + 1];
1002
+ } else if (process.argv.includes('--expose')) {
1003
+ host = '0.0.0.0';
1004
+ }
1005
+
1006
+ await startApiServer(port, createProvider, host);
937
1007
  return;
938
1008
  }
939
1009
 
@@ -4,6 +4,10 @@ import crypto from 'crypto';
4
4
 
5
5
  const sessionPermissions = new Set();
6
6
 
7
+ export function setYoloMode(enabled) {
8
+ global.bananaYoloMode = !!enabled;
9
+ }
10
+
7
11
  function wrapText(text, width) {
8
12
  const lines = [];
9
13
  for (let i = 0; i < text.length; i += width) {
@@ -13,6 +17,10 @@ function wrapText(text, width) {
13
17
  }
14
18
 
15
19
  export async function requestPermission(actionType, details) {
20
+ if (global.bananaYoloMode || process.argv.includes('--yolo')) {
21
+ return { allowed: true };
22
+ }
23
+
16
24
  const permKey = `allow_session_${actionType}`;
17
25
 
18
26
  if (sessionPermissions.has(permKey)) {
package/src/prompt.js CHANGED
@@ -41,7 +41,7 @@ Always use tools when they would help. Be concise but thorough. `;
41
41
  } catch (e) {}
42
42
 
43
43
  // Load Global Memory
44
- if (config.useMemory) {
44
+ if (config.useMemory !== false) {
45
45
  prompt += `\n\n# Global AI Memory\nYou have the ability to remember facts across ALL sessions and projects using the \`save_memory\` tool. If the user tells you their name, personal preferences, coding rules, or other information they might want to persist, feel free to use the \`save_memory\` tool so you can remember it in the future.\n`;
46
46
 
47
47
  try {
@@ -92,6 +92,26 @@ The user is operating in "Plan Mode".
92
92
  `;
93
93
  }
94
94
 
95
+ if (config.askMode) {
96
+ prompt += `
97
+ [ASK MODE ENABLED]
98
+ The user is operating in "Ask Mode".
99
+ - You are strictly restricted to answering questions, explaining code, and providing information.
100
+ - You MUST NOT make any changes to the codebase. Do NOT use tools that modify files or execute shell commands that change state (e.g. creating/deleting files, installing packages).
101
+ - Use read-only tools like search_files, list_directory, read_file, and read-only execute_command (like running a test or git status) to gather information to answer the user's questions.
102
+ `;
103
+ }
104
+
105
+ if (config.securityMode) {
106
+ prompt += `
107
+ [SECURITY MODE ENABLED]
108
+ The user is operating in "Security Mode".
109
+ - Your primary objective is to find security vulnerabilities, misconfigurations, and bad practices in the codebase.
110
+ - Act as a red-team auditor. Search for OWASP Top 10 vulnerabilities, leaked API keys, unsafe inputs, injection flaws, etc.
111
+ - Provide detailed reports of any vulnerabilities found, including the file path, the affected lines, and suggestions for remediation.
112
+ `;
113
+ }
114
+
95
115
  if (hasPatchTool) {
96
116
  prompt += `
97
117
  When editing existing files, PREFER using the 'patch_file' tool for surgical, targeted changes instead of 'write_file', especially for large files. This prevents accidental truncation and is much more efficient. Only use 'write_file' when creating new files or when making very extensive changes to a small file.`;
package/src/server.js CHANGED
@@ -6,7 +6,7 @@ import chalk from 'chalk';
6
6
  import { loadConfig } from './config.js';
7
7
  import { listSessions, loadSession } from './sessions.js';
8
8
 
9
- export async function startApiServer(port = 3000, createProvider) {
9
+ export async function startApiServer(port = 3000, createProvider, host = '127.0.0.1') {
10
10
  const app = express();
11
11
  const server = http.createServer(app);
12
12
  const wss = new WebSocketServer({ server });
@@ -140,8 +140,8 @@ export async function startApiServer(port = 3000, createProvider) {
140
140
  res.json({ status: 'running', provider: config.provider, model: config.model });
141
141
  });
142
142
 
143
- server.listen(port, () => {
144
- console.log(chalk.green.bold(`\nšŸŒ Banana Code API Server running at http://localhost:${port}`));
143
+ server.listen(port, host, () => {
144
+ console.log(chalk.green.bold(`\nšŸŒ Banana Code API Server running at http://${host}:${port}`));
145
145
  console.log(chalk.gray(`WebSocket streaming enabled on the same port.\n`));
146
146
  });
147
147
  }
@@ -0,0 +1,32 @@
1
+ import fs from 'fs/promises';
2
+ import path from 'path';
3
+ import { requestPermission } from '../permissions.js';
4
+
5
+ export async function readManyFiles({ filepaths }) {
6
+ if (!Array.isArray(filepaths)) {
7
+ return 'Error: filepaths must be an array of strings.';
8
+ }
9
+
10
+ let results = [];
11
+
12
+ // Request permission for all files. You could ask for them individually or batch them.
13
+ // Here we'll request them individually but sequentially.
14
+ for (const filepath of filepaths) {
15
+ const absPath = path.resolve(process.cwd(), filepath);
16
+ const perm = await requestPermission('Read File', filepath);
17
+
18
+ if (!perm.allowed) {
19
+ results.push(`--- File: ${filepath} ---\nUser denied permission to read: ${filepath}\n`);
20
+ continue;
21
+ }
22
+
23
+ try {
24
+ const content = await fs.readFile(absPath, 'utf8');
25
+ results.push(`--- File: ${filepath} ---\n${content}\n`);
26
+ } catch (err) {
27
+ results.push(`--- File: ${filepath} ---\nError reading file: ${err.message}\n`);
28
+ }
29
+ }
30
+
31
+ return results.join('\n');
32
+ }
@@ -1,5 +1,6 @@
1
1
  import { execCommand } from './execCommand.js';
2
2
  import { readFile } from './readFile.js';
3
+ import { readManyFiles } from './readManyFiles.js';
3
4
  import { writeFile } from './writeFile.js';
4
5
  import { fetchUrl } from './fetchUrl.js';
5
6
  import { searchFiles } from './searchFiles.js';
@@ -36,6 +37,21 @@ export const TOOLS = [
36
37
  required: ['filepath']
37
38
  }
38
39
  },
40
+ {
41
+ name: 'read_many_files',
42
+ description: 'Read the contents of multiple files at once.',
43
+ parameters: {
44
+ type: 'object',
45
+ properties: {
46
+ filepaths: {
47
+ type: 'array',
48
+ description: 'List of file paths to read',
49
+ items: { type: 'string' }
50
+ }
51
+ },
52
+ required: ['filepaths']
53
+ }
54
+ },
39
55
  {
40
56
  name: 'write_file',
41
57
  description: 'Write content to a file. Overwrites existing content.',
@@ -210,11 +226,15 @@ export const TOOLS = [
210
226
 
211
227
  export function getAvailableTools(config = {}) {
212
228
  let available = TOOLS.filter(tool => {
229
+ if (config.askMode) {
230
+ const forbiddenInAskMode = ['write_file', 'patch_file'];
231
+ if (forbiddenInAskMode.includes(tool.name)) return false;
232
+ }
213
233
  if (tool.beta) {
214
234
  return config.betaTools && config.betaTools.includes(tool.name);
215
235
  }
216
236
  if (tool.memoryFeature) {
217
- return config.useMemory === true;
237
+ return config.useMemory !== false;
218
238
  }
219
239
  if (tool.settingsFeature) {
220
240
  // Default to true if not explicitly set to false
@@ -274,6 +294,7 @@ export async function executeTool(name, args, config) {
274
294
  switch (name) {
275
295
  case 'execute_command': return await execCommand(args);
276
296
  case 'read_file': return await readFile(args);
297
+ case 'read_many_files': return await readManyFiles(args);
277
298
  case 'write_file': return await writeFile(args);
278
299
  case 'fetch_url': return await fetchUrl(args);
279
300
  case 'search_files': return await searchFiles(args);