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

View File

@@ -44,6 +44,7 @@ import {
type GeminiCLIExtension,
type HookDefinition,
type HookEventName,
type ResolvedExtensionSetting,
} from '@google/gemini-cli-core';
import { maybeRequestConsentOrFail } from './extensions/consent.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);
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) {
config.mcpServers = Object.fromEntries(
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,
version: config.version,
path: effectiveExtensionPath,
@@ -546,6 +565,8 @@ Would you like to attempt to install via "git clone" instead?`,
this.workspaceDir,
),
id: getExtensionId(config, installMetadata),
settings: config.settings,
resolvedSettings,
};
this.loadedExtensions = [...this.loadedExtensions, extension];
@@ -700,6 +721,13 @@ Would you like to attempt to install via "git clone" instead?`,
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;
}

View File

@@ -125,4 +125,33 @@ describe('<ExtensionsList />', () => {
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();
});
});

View File

@@ -23,7 +23,7 @@ export const ExtensionsList: React.FC<ExtensionsList> = ({ extensions }) => {
return (
<Box flexDirection="column" marginTop={1} marginBottom={1}>
<Text>Installed extensions:</Text>
<Text>Installed extensions: </Text>
<Box flexDirection="column" paddingLeft={2}>
{extensions.map((ext) => {
const state = extensionsUpdateState.get(ext.name);
@@ -59,12 +59,22 @@ export const ExtensionsList: React.FC<ExtensionsList> = ({ extensions }) => {
}
return (
<Box key={ext.name}>
<Box key={ext.name} flexDirection="column" marginBottom={1}>
<Text>
<Text color="cyan">{`${ext.name} (v${ext.version})`}</Text>
<Text color={activeColor}>{` - ${activeString}`}</Text>
{<Text color={stateColor}>{` (${stateText})`}</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>
);
})}

View File

@@ -134,6 +134,20 @@ export interface CodebaseInvestigatorSettings {
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 {
enabled?: boolean;
}
@@ -155,6 +169,8 @@ export interface GeminiCLIExtension {
excludeTools?: string[];
id: string;
hooks?: { [K in HookEventName]?: HookDefinition[] };
settings?: ExtensionSetting[];
resolvedSettings?: ResolvedExtensionSetting[];
}
export interface ExtensionInstallMetadata {