Add extension settings info to /extensions list (#14905)

This commit is contained in:
christine betts
2025-12-30 16:09:48 -05:00
committed by GitHub
parent ec79fe1ab2
commit ec11b8afbf
4 changed files with 86 additions and 3 deletions
+29 -1
View File
@@ -44,6 +44,7 @@ import {
type GeminiCLIExtension, type GeminiCLIExtension,
type HookDefinition, type HookDefinition,
type HookEventName, type HookEventName,
type ResolvedExtensionSetting,
} from '@google/gemini-cli-core'; } from '@google/gemini-cli-core';
import { maybeRequestConsentOrFail } from './extensions/consent.js'; import { maybeRequestConsentOrFail } from './extensions/consent.js';
import { resolveEnvVarsInObject } from '../utils/envVarResolver.js'; import { resolveEnvVarsInObject } from '../utils/envVarResolver.js';
@@ -509,6 +510,24 @@ Would you like to attempt to install via "git clone" instead?`,
); );
config = resolveEnvVarsInObject(config, customEnv); config = resolveEnvVarsInObject(config, customEnv);
const resolvedSettings: ResolvedExtensionSetting[] = [];
if (config.settings) {
for (const setting of config.settings) {
const value = customEnv[setting.envVar];
resolvedSettings.push({
name: setting.name,
envVar: setting.envVar,
value:
value === undefined
? '[not set]'
: setting.sensitive
? '***'
: value,
sensitive: setting.sensitive ?? false,
});
}
}
if (config.mcpServers) { if (config.mcpServers) {
config.mcpServers = Object.fromEntries( config.mcpServers = Object.fromEntries(
Object.entries(config.mcpServers).map(([key, value]) => [ Object.entries(config.mcpServers).map(([key, value]) => [
@@ -532,7 +551,7 @@ Would you like to attempt to install via "git clone" instead?`,
}); });
} }
const extension = { const extension: GeminiCLIExtension = {
name: config.name, name: config.name,
version: config.version, version: config.version,
path: effectiveExtensionPath, path: effectiveExtensionPath,
@@ -546,6 +565,8 @@ Would you like to attempt to install via "git clone" instead?`,
this.workspaceDir, this.workspaceDir,
), ),
id: getExtensionId(config, installMetadata), id: getExtensionId(config, installMetadata),
settings: config.settings,
resolvedSettings,
}; };
this.loadedExtensions = [...this.loadedExtensions, extension]; this.loadedExtensions = [...this.loadedExtensions, extension];
@@ -700,6 +721,13 @@ Would you like to attempt to install via "git clone" instead?`,
output += `\n ${tool}`; output += `\n ${tool}`;
}); });
} }
const resolvedSettings = extension.resolvedSettings;
if (resolvedSettings && resolvedSettings.length > 0) {
output += `\n Settings:`;
resolvedSettings.forEach((setting) => {
output += `\n ${setting.name}: ${setting.value}`;
});
}
return output; return output;
} }
@@ -125,4 +125,33 @@ describe('<ExtensionsList />', () => {
unmount(); unmount();
}); });
} }
it('should render resolved settings for an extension', () => {
mockUIState(new Map());
const extensionWithSettings = {
...mockExtensions[0],
resolvedSettings: [
{
name: 'sensitiveApiKey',
value: '***',
envVar: 'API_KEY',
sensitive: true,
},
{
name: 'maxTokens',
value: '1000',
envVar: 'MAX_TOKENS',
sensitive: false,
},
],
};
const { lastFrame, unmount } = render(
<ExtensionsList extensions={[extensionWithSettings]} />,
);
const output = lastFrame();
expect(output).toContain('settings:');
expect(output).toContain('- sensitiveApiKey: ***');
expect(output).toContain('- maxTokens: 1000');
unmount();
});
}); });
@@ -23,7 +23,7 @@ export const ExtensionsList: React.FC<ExtensionsList> = ({ extensions }) => {
return ( return (
<Box flexDirection="column" marginTop={1} marginBottom={1}> <Box flexDirection="column" marginTop={1} marginBottom={1}>
<Text>Installed extensions:</Text> <Text>Installed extensions: </Text>
<Box flexDirection="column" paddingLeft={2}> <Box flexDirection="column" paddingLeft={2}>
{extensions.map((ext) => { {extensions.map((ext) => {
const state = extensionsUpdateState.get(ext.name); const state = extensionsUpdateState.get(ext.name);
@@ -59,12 +59,22 @@ export const ExtensionsList: React.FC<ExtensionsList> = ({ extensions }) => {
} }
return ( return (
<Box key={ext.name}> <Box key={ext.name} flexDirection="column" marginBottom={1}>
<Text> <Text>
<Text color="cyan">{`${ext.name} (v${ext.version})`}</Text> <Text color="cyan">{`${ext.name} (v${ext.version})`}</Text>
<Text color={activeColor}>{` - ${activeString}`}</Text> <Text color={activeColor}>{` - ${activeString}`}</Text>
{<Text color={stateColor}>{` (${stateText})`}</Text>} {<Text color={stateColor}>{` (${stateText})`}</Text>}
</Text> </Text>
{ext.resolvedSettings && ext.resolvedSettings.length > 0 && (
<Box flexDirection="column" paddingLeft={2}>
<Text>settings:</Text>
{ext.resolvedSettings.map((setting) => (
<Text key={setting.name}>
- {setting.name}: {setting.value}
</Text>
))}
</Box>
)}
</Box> </Box>
); );
})} })}
+16
View File
@@ -134,6 +134,20 @@ export interface CodebaseInvestigatorSettings {
model?: string; model?: string;
} }
export interface ExtensionSetting {
name: string;
description: string;
envVar: string;
sensitive?: boolean;
}
export interface ResolvedExtensionSetting {
name: string;
envVar: string;
value: string;
sensitive: boolean;
}
export interface IntrospectionAgentSettings { export interface IntrospectionAgentSettings {
enabled?: boolean; enabled?: boolean;
} }
@@ -155,6 +169,8 @@ export interface GeminiCLIExtension {
excludeTools?: string[]; excludeTools?: string[];
id: string; id: string;
hooks?: { [K in HookEventName]?: HookDefinition[] }; hooks?: { [K in HookEventName]?: HookDefinition[] };
settings?: ExtensionSetting[];
resolvedSettings?: ResolvedExtensionSetting[];
} }
export interface ExtensionInstallMetadata { export interface ExtensionInstallMetadata {