mirror of
https://github.com/FBukovina/burn-baby-burn.git
synced 2026-10-27 15:14:32 +00:00
Trigger Codex OAuth from Ignite
This commit is contained in:
parent
4246999931
commit
12c36ae23d
2 changed files with 330 additions and 13 deletions
|
|
@ -32,6 +32,12 @@ burn 100000 --model sonnet # walk away
|
||||||
burn 50000 --backend codex # 🤖 burn OpenAI tokens
|
burn 50000 --backend codex # 🤖 burn OpenAI tokens
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Run `burn` with no arguments to open the TUI. When you select OpenAI
|
||||||
|
Codex and press `IGNITE`, the TUI launches the default browser OAuth
|
||||||
|
flow if Codex is not already connected. You can also use the `AUTH` row
|
||||||
|
to recheck or sign in manually, or run `codex login` before opening
|
||||||
|
`burn`.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
|
||||||
337
bin/tui.js
337
bin/tui.js
|
|
@ -7,7 +7,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const readline = require('readline');
|
const readline = require('readline');
|
||||||
const { spawn } = require('child_process');
|
const { spawn, spawnSync } = require('child_process');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
|
|
||||||
|
|
@ -59,13 +59,20 @@ const ACHIEVEMENTS = [
|
||||||
|
|
||||||
// Application State
|
// Application State
|
||||||
let activeScreen = 'config'; // 'config', 'burn', 'victory'
|
let activeScreen = 'config'; // 'config', 'burn', 'victory'
|
||||||
let focusedItem = 'backend'; // 'backend', 'model', 'tokens', 'ignite'
|
let focusedItem = 'backend'; // 'backend', 'auth', 'model', 'tokens', 'ignite'
|
||||||
let backend = 'claude';
|
let backend = 'claude';
|
||||||
let activeModelIdx = 0;
|
let activeModelIdx = 0;
|
||||||
let selectedPresetIdx = 1; // 50k default
|
let selectedPresetIdx = 1; // 50k default
|
||||||
let customTokens = 250000;
|
let customTokens = 250000;
|
||||||
let isWarningNihilist = false;
|
let isWarningNihilist = false;
|
||||||
let isSmallScreen = false;
|
let isSmallScreen = false;
|
||||||
|
let codexAuthState = 'unknown'; // 'unknown', 'checking', 'connected', 'logged-out', 'error'
|
||||||
|
let codexAuthDetail = 'Not checked';
|
||||||
|
let codexAuthCheckSeq = 0;
|
||||||
|
let codexAuthCheck = null;
|
||||||
|
let codexAuthFlowActive = false;
|
||||||
|
let codexIgniteAfterAuth = false;
|
||||||
|
let codexIgniteLoginAttempted = false;
|
||||||
|
|
||||||
// Burn Stats
|
// Burn Stats
|
||||||
let activeChild = null;
|
let activeChild = null;
|
||||||
|
|
@ -281,6 +288,34 @@ function costDisplay() {
|
||||||
return costString === 'cost n/a' ? costString : `${costString} USD`;
|
return costString === 'cost n/a' ? costString : `${costString} USD`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function codexAuthDisplay() {
|
||||||
|
switch (codexAuthState) {
|
||||||
|
case 'checking':
|
||||||
|
return `${YELLOW}Checking...${RESET} ${DIM}${codexAuthDetail}${RESET}`;
|
||||||
|
case 'connected':
|
||||||
|
return `${GREEN}${BOLD}Connected${RESET} ${DIM}Space/Enter rechecks${RESET}`;
|
||||||
|
case 'logged-out':
|
||||||
|
return `${RED}${BOLD}Sign in to Codex${RESET} ${DIM}opens browser OAuth${RESET}`;
|
||||||
|
case 'error':
|
||||||
|
return `${RED}${BOLD}Recheck${RESET} ${DIM}${codexAuthDetail}${RESET}`;
|
||||||
|
case 'unknown':
|
||||||
|
default:
|
||||||
|
return `${YELLOW}${BOLD}Recheck${RESET} ${DIM}Codex login has not been checked${RESET}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function codexStatusDetail(stdout, stderr) {
|
||||||
|
const lines = stripAnsi(`${stdout}\n${stderr}`)
|
||||||
|
.split(/\r\n|\r|\n/)
|
||||||
|
.map(line => line.trim())
|
||||||
|
.filter(Boolean);
|
||||||
|
const usefulLines = lines.filter(line =>
|
||||||
|
!line.startsWith('WARNING:') &&
|
||||||
|
!line.includes('could not update PATH')
|
||||||
|
);
|
||||||
|
return usefulLines.find(line => /logged in/i.test(line)) || usefulLines[0] || lines[0] || '';
|
||||||
|
}
|
||||||
|
|
||||||
function isCoreVisualLine(line) {
|
function isCoreVisualLine(line) {
|
||||||
return (
|
return (
|
||||||
line.includes('▲') ||
|
line.includes('▲') ||
|
||||||
|
|
@ -403,6 +438,215 @@ function setScreen(newScreen) {
|
||||||
process.stdout.write(CLEAR_SCREEN);
|
process.stdout.write(CLEAR_SCREEN);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function configFields() {
|
||||||
|
return backend === 'codex'
|
||||||
|
? ['backend', 'auth', 'model', 'tokens', 'ignite']
|
||||||
|
: ['backend', 'model', 'tokens', 'ignite'];
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeFocusedItem() {
|
||||||
|
if (!configFields().includes(focusedItem)) {
|
||||||
|
focusedItem = 'backend';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setBackend(nextBackend) {
|
||||||
|
if (backend === nextBackend) return;
|
||||||
|
backend = nextBackend;
|
||||||
|
activeModelIdx = 0;
|
||||||
|
codexIgniteAfterAuth = false;
|
||||||
|
codexIgniteLoginAttempted = false;
|
||||||
|
normalizeFocusedItem();
|
||||||
|
|
||||||
|
if (backend === 'codex') {
|
||||||
|
checkCodexAuthStatus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function startRenderLoop() {
|
||||||
|
if (renderTimer) return;
|
||||||
|
renderTimer = setInterval(() => {
|
||||||
|
pulseTimerCount++;
|
||||||
|
drawScreen();
|
||||||
|
}, 150);
|
||||||
|
}
|
||||||
|
|
||||||
|
function stopRenderLoop() {
|
||||||
|
if (!renderTimer) return;
|
||||||
|
clearInterval(renderTimer);
|
||||||
|
renderTimer = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function pauseTUIForExternalCommand() {
|
||||||
|
stopRenderLoop();
|
||||||
|
process.stdout.write(CLEAR_SCREEN + SHOW_CURSOR + RESET + EXIT_ALT_SCREEN);
|
||||||
|
if (process.stdin.isTTY) {
|
||||||
|
try {
|
||||||
|
process.stdin.setRawMode(false);
|
||||||
|
} catch (e) {}
|
||||||
|
process.stdin.pause();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function resumeTUIAfterExternalCommand() {
|
||||||
|
if (cleanupComplete) return;
|
||||||
|
if (process.stdin.isTTY) {
|
||||||
|
try {
|
||||||
|
process.stdin.setRawMode(true);
|
||||||
|
} catch (e) {}
|
||||||
|
process.stdin.resume();
|
||||||
|
}
|
||||||
|
process.stdout.write(ENTER_ALT_SCREEN + HIDE_CURSOR + CLEAR_SCREEN);
|
||||||
|
startRenderLoop();
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkCodexAuthStatus() {
|
||||||
|
const seq = ++codexAuthCheckSeq;
|
||||||
|
if (codexAuthCheck) {
|
||||||
|
try {
|
||||||
|
codexAuthCheck.kill();
|
||||||
|
} catch (e) {}
|
||||||
|
codexAuthCheck = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
codexAuthState = 'checking';
|
||||||
|
codexAuthDetail = 'Checking Codex login...';
|
||||||
|
|
||||||
|
const child = spawn('codex', ['login', 'status'], {
|
||||||
|
stdio: ['ignore', 'pipe', 'pipe'],
|
||||||
|
env: {
|
||||||
|
...process.env,
|
||||||
|
PAGER: 'cat'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
codexAuthCheck = child;
|
||||||
|
|
||||||
|
let stdout = '';
|
||||||
|
let stderr = '';
|
||||||
|
child.stdout.on('data', data => {
|
||||||
|
stdout += data.toString('utf8');
|
||||||
|
});
|
||||||
|
child.stderr.on('data', data => {
|
||||||
|
stderr += data.toString('utf8');
|
||||||
|
});
|
||||||
|
child.on('error', error => {
|
||||||
|
if (seq !== codexAuthCheckSeq || codexAuthCheck !== child) return;
|
||||||
|
codexAuthCheck = null;
|
||||||
|
codexAuthState = 'error';
|
||||||
|
codexAuthDetail = error.code === 'ENOENT'
|
||||||
|
? 'codex CLI not found on PATH'
|
||||||
|
: `status check failed: ${error.message}`;
|
||||||
|
finishPendingCodexIgnition();
|
||||||
|
});
|
||||||
|
child.on('close', code => {
|
||||||
|
if (seq !== codexAuthCheckSeq || codexAuthCheck !== child) return;
|
||||||
|
codexAuthCheck = null;
|
||||||
|
|
||||||
|
const detail = codexStatusDetail(stdout, stderr);
|
||||||
|
if (code === 0) {
|
||||||
|
codexAuthState = 'connected';
|
||||||
|
codexAuthDetail = detail || 'Logged in';
|
||||||
|
} else {
|
||||||
|
codexAuthState = 'logged-out';
|
||||||
|
codexAuthDetail = detail || 'Sign in with Codex OAuth';
|
||||||
|
}
|
||||||
|
finishPendingCodexIgnition();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function finishPendingCodexIgnition() {
|
||||||
|
if (!codexIgniteAfterAuth) return;
|
||||||
|
|
||||||
|
if (backend !== 'codex') {
|
||||||
|
codexIgniteAfterAuth = false;
|
||||||
|
codexIgniteLoginAttempted = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (codexAuthState === 'connected') {
|
||||||
|
codexIgniteAfterAuth = false;
|
||||||
|
codexIgniteLoginAttempted = false;
|
||||||
|
startCombustion();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (codexAuthState === 'error' && codexAuthDetail === 'codex CLI not found on PATH') {
|
||||||
|
codexIgniteAfterAuth = false;
|
||||||
|
codexIgniteLoginAttempted = false;
|
||||||
|
focusedItem = 'auth';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (codexAuthState === 'logged-out' || codexAuthState === 'error') {
|
||||||
|
if (codexIgniteLoginAttempted) {
|
||||||
|
codexIgniteAfterAuth = false;
|
||||||
|
codexIgniteLoginAttempted = false;
|
||||||
|
focusedItem = 'auth';
|
||||||
|
if (codexAuthState === 'logged-out') {
|
||||||
|
codexAuthDetail = 'Codex login did not connect';
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
runCodexLoginFlow({ continueAfterAuth: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function runCodexLoginFlow({ continueAfterAuth = false } = {}) {
|
||||||
|
if (codexAuthFlowActive) return;
|
||||||
|
if (codexAuthState === 'checking') {
|
||||||
|
if (continueAfterAuth) codexIgniteAfterAuth = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (continueAfterAuth) {
|
||||||
|
codexIgniteAfterAuth = true;
|
||||||
|
codexIgniteLoginAttempted = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
codexAuthFlowActive = true;
|
||||||
|
codexAuthState = 'checking';
|
||||||
|
codexAuthDetail = 'Launching Codex OAuth...';
|
||||||
|
drawScreen();
|
||||||
|
pauseTUIForExternalCommand();
|
||||||
|
|
||||||
|
console.log('\nLaunching Codex OAuth. Follow the browser login, then return here.\n');
|
||||||
|
const result = spawnSync('codex', ['login'], {
|
||||||
|
stdio: 'inherit',
|
||||||
|
env: {
|
||||||
|
...process.env,
|
||||||
|
PAGER: 'cat'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
resumeTUIAfterExternalCommand();
|
||||||
|
codexAuthFlowActive = false;
|
||||||
|
|
||||||
|
if (result.error) {
|
||||||
|
codexIgniteAfterAuth = false;
|
||||||
|
codexIgniteLoginAttempted = false;
|
||||||
|
focusedItem = 'auth';
|
||||||
|
codexAuthState = 'error';
|
||||||
|
codexAuthDetail = result.error.code === 'ENOENT'
|
||||||
|
? 'codex CLI not found on PATH'
|
||||||
|
: `login failed: ${result.error.message}`;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.status !== 0) {
|
||||||
|
codexIgniteAfterAuth = false;
|
||||||
|
codexIgniteLoginAttempted = false;
|
||||||
|
focusedItem = 'auth';
|
||||||
|
codexAuthState = 'error';
|
||||||
|
codexAuthDetail = result.signal
|
||||||
|
? `codex login stopped by ${result.signal}`
|
||||||
|
: `codex login exited with ${result.status}`;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
checkCodexAuthStatus();
|
||||||
|
}
|
||||||
|
|
||||||
// Initialization and keystroke capture
|
// Initialization and keystroke capture
|
||||||
function startTUI() {
|
function startTUI() {
|
||||||
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
||||||
|
|
@ -422,6 +666,8 @@ function startTUI() {
|
||||||
exitTUI(130);
|
exitTUI(130);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (codexAuthFlowActive) return;
|
||||||
|
|
||||||
if (activeScreen === 'config') {
|
if (activeScreen === 'config') {
|
||||||
handleConfigKey(str, key);
|
handleConfigKey(str, key);
|
||||||
} else if (activeScreen === 'burn') {
|
} else if (activeScreen === 'burn') {
|
||||||
|
|
@ -432,10 +678,7 @@ function startTUI() {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Start 150ms render loop for smooth flicker-free animations (6.6 FPS)
|
// Start 150ms render loop for smooth flicker-free animations (6.6 FPS)
|
||||||
renderTimer = setInterval(() => {
|
startRenderLoop();
|
||||||
pulseTimerCount++;
|
|
||||||
drawScreen();
|
|
||||||
}, 150);
|
|
||||||
|
|
||||||
process.once('SIGINT', () => exitTUI(130));
|
process.once('SIGINT', () => exitTUI(130));
|
||||||
process.once('SIGTERM', () => exitTUI(143));
|
process.once('SIGTERM', () => exitTUI(143));
|
||||||
|
|
@ -472,9 +715,12 @@ function cleanup() {
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
activeChild = null;
|
activeChild = null;
|
||||||
}
|
}
|
||||||
if (renderTimer) {
|
stopRenderLoop();
|
||||||
clearInterval(renderTimer);
|
if (codexAuthCheck) {
|
||||||
renderTimer = null;
|
try {
|
||||||
|
codexAuthCheck.kill();
|
||||||
|
} catch (e) {}
|
||||||
|
codexAuthCheck = null;
|
||||||
}
|
}
|
||||||
if (process.stdin.isTTY) {
|
if (process.stdin.isTTY) {
|
||||||
try {
|
try {
|
||||||
|
|
@ -487,6 +733,8 @@ function cleanup() {
|
||||||
|
|
||||||
// Config Screen Key Handlers
|
// Config Screen Key Handlers
|
||||||
function handleConfigKey(str, key) {
|
function handleConfigKey(str, key) {
|
||||||
|
normalizeFocusedItem();
|
||||||
|
|
||||||
const isUp = (key && key.name === 'up') || str === '\u001b[A' || str === '\u001bOA';
|
const isUp = (key && key.name === 'up') || str === '\u001b[A' || str === '\u001bOA';
|
||||||
const isDown = (key && key.name === 'down') || str === '\u001b[B' || str === '\u001bOB';
|
const isDown = (key && key.name === 'down') || str === '\u001b[B' || str === '\u001bOB';
|
||||||
const isLeft = (key && key.name === 'left') || str === '\u001b[D' || str === '\u001bOD';
|
const isLeft = (key && key.name === 'left') || str === '\u001b[D' || str === '\u001bOD';
|
||||||
|
|
@ -511,12 +759,12 @@ function handleConfigKey(str, key) {
|
||||||
|
|
||||||
// Standard Navigation
|
// Standard Navigation
|
||||||
if (isUp) {
|
if (isUp) {
|
||||||
const fields = ['backend', 'model', 'tokens', 'ignite'];
|
const fields = configFields();
|
||||||
let idx = fields.indexOf(focusedItem) - 1;
|
let idx = fields.indexOf(focusedItem) - 1;
|
||||||
if (idx < 0) idx = fields.length - 1;
|
if (idx < 0) idx = fields.length - 1;
|
||||||
focusedItem = fields[idx];
|
focusedItem = fields[idx];
|
||||||
} else if (isDown) {
|
} else if (isDown) {
|
||||||
const fields = ['backend', 'model', 'tokens', 'ignite'];
|
const fields = configFields();
|
||||||
let idx = (fields.indexOf(focusedItem) + 1) % fields.length;
|
let idx = (fields.indexOf(focusedItem) + 1) % fields.length;
|
||||||
focusedItem = fields[idx];
|
focusedItem = fields[idx];
|
||||||
}
|
}
|
||||||
|
|
@ -524,8 +772,15 @@ function handleConfigKey(str, key) {
|
||||||
// Toggling options
|
// Toggling options
|
||||||
if (focusedItem === 'backend') {
|
if (focusedItem === 'backend') {
|
||||||
if (isLeft || isRight || isSpace) {
|
if (isLeft || isRight || isSpace) {
|
||||||
backend = backend === 'claude' ? 'codex' : 'claude';
|
setBackend(backend === 'claude' ? 'codex' : 'claude');
|
||||||
activeModelIdx = 0; // reset model index
|
}
|
||||||
|
} else if (focusedItem === 'auth') {
|
||||||
|
if (isEnter || isSpace) {
|
||||||
|
if (codexAuthState === 'connected') {
|
||||||
|
checkCodexAuthStatus();
|
||||||
|
} else {
|
||||||
|
runCodexLoginFlow();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if (focusedItem === 'model') {
|
} else if (focusedItem === 'model') {
|
||||||
const modelsCount = BACKEND_MODELS[backend].length;
|
const modelsCount = BACKEND_MODELS[backend].length;
|
||||||
|
|
@ -592,6 +847,52 @@ function handleVictoryKey(str, key) {
|
||||||
|
|
||||||
// Spawning and Running the Burn Core
|
// Spawning and Running the Burn Core
|
||||||
function igniteCombustion() {
|
function igniteCombustion() {
|
||||||
|
if (backend !== 'codex') {
|
||||||
|
startCombustion();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (codexAuthState === 'connected') {
|
||||||
|
startCombustion();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
codexIgniteAfterAuth = true;
|
||||||
|
codexIgniteLoginAttempted = false;
|
||||||
|
focusedItem = 'auth';
|
||||||
|
|
||||||
|
if (codexAuthState === 'unknown') {
|
||||||
|
checkCodexAuthStatus();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (codexAuthState === 'checking') {
|
||||||
|
codexAuthDetail = 'Will ignite after Codex auth check...';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (codexAuthState === 'error' && codexAuthDetail === 'codex CLI not found on PATH') {
|
||||||
|
codexIgniteAfterAuth = false;
|
||||||
|
codexIgniteLoginAttempted = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (codexAuthState === 'logged-out' || codexAuthState === 'error') {
|
||||||
|
runCodexLoginFlow({ continueAfterAuth: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function startCombustion() {
|
||||||
|
if (backend === 'codex' && codexAuthState !== 'connected') {
|
||||||
|
focusedItem = 'auth';
|
||||||
|
if (codexAuthState === 'unknown') {
|
||||||
|
checkCodexAuthStatus();
|
||||||
|
} else if (codexAuthState === 'logged-out') {
|
||||||
|
codexAuthDetail = 'Sign in before igniting Codex';
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
setScreen('burn');
|
setScreen('burn');
|
||||||
|
|
||||||
burnWasAborted = false;
|
burnWasAborted = false;
|
||||||
|
|
@ -774,6 +1075,8 @@ function drawScreen() {
|
||||||
|
|
||||||
// 1. Compile Screen: Config
|
// 1. Compile Screen: Config
|
||||||
function compileConfigScreen() {
|
function compileConfigScreen() {
|
||||||
|
normalizeFocusedItem();
|
||||||
|
|
||||||
let s = '';
|
let s = '';
|
||||||
|
|
||||||
s += `${ORANGE}┌────────────────────────────────────────────────────────┐${RESET}\n`;
|
s += `${ORANGE}┌────────────────────────────────────────────────────────┐${RESET}\n`;
|
||||||
|
|
@ -786,6 +1089,11 @@ function compileConfigScreen() {
|
||||||
const codexBullet = backend === 'codex' ? `${ORANGE}●${RESET}` : ' ';
|
const codexBullet = backend === 'codex' ? `${ORANGE}●${RESET}` : ' ';
|
||||||
s += `${bSel}${BOLD}🌐 BACKEND:${RESET} [${claudeBullet}] Claude Code (3.5) [${codexBullet}] OpenAI Codex (GPT-5)\n`;
|
s += `${bSel}${BOLD}🌐 BACKEND:${RESET} [${claudeBullet}] Claude Code (3.5) [${codexBullet}] OpenAI Codex (GPT-5)\n`;
|
||||||
|
|
||||||
|
if (backend === 'codex') {
|
||||||
|
const aSel = focusedItem === 'auth' ? `${ORANGE}▶${RESET} ` : ' ';
|
||||||
|
s += `${aSel}${BOLD}🔐 AUTH:${RESET} ${codexAuthDisplay()}\n`;
|
||||||
|
}
|
||||||
|
|
||||||
// Section 2: Model Selection
|
// Section 2: Model Selection
|
||||||
const mSel = focusedItem === 'model' ? `${ORANGE}▶${RESET} ` : ' ';
|
const mSel = focusedItem === 'model' ? `${ORANGE}▶${RESET} ` : ' ';
|
||||||
s += `${mSel}${BOLD}🌋 MODEL:${RESET} `;
|
s += `${mSel}${BOLD}🌋 MODEL:${RESET} `;
|
||||||
|
|
@ -831,6 +1139,9 @@ function compileConfigScreen() {
|
||||||
const modelsList = BACKEND_MODELS[backend];
|
const modelsList = BACKEND_MODELS[backend];
|
||||||
s += ` ${DIM}Desc: ${modelsList[activeModelIdx].desc}${RESET}\n`;
|
s += ` ${DIM}Desc: ${modelsList[activeModelIdx].desc}${RESET}\n`;
|
||||||
}
|
}
|
||||||
|
if (backend === 'codex') {
|
||||||
|
s += ` ${DIM}Ignite opens browser OAuth if Codex is not connected.${RESET}\n`;
|
||||||
|
}
|
||||||
|
|
||||||
s += `${DIM}────────────────────────────────────────────────────────${RESET}\n`;
|
s += `${DIM}────────────────────────────────────────────────────────${RESET}\n`;
|
||||||
s += `${DIM}[Arrow keys] Navigate & Tune [Space/Enter] Cycle [Q] Exit${RESET}\n`;
|
s += `${DIM}[Arrow keys] Navigate & Tune [Space/Enter] Cycle [Q] Exit${RESET}\n`;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue