Skip to content

Commit e20c0f0

Browse files
Neurocursoragent
andcommitted
Terminal: Add Ask Gemini flow (run-assistant-prompt + UI) so users can use Gemini without TTY
Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 56a1501 commit e20c0f0

File tree

2 files changed

+74
-1
lines changed

2 files changed

+74
-1
lines changed

electron-main.js

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,53 @@ function createWindow() {
216216
}
217217
});
218218

219+
// Run assistant (e.g. gemini) with a prompt so the user can "Ask Gemini" without TTY. Uses same command as terminal auto-start (BF6_TERMINAL_ASSISTANT_CMD or "gemini"). For gemini we use --prompt; other CLIs may get prompt on stdin.
220+
try {
221+
ipcMain.handle('run-assistant-prompt', async (event, promptText) => {
222+
const text = typeof promptText === 'string' ? promptText.trim() : '';
223+
if (!text) return { ok: false, stdout: '', stderr: 'No question provided.', error: 'empty' };
224+
const rawCmd = process.env.BF6_TERMINAL_ASSISTANT_CMD;
225+
const baseCmd = (rawCmd !== undefined && rawCmd !== null && String(rawCmd).trim() === '')
226+
? 'gemini'
227+
: String(rawCmd || 'gemini').trim();
228+
const isGemini = /^gemini(\s|$)/i.test(baseCmd);
229+
const termEnv = getTerminalEnv();
230+
const cwd = process.env.HOME || process.env.USERPROFILE || (typeof os.homedir === 'function' ? os.homedir() : undefined);
231+
232+
return new Promise((resolve) => {
233+
let stdout = '';
234+
let stderr = '';
235+
let proc;
236+
try {
237+
if (isGemini) {
238+
const args = baseCmd.split(/\s+/);
239+
const bin = args[0];
240+
const rest = args.slice(1);
241+
proc = spawn(bin, [...rest, '--prompt', text], { cwd, env: termEnv, shell: isWin, windowsHide: true });
242+
} else {
243+
proc = spawn(baseCmd, [], { cwd, env: termEnv, shell: true, windowsHide: true, stdio: ['pipe', 'pipe', 'pipe'] });
244+
if (proc.stdin) {
245+
proc.stdin.write(text + '\n');
246+
proc.stdin.end();
247+
}
248+
}
249+
if (proc.stdout) proc.stdout.on('data', (d) => { stdout += terminalDataToStr(d); });
250+
if (proc.stderr) proc.stderr.on('data', (d) => { stderr += terminalDataToStr(d); });
251+
proc.on('error', (err) => {
252+
resolve({ ok: false, stdout, stderr: stderr || err.message, error: String(err.code || err.message) });
253+
});
254+
proc.on('close', (code) => {
255+
resolve({ ok: code === 0, stdout, stderr, exitCode: code });
256+
});
257+
} catch (err) {
258+
resolve({ ok: false, stdout: '', stderr: err.message || String(err), error: String(err) });
259+
}
260+
});
261+
});
262+
} catch (e) {
263+
console.warn('[BF6] run-assistant-prompt handler failed:', e);
264+
}
265+
219266
// Help guides: renderer asks main to read .md. Try multiple paths for dev vs packaged and different packager layouts.
220267
try {
221268
ipcMain.handle('get-guide-content', (event, key) => {

web_ui/main.js

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -243,8 +243,13 @@ function setupTerminalDrawer() {
243243
<button type="button" id="terminal-drawer-close" style="background:none; border:none; color:#ccc; cursor:pointer; font-size:16px;">×</button>
244244
</div>
245245
<pre id="terminal-output" style="flex:1; margin:0; padding:10px; overflow:auto; font-family:Consolas,monospace; font-size:12px; color:#d4d4d4; white-space:pre-wrap; word-break:break-all;"></pre>
246+
<div style="padding:6px 8px; border-top:1px solid #333; display:flex; gap:8px; align-items:center; flex-shrink:0;">
247+
<label for="terminal-ask-input" style="color:#888; font-size:11px; white-space:nowrap;">Ask Gemini:</label>
248+
<input type="text" id="terminal-ask-input" placeholder="Type a question and click Ask (no TTY needed)" style="flex:1; padding:6px 10px; border-radius:6px; border:1px solid #333; background:#252526; color:#fff; font-family:Consolas,monospace; font-size:12px;" />
249+
<button type="button" id="terminal-ask-btn" style="padding:6px 12px; border-radius:6px; border:1px solid #333; background:#0d5c0d; color:#baff80; cursor:pointer; font-weight:600;">Ask</button>
250+
</div>
246251
<div style="padding:8px; border-top:1px solid #333; display:flex; gap:8px; align-items:center; flex-shrink:0;">
247-
<input type="text" id="terminal-input" placeholder="Type a command and press Enter (e.g. help, gemini, ollama run llama)" style="flex:1; padding:8px 10px; border-radius:6px; border:1px solid #333; background:#252526; color:#fff; font-family:Consolas,monospace; font-size:12px;" />
252+
<input type="text" id="terminal-input" placeholder="Shell command (e.g. help, gemini, ollama run llama)" style="flex:1; padding:8px 10px; border-radius:6px; border:1px solid #333; background:#252526; color:#fff; font-family:Consolas,monospace; font-size:12px;" />
248253
<button type="button" id="terminal-send" style="padding:8px 12px; border-radius:6px; border:1px solid #333; background:#2a2a2a; color:#baff80; cursor:pointer;">Send</button>
249254
</div>
250255
</div>
@@ -254,16 +259,37 @@ function setupTerminalDrawer() {
254259
const output = container.querySelector('#terminal-output');
255260
const input = container.querySelector('#terminal-input');
256261
const sendBtn = container.querySelector('#terminal-send');
262+
const askInput = container.querySelector('#terminal-ask-input');
263+
const askBtn = container.querySelector('#terminal-ask-btn');
257264
const closeBtn = container.querySelector('#terminal-drawer-close');
258265
const header = container.querySelector('#terminal-drawer-header');
259266

260267
function appendOut(text) { output.textContent += text; output.scrollTop = output.scrollHeight; }
261268
function sendLine() { const t = (input.value || '').trim() + '\r'; if (t) { ipc.send('terminal-keystroke', t); appendOut(input.value + '\n'); input.value = ''; } }
262269

270+
function askGemini() {
271+
const q = (askInput && askInput.value) ? askInput.value.trim() : '';
272+
if (!q || !ipc.invoke) return;
273+
askBtn.disabled = true;
274+
appendOut('\n[You] ' + q + '\n');
275+
if (askInput) askInput.value = '';
276+
ipc.invoke('run-assistant-prompt', q).then((result) => {
277+
if (result && (result.stdout || result.stderr)) {
278+
appendOut((result.stdout || '') + (result.stderr ? (result.stderr + '\n') : '') + '\n');
279+
} else if (result && result.error) {
280+
appendOut('[Error] ' + (result.stderr || result.error) + '\n');
281+
}
282+
}).catch((err) => {
283+
appendOut('[Error] ' + (err && err.message ? err.message : String(err)) + '\n');
284+
}).finally(() => { askBtn.disabled = false; });
285+
}
286+
263287
ipc.on('terminal-incoming', (e, data) => appendOut(typeof data === 'string' ? data : String(data)));
264288
closeBtn.onclick = () => { container.style.transform = 'translateY(100%)'; };
265289
sendBtn.onclick = sendLine;
266290
input.onkeydown = (e) => { if (e.key === 'Enter') { e.preventDefault(); sendLine(); } };
291+
if (askBtn) askBtn.onclick = askGemini;
292+
if (askInput) askInput.onkeydown = (e) => { if (e.key === 'Enter') { e.preventDefault(); askGemini(); } };
267293
btn.addEventListener('click', () => { const open = container.style.transform === 'translateY(0)'; container.style.transform = open ? 'translateY(100%)' : 'translateY(0)'; if (!open) setTimeout(() => input.focus(), 100); });
268294

269295
// Resize by dragging the header (same pattern as Code Preview drawer).

0 commit comments

Comments
 (0)