mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-04-27 21:44:25 -07:00
Add extension settings info to /extensions list (#14905)
This commit is contained in:
@@ -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>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user