Skip to content

Commit 2bf6a75

Browse files
authored
Merge branch 'main' into main
2 parents 651eb6c + 0170791 commit 2bf6a75

6 files changed

Lines changed: 168 additions & 40 deletions

File tree

packages/cli/src/services/BuiltinCommandLoader.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import { statsCommand } from '../ui/commands/statsCommand.js';
2929
import { themeCommand } from '../ui/commands/themeCommand.js';
3030
import { toolsCommand } from '../ui/commands/toolsCommand.js';
3131
import { vimCommand } from '../ui/commands/vimCommand.js';
32+
import { configCommand } from '../ui/commands/configCommand.js';
3233

3334
/**
3435
* Loads the core, hard-coded slash commands that are an integral part
@@ -54,6 +55,7 @@ export class BuiltinCommandLoader implements ICommandLoader {
5455
compressCommand,
5556
copyCommand,
5657
corgiCommand,
58+
configCommand,
5759
docsCommand,
5860
editorCommand,
5961
extensionsCommand,

packages/cli/src/ui/App.tsx

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,12 @@ import { EditorSettingsDialog } from './components/EditorSettingsDialog.js';
3939
import { ShellConfirmationDialog } from './components/ShellConfirmationDialog.js';
4040
import { Colors } from './colors.js';
4141
import { Help } from './components/Help.js';
42-
import { loadHierarchicalGeminiMemory } from '../config/config.js';
43-
import { LoadedSettings } from '../config/settings.js';
42+
import {
43+
loadHierarchicalGeminiMemory,
44+
loadCliConfig,
45+
parseArguments,
46+
} from '../config/config.js';
47+
import { LoadedSettings, loadSettings } from '../config/settings.js';
4448
import { Tips } from './components/Tips.js';
4549
import { ConsolePatcher } from './utils/ConsolePatcher.js';
4650
import { registerCleanup } from '../utils/cleanup.js';
@@ -62,6 +66,7 @@ import {
6266
AuthType,
6367
type IdeContext,
6468
ideContext,
69+
sessionId,
6570
} from '@google/gemini-cli-core';
6671
import { validateAuthMethod } from '../config/auth.js';
6772
import { useLogger } from './hooks/useLogger.js';
@@ -89,6 +94,7 @@ import { OverflowProvider } from './contexts/OverflowContext.js';
8994
import { ShowMoreLines } from './components/ShowMoreLines.js';
9095
import { PrivacyNotice } from './privacy/PrivacyNotice.js';
9196
import { appEvents, AppEvent } from '../utils/events.js';
97+
import { loadExtensions } from '../config/extension.js';
9298

9399
const CTRL_EXIT_PROMPT_DURATION_MS = 1000;
94100

@@ -107,12 +113,14 @@ export const AppWrapper = (props: AppProps) => (
107113
</SessionStatsProvider>
108114
);
109115

110-
const App = ({ config, settings, startupWarnings = [], version }: AppProps) => {
116+
const App = (props: AppProps) => {
117+
const [config, setConfig] = useState<Config>(props.config);
118+
const [settings, setSettings] = useState<LoadedSettings>(props.settings);
111119
const isFocused = useFocus();
112120
useBracketedPaste();
113121
const [updateMessage, setUpdateMessage] = useState<string | null>(null);
114122
const { stdout } = useStdout();
115-
const nightly = version.includes('nightly');
123+
const nightly = props.version.includes('nightly');
116124

117125
useEffect(() => {
118126
checkForUpdates().then(setUpdateMessage);
@@ -307,6 +315,22 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => {
307315
}
308316
}, [config, addItem, settings.merged]);
309317

318+
const refreshConfig = useCallback(async () => {
319+
const newSettings = loadSettings(process.cwd());
320+
const newExtensions = loadExtensions(process.cwd());
321+
const argv = await parseArguments();
322+
const newConfig = await loadCliConfig(
323+
newSettings.merged,
324+
newExtensions,
325+
sessionId,
326+
argv,
327+
);
328+
await newConfig.initialize();
329+
setConfig(newConfig);
330+
setSettings(newSettings);
331+
setGeminiMdFileCount(newConfig.getGeminiMdFileCount());
332+
}, []);
333+
310334
// Watch for model changes (e.g., from Flash fallback)
311335
useEffect(() => {
312336
const checkModelChange = () => {
@@ -474,6 +498,7 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => {
474498
openPrivacyNotice,
475499
toggleVimEnabled,
476500
setIsProcessing,
501+
refreshConfig,
477502
);
478503

479504
const {
@@ -777,7 +802,7 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => {
777802
{!settings.merged.hideBanner && (
778803
<Header
779804
terminalWidth={terminalWidth}
780-
version={version}
805+
version={props.version}
781806
nightly={nightly}
782807
/>
783808
)}
@@ -821,15 +846,15 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => {
821846
{showHelp && <Help commands={slashCommands} />}
822847

823848
<Box flexDirection="column" ref={mainControlsRef}>
824-
{startupWarnings.length > 0 && (
849+
{props.startupWarnings && props.startupWarnings.length > 0 && (
825850
<Box
826851
borderStyle="round"
827852
borderColor={Colors.AccentYellow}
828853
paddingX={1}
829854
marginY={1}
830855
flexDirection="column"
831856
>
832-
{startupWarnings.map((warning, index) => (
857+
{props.startupWarnings.map((warning, index) => (
833858
<Text key={index} color={Colors.AccentYellow}>
834859
{warning}
835860
</Text>
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/**
2+
* @license
3+
* Copyright 2025 Google LLC
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
import {
8+
CommandKind,
9+
SlashCommand,
10+
SlashCommandActionReturn,
11+
} from './types.js';
12+
13+
export const configCommand: SlashCommand = {
14+
name: 'config',
15+
description: 'Commands for interacting with the CLI configuration.',
16+
kind: CommandKind.BUILT_IN,
17+
subCommands: [
18+
{
19+
name: 'refresh',
20+
description: 'Reload settings and extensions from the filesystem.',
21+
kind: CommandKind.BUILT_IN,
22+
action: async (context): Promise<SlashCommandActionReturn> => {
23+
await context.ui.refreshConfig();
24+
return {
25+
type: 'message',
26+
messageType: 'info',
27+
content:
28+
'Configuration, extensions, memory, and tools have been refreshed.',
29+
};
30+
},
31+
},
32+
],
33+
};

packages/cli/src/ui/commands/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ export interface CommandContext {
5959
/** Toggles a special display mode. */
6060
toggleCorgiMode: () => void;
6161
toggleVimEnabled: () => Promise<boolean>;
62+
refreshConfig: () => Promise<void>;
6263
};
6364
// Session-specific data
6465
session: {

packages/cli/src/ui/hooks/slashCommandProcessor.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ export const useSlashCommandProcessor = (
5050
openPrivacyNotice: () => void,
5151
toggleVimEnabled: () => Promise<boolean>,
5252
setIsProcessing: (isProcessing: boolean) => void,
53+
refreshConfig: () => Promise<void>,
5354
) => {
5455
const session = useSessionStats();
5556
const [commands, setCommands] = useState<readonly SlashCommand[]>([]);
@@ -158,6 +159,7 @@ export const useSlashCommandProcessor = (
158159
setPendingItem: setPendingCompressionItem,
159160
toggleCorgiMode,
160161
toggleVimEnabled,
162+
refreshConfig,
161163
},
162164
session: {
163165
stats: session.stats,
@@ -180,6 +182,7 @@ export const useSlashCommandProcessor = (
180182
toggleCorgiMode,
181183
toggleVimEnabled,
182184
sessionShellAllowlist,
185+
refreshConfig,
183186
],
184187
);
185188

packages/core/src/config/config.ts

Lines changed: 97 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -188,60 +188,62 @@ export interface ConfigParameters {
188188
export class Config {
189189
private toolRegistry!: ToolRegistry;
190190
private promptRegistry!: PromptRegistry;
191-
private readonly sessionId: string;
191+
private sessionId: string;
192192
private contentGeneratorConfig!: ContentGeneratorConfig;
193-
private readonly embeddingModel: string;
194-
private readonly sandbox: SandboxConfig | undefined;
195-
private readonly targetDir: string;
196-
private readonly debugMode: boolean;
197-
private readonly question: string | undefined;
198-
private readonly fullContext: boolean;
199-
private readonly coreTools: string[] | undefined;
200-
private readonly excludeTools: string[] | undefined;
201-
private readonly toolDiscoveryCommand: string | undefined;
202-
private readonly toolCallCommand: string | undefined;
203-
private readonly mcpServerCommand: string | undefined;
204-
private readonly mcpServers: Record<string, MCPServerConfig> | undefined;
193+
private embeddingModel: string;
194+
private sandbox: SandboxConfig | undefined;
195+
private targetDir: string;
196+
private debugMode: boolean;
197+
private question: string | undefined;
198+
private fullContext: boolean;
199+
private coreTools: string[] | undefined;
200+
private excludeTools: string[] | undefined;
201+
private toolDiscoveryCommand: string | undefined;
202+
private toolCallCommand: string | undefined;
203+
private mcpServerCommand: string | undefined;
204+
private mcpServers: Record<string, MCPServerConfig> | undefined;
205205
private userMemory: string;
206206
private geminiMdFileCount: number;
207207
private approvalMode: ApprovalMode;
208-
private readonly showMemoryUsage: boolean;
209-
private readonly accessibility: AccessibilitySettings;
210-
private readonly telemetrySettings: TelemetrySettings;
211-
private readonly usageStatisticsEnabled: boolean;
208+
private showMemoryUsage: boolean;
209+
private accessibility: AccessibilitySettings;
210+
private telemetrySettings: TelemetrySettings;
211+
private usageStatisticsEnabled: boolean;
212212
private geminiClient!: GeminiClient;
213-
private readonly fileFiltering: {
213+
private fileFiltering: {
214214
respectGitIgnore: boolean;
215215
respectGeminiIgnore: boolean;
216216
enableRecursiveFileSearch: boolean;
217217
};
218218
private fileDiscoveryService: FileDiscoveryService | null = null;
219219
private gitService: GitService | undefined = undefined;
220-
private readonly checkpointing: boolean;
221-
private readonly proxy: string | undefined;
222-
private readonly cwd: string;
223-
private readonly bugCommand: BugCommandSettings | undefined;
224-
private readonly model: string;
225-
private readonly extensionContextFilePaths: string[];
226-
private readonly noBrowser: boolean;
227-
private readonly ideMode: boolean;
228-
private readonly ideClient: IdeClient | undefined;
220+
private checkpointing: boolean;
221+
private proxy: string | undefined;
222+
private cwd: string;
223+
private bugCommand: BugCommandSettings | undefined;
224+
private model: string;
225+
private extensionContextFilePaths: string[];
226+
private noBrowser: boolean;
227+
private ideMode: boolean;
228+
private ideClient: IdeClient | undefined;
229229
private modelSwitchedDuringSession: boolean = false;
230-
private readonly maxSessionTurns: number;
231-
private readonly listExtensions: boolean;
232-
private readonly _extensions: GeminiCLIExtension[];
233-
private readonly _blockedMcpServers: Array<{
230+
private maxSessionTurns: number;
231+
private listExtensions: boolean;
232+
private _extensions: GeminiCLIExtension[];
233+
private _blockedMcpServers: Array<{
234234
name: string;
235235
extensionName: string;
236236
}>;
237237
flashFallbackHandler?: FlashFallbackHandler;
238238
private quotaErrorOccurred: boolean = false;
239-
private readonly summarizeToolOutput:
239+
private summarizeToolOutput:
240240
| Record<string, SummarizeToolOutputSettings>
241241
| undefined;
242-
private readonly experimentalAcp: boolean = false;
242+
private experimentalAcp: boolean = false;
243+
private _params: ConfigParameters;
243244

244245
constructor(params: ConfigParameters) {
246+
this._params = params;
245247
this.sessionId = params.sessionId;
246248
this.embeddingModel =
247249
params.embeddingModel ?? DEFAULT_GEMINI_EMBEDDING_MODEL;
@@ -310,6 +312,68 @@ export class Config {
310312
}
311313
}
312314

315+
async refresh() {
316+
// Re-run initialization logic.
317+
await this.initialize();
318+
// After re-initializing, the tool registry will be updated.
319+
// We need to update the gemini client with the new tools.
320+
await this.geminiClient.setTools();
321+
}
322+
323+
update(params: ConfigParameters) {
324+
this._params = params;
325+
// Re-assign all properties from the new params.
326+
this.sessionId = params.sessionId;
327+
this.embeddingModel =
328+
params.embeddingModel ?? DEFAULT_GEMINI_EMBEDDING_MODEL;
329+
this.sandbox = params.sandbox;
330+
this.targetDir = path.resolve(params.targetDir);
331+
this.debugMode = params.debugMode;
332+
this.question = params.question;
333+
this.fullContext = params.fullContext ?? false;
334+
this.coreTools = params.coreTools;
335+
this.excludeTools = params.excludeTools;
336+
this.toolDiscoveryCommand = params.toolDiscoveryCommand;
337+
this.toolCallCommand = params.toolCallCommand;
338+
this.mcpServerCommand = params.mcpServerCommand;
339+
this.mcpServers = params.mcpServers;
340+
this.userMemory = params.userMemory ?? '';
341+
this.geminiMdFileCount = params.geminiMdFileCount ?? 0;
342+
this.approvalMode = params.approvalMode ?? ApprovalMode.DEFAULT;
343+
this.showMemoryUsage = params.showMemoryUsage ?? false;
344+
this.accessibility = params.accessibility ?? {};
345+
this.telemetrySettings = {
346+
enabled: params.telemetry?.enabled ?? false,
347+
target: params.telemetry?.target ?? DEFAULT_TELEMETRY_TARGET,
348+
otlpEndpoint: params.telemetry?.otlpEndpoint ?? DEFAULT_OTLP_ENDPOINT,
349+
logPrompts: params.telemetry?.logPrompts ?? true,
350+
outfile: params.telemetry?.outfile,
351+
};
352+
this.usageStatisticsEnabled = params.usageStatisticsEnabled ?? true;
353+
this.fileFiltering = {
354+
respectGitIgnore: params.fileFiltering?.respectGitIgnore ?? true,
355+
respectGeminiIgnore: params.fileFiltering?.respectGeminiIgnore ?? true,
356+
enableRecursiveFileSearch:
357+
params.fileFiltering?.enableRecursiveFileSearch ?? true,
358+
};
359+
this.checkpointing = params.checkpointing ?? false;
360+
this.proxy = params.proxy;
361+
this.cwd = params.cwd ?? process.cwd();
362+
this.fileDiscoveryService = params.fileDiscoveryService ?? null;
363+
this.bugCommand = params.bugCommand;
364+
this.model = params.model;
365+
this.extensionContextFilePaths = params.extensionContextFilePaths ?? [];
366+
this.maxSessionTurns = params.maxSessionTurns ?? -1;
367+
this.experimentalAcp = params.experimentalAcp ?? false;
368+
this.listExtensions = params.listExtensions ?? false;
369+
this._extensions = params.extensions ?? [];
370+
this._blockedMcpServers = params.blockedMcpServers ?? [];
371+
this.noBrowser = params.noBrowser ?? false;
372+
this.summarizeToolOutput = params.summarizeToolOutput;
373+
this.ideMode = params.ideMode ?? false;
374+
this.ideClient = params.ideClient;
375+
}
376+
313377
async initialize(): Promise<void> {
314378
// Initialize centralized FileDiscoveryService
315379
this.getFileService();

0 commit comments

Comments
 (0)