/** * @license * Copyright 2026 Google LLC * SPDX-License-Identifier: Apache-2.0 */ import { Box, Text } from 'ink'; import { useState } from 'react'; import { theme } from '../semantic-colors.js'; import { useConfig } from '../contexts/ConfigContext.js'; import { relaunchApp } from '../../utils/processUtils.js'; import { GEMINI_DIR, DEFAULT_CONTEXT_FILENAME } from '@google/gemini-cli-core'; import * as fs from 'node:fs/promises'; import * as path from 'node:path'; import { execSync } from 'node:child_process'; import { useTextBuffer } from './shared/text-buffer.js'; import { TextInput } from './shared/TextInput.js'; enum Step { MISSION, FIRST_STEPS, SISYPHUS_CONFIG, SAVING, ERROR, } export const ForeverModeOnboardingDialog = ({ onComplete, }: { onComplete: () => void; }) => { const config = useConfig(); const [step, setStep] = useState(Step.MISSION); const [sisyphusFocus, setSisyphusFocus] = useState<'timeout' | 'prompt'>( 'timeout', ); const [error, setError] = useState(null); const missionBuffer = useTextBuffer({ initialText: '', viewport: { width: 80, height: 3 }, singleLine: false, }); const firstStepsBuffer = useTextBuffer({ initialText: '', viewport: { width: 80, height: 5 }, singleLine: false, }); const sisyphusTimeoutBuffer = useTextBuffer({ initialText: '', viewport: { width: 50, height: 1 }, singleLine: true, }); const sisyphusPromptBuffer = useTextBuffer({ initialText: 'continue', viewport: { width: 50, height: 1 }, singleLine: true, }); const handleMissionSubmit = () => { if (missionBuffer.text.trim()) setStep(Step.FIRST_STEPS); }; const handleFirstStepsSubmit = () => { if (firstStepsBuffer.text.trim()) setStep(Step.SISYPHUS_CONFIG); }; const handleSisyphusTimeoutSubmit = (value: string) => { const num = parseInt(value, 10); if (!isNaN(num) && num > 0) { setSisyphusFocus('prompt'); } else { void handleSaveSettings(); } }; const handleSisyphusPromptSubmit = () => { void handleSaveSettings(); }; const handleSaveSettings = async () => { setStep(Step.SAVING); try { const timeoutNum = parseInt(sisyphusTimeoutBuffer.text, 10); const hasSisyphus = !isNaN(timeoutNum) && timeoutNum > 0; let frontmatter = '---\n'; frontmatter += 'sisyphus:\n'; frontmatter += ` enabled: ${hasSisyphus}\n`; if (hasSisyphus) { frontmatter += ` idleTimeout: ${timeoutNum}\n`; if (sisyphusPromptBuffer.text.trim()) { frontmatter += ` prompt: "${sisyphusPromptBuffer.text.trim()}"\n`; } } frontmatter += '---\n\n'; let content = frontmatter; if (missionBuffer.text.trim()) { content += `# Mission\n${missionBuffer.text.trim()}\n\n`; } const geminiDir = path.join(config.getTargetDir(), GEMINI_DIR); await fs.mkdir(geminiDir, { recursive: true }); await fs.writeFile( path.join(geminiDir, DEFAULT_CONTEXT_FILENAME), content, 'utf-8', ); if (firstStepsBuffer.text.trim()) { await fs.writeFile( path.join(geminiDir, '.onboarding_prompt'), firstStepsBuffer.text.trim(), 'utf-8', ); } try { execSync('git init', { cwd: geminiDir, stdio: 'ignore' }); execSync('git add .', { cwd: geminiDir, stdio: 'ignore' }); execSync('git commit -m "chore(memory): initialize gemini memory"', { cwd: geminiDir, stdio: 'ignore', }); } catch (_e) { // Ignore git errors if git is not installed or user has no git config } onComplete(); // Before relaunch await relaunchApp(); } catch (e: unknown) { if (e instanceof Error) { setError(e.message); } else { setError(String(e)); } setStep(Step.ERROR); } }; if (step === Step.ERROR) { return ( Failed to generate config {error} Please create the .gemini/GEMINI.md file manually and try again. ); } if (step === Step.SAVING) { return ( Saving your configuration... please wait. ); } if (step === Step.MISSION) { return ( Welcome to Forever Mode! You launched the CLI with --forever, which runs the agent continuously. To get started, we need to set up your{' '} .gemini/GEMINI.md configuration file. What is the primary mission of the agent? (e.g. "Refactor the authentication module to use OAuth2") ); } if (step === Step.FIRST_STEPS) { return ( What are the immediate first steps? (e.g. "Investigate src/auth.ts and propose changes") ); } if (step === Step.SISYPHUS_CONFIG) { return ( Sisyphus Mode (Auto-resume) If the agent completes a task and remains idle, it can automatically resume itself by sending a specific prompt. Enter idle timeout in minutes before the agent automatically resumes (leave blank to disable): ❯{' '} {sisyphusFocus === 'prompt' && ( What prompt should be sent when Sisyphus triggers? )} ); } if (step === Step.SAVING) { return ( Saving your settings and launching the agent... ); } if (step === Step.ERROR && error) { return ( Error {error} Press Ctrl+C to exit and try again. ); } return null; };