/** * @license * Copyright 2025 Google LLC * SPDX-License-Identifier: Apache-2.0 */ import { Box, Text } from 'ink'; import type React from 'react'; import { useEffect, useState, useCallback } from 'react'; import { theme } from '../semantic-colors.js'; import stripAnsi from 'strip-ansi'; import type { RadioSelectItem } from './shared/RadioButtonSelect.js'; import { RadioButtonSelect } from './shared/RadioButtonSelect.js'; import { MaxSizedBox } from './shared/MaxSizedBox.js'; import { Scrollable } from './shared/Scrollable.js'; import { useKeypress } from '../hooks/useKeypress.js'; import * as process from 'node:process'; import * as path from 'node:path'; import { relaunchApp } from '../../utils/processUtils.js'; import { runExitCleanup } from '../../utils/cleanup.js'; import { ExitCodes, type FolderDiscoveryResults, } from '@google/gemini-cli-core'; import { useUIState } from '../contexts/UIStateContext.js'; import { useAlternateBuffer } from '../hooks/useAlternateBuffer.js'; import { OverflowProvider } from '../contexts/OverflowContext.js'; import { ShowMoreLines } from './ShowMoreLines.js'; import { StickyHeader } from './StickyHeader.js'; export enum FolderTrustChoice { TRUST_FOLDER = 'trust_folder', TRUST_PARENT = 'trust_parent', DO_NOT_TRUST = 'do_not_trust', } interface FolderTrustDialogProps { onSelect: (choice: FolderTrustChoice) => void; isRestarting?: boolean; discoveryResults?: FolderDiscoveryResults | null; } export const FolderTrustDialog: React.FC = ({ onSelect, isRestarting, discoveryResults, }) => { const [exiting, setExiting] = useState(false); const { terminalHeight, terminalWidth, constrainHeight } = useUIState(); const isAlternateBuffer = useAlternateBuffer(); const isExpanded = !constrainHeight; useEffect(() => { let timer: ReturnType; if (isRestarting) { timer = setTimeout(async () => { await relaunchApp(); }, 250); } return () => { if (timer) clearTimeout(timer); }; }, [isRestarting]); const handleExit = useCallback(() => { setExiting(true); // Give time for the UI to render the exiting message setTimeout(async () => { await runExitCleanup(); process.exit(ExitCodes.FATAL_CANCELLATION_ERROR); }, 100); }, []); useKeypress( (key) => { if (key.name === 'escape') { handleExit(); return true; } return false; }, { isActive: !isRestarting }, ); const dirName = path.basename(process.cwd()); const parentFolder = path.basename(path.dirname(process.cwd())); const options: Array> = [ { label: `Trust folder (${dirName})`, value: FolderTrustChoice.TRUST_FOLDER, key: `Trust folder (${dirName})`, }, { label: `Trust parent folder (${parentFolder})`, value: FolderTrustChoice.TRUST_PARENT, key: `Trust parent folder (${parentFolder})`, }, { label: "Don't trust", value: FolderTrustChoice.DO_NOT_TRUST, key: "Don't trust", }, ]; const hasDiscovery = discoveryResults && (discoveryResults.commands.length > 0 || discoveryResults.mcps.length > 0 || discoveryResults.hooks.length > 0 || discoveryResults.skills.length > 0 || discoveryResults.settings.length > 0); const hasWarnings = discoveryResults && discoveryResults.securityWarnings.length > 0; const hasErrors = discoveryResults && discoveryResults.discoveryErrors && discoveryResults.discoveryErrors.length > 0; const dialogWidth = terminalWidth - 2; const borderColor = theme.status.warning; // Header: 3 lines // Options: options.length + 2 lines for margins // Footer: 1 line // Safety margin: 2 lines const overhead = 3 + options.length + 2 + 1 + 2; const scrollableHeight = Math.max(4, terminalHeight - overhead); const groups = [ { label: 'Commands', items: discoveryResults?.commands ?? [] }, { label: 'MCP Servers', items: discoveryResults?.mcps ?? [] }, { label: 'Hooks', items: discoveryResults?.hooks ?? [] }, { label: 'Skills', items: discoveryResults?.skills ?? [] }, { label: 'Setting overrides', items: discoveryResults?.settings ?? [] }, ].filter((g) => g.items.length > 0); const discoveryContent = ( Trusting a folder allows Gemini CLI to load its local configurations, including custom commands, hooks, MCP servers, agent skills, and settings. These configurations could execute code on your behalf or change the behavior of the CLI. {hasErrors && ( ❌ Discovery Errors: {discoveryResults.discoveryErrors.map((error, i) => ( • {stripAnsi(error)} ))} )} {hasWarnings && ( ⚠️ Security Warnings: {discoveryResults.securityWarnings.map((warning, i) => ( • {stripAnsi(warning)} ))} )} {hasDiscovery && ( This folder contains: {groups.map((group) => ( • {group.label} ({group.items.length}): {group.items.map((item, idx) => ( - {stripAnsi(item)} ))} ))} )} ); const title = ( Do you trust the files in this folder? ); const selectOptions = ( ); const renderContent = () => { if (isAlternateBuffer) { return ( {title} {discoveryContent} {selectOptions} ); } return ( {title} {discoveryContent} {selectOptions} ); }; const content = ( {renderContent()} {isRestarting && ( Gemini CLI is restarting to apply the trust changes... )} {exiting && ( A folder trust level must be selected to continue. Exiting since escape was pressed. )} ); return isAlternateBuffer ? ( {content} ) : ( content ); };