summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/cli/src/services/BuiltinCommandLoader.ts2
-rw-r--r--packages/cli/src/ui/App.tsx39
-rw-r--r--packages/cli/src/ui/commands/configCommand.ts33
-rw-r--r--packages/cli/src/ui/commands/types.ts1
-rw-r--r--packages/cli/src/ui/hooks/slashCommandProcessor.ts3
-rw-r--r--packages/core/src/config/config.ts130
6 files changed, 168 insertions, 40 deletions
diff --git a/packages/cli/src/services/BuiltinCommandLoader.ts b/packages/cli/src/services/BuiltinCommandLoader.ts
index 7ba0d6bb..b0c85d2a 100644
--- a/packages/cli/src/services/BuiltinCommandLoader.ts
+++ b/packages/cli/src/services/BuiltinCommandLoader.ts
@@ -29,6 +29,7 @@ import { statsCommand } from '../ui/commands/statsCommand.js';
import { themeCommand } from '../ui/commands/themeCommand.js';
import { toolsCommand } from '../ui/commands/toolsCommand.js';
import { vimCommand } from '../ui/commands/vimCommand.js';
+import { configCommand } from '../ui/commands/configCommand.js';
/**
* Loads the core, hard-coded slash commands that are an integral part
@@ -54,6 +55,7 @@ export class BuiltinCommandLoader implements ICommandLoader {
compressCommand,
copyCommand,
corgiCommand,
+ configCommand,
docsCommand,
editorCommand,
extensionsCommand,
diff --git a/packages/cli/src/ui/App.tsx b/packages/cli/src/ui/App.tsx
index aacf45d7..43060fdb 100644
--- a/packages/cli/src/ui/App.tsx
+++ b/packages/cli/src/ui/App.tsx
@@ -39,8 +39,12 @@ import { EditorSettingsDialog } from './components/EditorSettingsDialog.js';
import { ShellConfirmationDialog } from './components/ShellConfirmationDialog.js';
import { Colors } from './colors.js';
import { Help } from './components/Help.js';
-import { loadHierarchicalGeminiMemory } from '../config/config.js';
-import { LoadedSettings } from '../config/settings.js';
+import {
+ loadHierarchicalGeminiMemory,
+ loadCliConfig,
+ parseArguments,
+} from '../config/config.js';
+import { LoadedSettings, loadSettings } from '../config/settings.js';
import { Tips } from './components/Tips.js';
import { ConsolePatcher } from './utils/ConsolePatcher.js';
import { registerCleanup } from '../utils/cleanup.js';
@@ -62,6 +66,7 @@ import {
AuthType,
type IdeContext,
ideContext,
+ sessionId,
} from '@google/gemini-cli-core';
import { validateAuthMethod } from '../config/auth.js';
import { useLogger } from './hooks/useLogger.js';
@@ -89,6 +94,7 @@ import { OverflowProvider } from './contexts/OverflowContext.js';
import { ShowMoreLines } from './components/ShowMoreLines.js';
import { PrivacyNotice } from './privacy/PrivacyNotice.js';
import { appEvents, AppEvent } from '../utils/events.js';
+import { loadExtensions } from '../config/extension.js';
const CTRL_EXIT_PROMPT_DURATION_MS = 1000;
@@ -107,12 +113,14 @@ export const AppWrapper = (props: AppProps) => (
</SessionStatsProvider>
);
-const App = ({ config, settings, startupWarnings = [], version }: AppProps) => {
+const App = (props: AppProps) => {
+ const [config, setConfig] = useState<Config>(props.config);
+ const [settings, setSettings] = useState<LoadedSettings>(props.settings);
const isFocused = useFocus();
useBracketedPaste();
const [updateMessage, setUpdateMessage] = useState<string | null>(null);
const { stdout } = useStdout();
- const nightly = version.includes('nightly');
+ const nightly = props.version.includes('nightly');
useEffect(() => {
checkForUpdates().then(setUpdateMessage);
@@ -307,6 +315,22 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => {
}
}, [config, addItem, settings.merged]);
+ const refreshConfig = useCallback(async () => {
+ const newSettings = loadSettings(process.cwd());
+ const newExtensions = loadExtensions(process.cwd());
+ const argv = await parseArguments();
+ const newConfig = await loadCliConfig(
+ newSettings.merged,
+ newExtensions,
+ sessionId,
+ argv,
+ );
+ await newConfig.initialize();
+ setConfig(newConfig);
+ setSettings(newSettings);
+ setGeminiMdFileCount(newConfig.getGeminiMdFileCount());
+ }, []);
+
// Watch for model changes (e.g., from Flash fallback)
useEffect(() => {
const checkModelChange = () => {
@@ -474,6 +498,7 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => {
openPrivacyNotice,
toggleVimEnabled,
setIsProcessing,
+ refreshConfig,
);
const {
@@ -777,7 +802,7 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => {
{!settings.merged.hideBanner && (
<Header
terminalWidth={terminalWidth}
- version={version}
+ version={props.version}
nightly={nightly}
/>
)}
@@ -821,7 +846,7 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => {
{showHelp && <Help commands={slashCommands} />}
<Box flexDirection="column" ref={mainControlsRef}>
- {startupWarnings.length > 0 && (
+ {props.startupWarnings && props.startupWarnings.length > 0 && (
<Box
borderStyle="round"
borderColor={Colors.AccentYellow}
@@ -829,7 +854,7 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => {
marginY={1}
flexDirection="column"
>
- {startupWarnings.map((warning, index) => (
+ {props.startupWarnings.map((warning, index) => (
<Text key={index} color={Colors.AccentYellow}>
{warning}
</Text>
diff --git a/packages/cli/src/ui/commands/configCommand.ts b/packages/cli/src/ui/commands/configCommand.ts
new file mode 100644
index 00000000..3651b221
--- /dev/null
+++ b/packages/cli/src/ui/commands/configCommand.ts
@@ -0,0 +1,33 @@
+/**
+ * @license
+ * Copyright 2025 Google LLC
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import {
+ CommandKind,
+ SlashCommand,
+ SlashCommandActionReturn,
+} from './types.js';
+
+export const configCommand: SlashCommand = {
+ name: 'config',
+ description: 'Commands for interacting with the CLI configuration.',
+ kind: CommandKind.BUILT_IN,
+ subCommands: [
+ {
+ name: 'refresh',
+ description: 'Reload settings and extensions from the filesystem.',
+ kind: CommandKind.BUILT_IN,
+ action: async (context): Promise<SlashCommandActionReturn> => {
+ await context.ui.refreshConfig();
+ return {
+ type: 'message',
+ messageType: 'info',
+ content:
+ 'Configuration, extensions, memory, and tools have been refreshed.',
+ };
+ },
+ },
+ ],
+};
diff --git a/packages/cli/src/ui/commands/types.ts b/packages/cli/src/ui/commands/types.ts
index 2844177f..6665da4b 100644
--- a/packages/cli/src/ui/commands/types.ts
+++ b/packages/cli/src/ui/commands/types.ts
@@ -59,6 +59,7 @@ export interface CommandContext {
/** Toggles a special display mode. */
toggleCorgiMode: () => void;
toggleVimEnabled: () => Promise<boolean>;
+ refreshConfig: () => Promise<void>;
};
// Session-specific data
session: {
diff --git a/packages/cli/src/ui/hooks/slashCommandProcessor.ts b/packages/cli/src/ui/hooks/slashCommandProcessor.ts
index be32de11..67e49c21 100644
--- a/packages/cli/src/ui/hooks/slashCommandProcessor.ts
+++ b/packages/cli/src/ui/hooks/slashCommandProcessor.ts
@@ -50,6 +50,7 @@ export const useSlashCommandProcessor = (
openPrivacyNotice: () => void,
toggleVimEnabled: () => Promise<boolean>,
setIsProcessing: (isProcessing: boolean) => void,
+ refreshConfig: () => Promise<void>,
) => {
const session = useSessionStats();
const [commands, setCommands] = useState<readonly SlashCommand[]>([]);
@@ -158,6 +159,7 @@ export const useSlashCommandProcessor = (
setPendingItem: setPendingCompressionItem,
toggleCorgiMode,
toggleVimEnabled,
+ refreshConfig,
},
session: {
stats: session.stats,
@@ -180,6 +182,7 @@ export const useSlashCommandProcessor = (
toggleCorgiMode,
toggleVimEnabled,
sessionShellAllowlist,
+ refreshConfig,
],
);
diff --git a/packages/core/src/config/config.ts b/packages/core/src/config/config.ts
index 7ccfdbc8..e03abe8a 100644
--- a/packages/core/src/config/config.ts
+++ b/packages/core/src/config/config.ts
@@ -188,60 +188,62 @@ export interface ConfigParameters {
export class Config {
private toolRegistry!: ToolRegistry;
private promptRegistry!: PromptRegistry;
- private readonly sessionId: string;
+ private sessionId: string;
private contentGeneratorConfig!: ContentGeneratorConfig;
- private readonly embeddingModel: string;
- private readonly sandbox: SandboxConfig | undefined;
- private readonly targetDir: string;
- private readonly debugMode: boolean;
- private readonly question: string | undefined;
- private readonly fullContext: boolean;
- private readonly coreTools: string[] | undefined;
- private readonly excludeTools: string[] | undefined;
- private readonly toolDiscoveryCommand: string | undefined;
- private readonly toolCallCommand: string | undefined;
- private readonly mcpServerCommand: string | undefined;
- private readonly mcpServers: Record<string, MCPServerConfig> | undefined;
+ private embeddingModel: string;
+ private sandbox: SandboxConfig | undefined;
+ private targetDir: string;
+ private debugMode: boolean;
+ private question: string | undefined;
+ private fullContext: boolean;
+ private coreTools: string[] | undefined;
+ private excludeTools: string[] | undefined;
+ private toolDiscoveryCommand: string | undefined;
+ private toolCallCommand: string | undefined;
+ private mcpServerCommand: string | undefined;
+ private mcpServers: Record<string, MCPServerConfig> | undefined;
private userMemory: string;
private geminiMdFileCount: number;
private approvalMode: ApprovalMode;
- private readonly showMemoryUsage: boolean;
- private readonly accessibility: AccessibilitySettings;
- private readonly telemetrySettings: TelemetrySettings;
- private readonly usageStatisticsEnabled: boolean;
+ private showMemoryUsage: boolean;
+ private accessibility: AccessibilitySettings;
+ private telemetrySettings: TelemetrySettings;
+ private usageStatisticsEnabled: boolean;
private geminiClient!: GeminiClient;
- private readonly fileFiltering: {
+ private fileFiltering: {
respectGitIgnore: boolean;
respectGeminiIgnore: boolean;
enableRecursiveFileSearch: boolean;
};
private fileDiscoveryService: FileDiscoveryService | null = null;
private gitService: GitService | undefined = undefined;
- private readonly checkpointing: boolean;
- private readonly proxy: string | undefined;
- private readonly cwd: string;
- private readonly bugCommand: BugCommandSettings | undefined;
- private readonly model: string;
- private readonly extensionContextFilePaths: string[];
- private readonly noBrowser: boolean;
- private readonly ideMode: boolean;
- private readonly ideClient: IdeClient | undefined;
+ private checkpointing: boolean;
+ private proxy: string | undefined;
+ private cwd: string;
+ private bugCommand: BugCommandSettings | undefined;
+ private model: string;
+ private extensionContextFilePaths: string[];
+ private noBrowser: boolean;
+ private ideMode: boolean;
+ private ideClient: IdeClient | undefined;
private modelSwitchedDuringSession: boolean = false;
- private readonly maxSessionTurns: number;
- private readonly listExtensions: boolean;
- private readonly _extensions: GeminiCLIExtension[];
- private readonly _blockedMcpServers: Array<{
+ private maxSessionTurns: number;
+ private listExtensions: boolean;
+ private _extensions: GeminiCLIExtension[];
+ private _blockedMcpServers: Array<{
name: string;
extensionName: string;
}>;
flashFallbackHandler?: FlashFallbackHandler;
private quotaErrorOccurred: boolean = false;
- private readonly summarizeToolOutput:
+ private summarizeToolOutput:
| Record<string, SummarizeToolOutputSettings>
| undefined;
- private readonly experimentalAcp: boolean = false;
+ private experimentalAcp: boolean = false;
+ private _params: ConfigParameters;
constructor(params: ConfigParameters) {
+ this._params = params;
this.sessionId = params.sessionId;
this.embeddingModel =
params.embeddingModel ?? DEFAULT_GEMINI_EMBEDDING_MODEL;
@@ -310,6 +312,68 @@ export class Config {
}
}
+ async refresh() {
+ // Re-run initialization logic.
+ await this.initialize();
+ // After re-initializing, the tool registry will be updated.
+ // We need to update the gemini client with the new tools.
+ await this.geminiClient.setTools();
+ }
+
+ update(params: ConfigParameters) {
+ this._params = params;
+ // Re-assign all properties from the new params.
+ this.sessionId = params.sessionId;
+ this.embeddingModel =
+ params.embeddingModel ?? DEFAULT_GEMINI_EMBEDDING_MODEL;
+ this.sandbox = params.sandbox;
+ this.targetDir = path.resolve(params.targetDir);
+ this.debugMode = params.debugMode;
+ this.question = params.question;
+ this.fullContext = params.fullContext ?? false;
+ this.coreTools = params.coreTools;
+ this.excludeTools = params.excludeTools;
+ this.toolDiscoveryCommand = params.toolDiscoveryCommand;
+ this.toolCallCommand = params.toolCallCommand;
+ this.mcpServerCommand = params.mcpServerCommand;
+ this.mcpServers = params.mcpServers;
+ this.userMemory = params.userMemory ?? '';
+ this.geminiMdFileCount = params.geminiMdFileCount ?? 0;
+ this.approvalMode = params.approvalMode ?? ApprovalMode.DEFAULT;
+ this.showMemoryUsage = params.showMemoryUsage ?? false;
+ this.accessibility = params.accessibility ?? {};
+ this.telemetrySettings = {
+ enabled: params.telemetry?.enabled ?? false,
+ target: params.telemetry?.target ?? DEFAULT_TELEMETRY_TARGET,
+ otlpEndpoint: params.telemetry?.otlpEndpoint ?? DEFAULT_OTLP_ENDPOINT,
+ logPrompts: params.telemetry?.logPrompts ?? true,
+ outfile: params.telemetry?.outfile,
+ };
+ this.usageStatisticsEnabled = params.usageStatisticsEnabled ?? true;
+ this.fileFiltering = {
+ respectGitIgnore: params.fileFiltering?.respectGitIgnore ?? true,
+ respectGeminiIgnore: params.fileFiltering?.respectGeminiIgnore ?? true,
+ enableRecursiveFileSearch:
+ params.fileFiltering?.enableRecursiveFileSearch ?? true,
+ };
+ this.checkpointing = params.checkpointing ?? false;
+ this.proxy = params.proxy;
+ this.cwd = params.cwd ?? process.cwd();
+ this.fileDiscoveryService = params.fileDiscoveryService ?? null;
+ this.bugCommand = params.bugCommand;
+ this.model = params.model;
+ this.extensionContextFilePaths = params.extensionContextFilePaths ?? [];
+ this.maxSessionTurns = params.maxSessionTurns ?? -1;
+ this.experimentalAcp = params.experimentalAcp ?? false;
+ this.listExtensions = params.listExtensions ?? false;
+ this._extensions = params.extensions ?? [];
+ this._blockedMcpServers = params.blockedMcpServers ?? [];
+ this.noBrowser = params.noBrowser ?? false;
+ this.summarizeToolOutput = params.summarizeToolOutput;
+ this.ideMode = params.ideMode ?? false;
+ this.ideClient = params.ideClient;
+ }
+
async initialize(): Promise<void> {
// Initialize centralized FileDiscoveryService
this.getFileService();