Add commands for listing and updating per-extension settings (#12664)

This commit is contained in:
christine betts
2025-12-03 19:16:16 -05:00
committed by GitHub
parent 470f3b057f
commit e0a2227faf
6 changed files with 365 additions and 9 deletions

View File

@@ -14,6 +14,7 @@ import { enableCommand } from './extensions/enable.js';
import { linkCommand } from './extensions/link.js';
import { newCommand } from './extensions/new.js';
import { validateCommand } from './extensions/validate.js';
import { settingsCommand } from './extensions/settings.js';
import { initializeOutputListenersAndFlush } from '../gemini.js';
export const extensionsCommand: CommandModule = {
@@ -32,6 +33,7 @@ export const extensionsCommand: CommandModule = {
.command(linkCommand)
.command(newCommand)
.command(validateCommand)
.command(settingsCommand)
.demandCommand(1, 'You need at least one command before continuing.')
.version(false),
handler: () => {

View File

@@ -0,0 +1,131 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import type { CommandModule } from 'yargs';
import {
getEnvContents,
updateSetting,
promptForSetting,
} from '../../config/extensions/extensionSettings.js';
import { getExtensionAndManager } from './utils.js';
import { debugLogger } from '@google/gemini-cli-core';
import { exitCli } from '../utils.js';
// --- SET COMMAND ---
interface SetArgs {
name: string;
setting: string;
}
const setCommand: CommandModule<object, SetArgs> = {
command: 'set <name> <setting>',
describe: 'Set a specific setting for an extension.',
builder: (yargs) =>
yargs
.positional('name', {
describe: 'Name of the extension to configure.',
type: 'string',
demandOption: true,
})
.positional('setting', {
describe: 'The setting to configure (name or env var).',
type: 'string',
demandOption: true,
}),
handler: async (args) => {
const { name, setting } = args;
const { extension, extensionManager } = await getExtensionAndManager(name);
if (!extension || !extensionManager) {
return;
}
const extensionConfig = extensionManager.loadExtensionConfig(
extension.path,
);
if (!extensionConfig) {
debugLogger.error(
`Could not find configuration for extension "${name}".`,
);
return;
}
await updateSetting(
extensionConfig,
extension.id,
setting,
promptForSetting,
);
await exitCli();
},
};
// --- LIST COMMAND ---
interface ListArgs {
name: string;
}
const listCommand: CommandModule<object, ListArgs> = {
command: 'list <name>',
describe: 'List all settings for an extension.',
builder: (yargs) =>
yargs.positional('name', {
describe: 'Name of the extension.',
type: 'string',
demandOption: true,
}),
handler: async (args) => {
const { name } = args;
const { extension, extensionManager } = await getExtensionAndManager(name);
if (!extension || !extensionManager) {
return;
}
const extensionConfig = extensionManager.loadExtensionConfig(
extension.path,
);
if (
!extensionConfig ||
!extensionConfig.settings ||
extensionConfig.settings.length === 0
) {
debugLogger.log(`Extension "${name}" has no settings to configure.`);
return;
}
const currentSettings = await getEnvContents(extensionConfig, extension.id);
debugLogger.log(`Settings for "${name}":`);
for (const setting of extensionConfig.settings) {
const value = currentSettings[setting.envVar];
let displayValue: string;
if (value === undefined) {
displayValue = '[not set]';
} else if (setting.sensitive) {
displayValue = '[value stored in keychain]';
} else {
displayValue = value;
}
debugLogger.log(`
- ${setting.name} (${setting.envVar})`);
debugLogger.log(` Description: ${setting.description}`);
debugLogger.log(` Value: ${displayValue}`);
}
await exitCli();
},
};
// --- SETTINGS COMMAND ---
export const settingsCommand: CommandModule = {
command: 'settings <command>',
describe: 'Manage extension settings.',
builder: (yargs) =>
yargs
.command(setCommand)
.command(listCommand)
.demandCommand(1, 'You need to specify a command (set or list).')
.version(false),
handler: () => {
// This handler is not called when a subcommand is provided.
// Yargs will show the help menu.
},
};

View File

@@ -0,0 +1,32 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { ExtensionManager } from '../../config/extension-manager.js';
import { promptForSetting } from '../../config/extensions/extensionSettings.js';
import { loadSettings } from '../../config/settings.js';
import { requestConsentNonInteractive } from '../../config/extensions/consent.js';
import { debugLogger } from '@google/gemini-cli-core';
export async function getExtensionAndManager(name: string) {
const workspaceDir = process.cwd();
const extensionManager = new ExtensionManager({
workspaceDir,
requestConsent: requestConsentNonInteractive,
requestSetting: promptForSetting,
settings: loadSettings(workspaceDir).merged,
});
await extensionManager.loadExtensions();
const extension = extensionManager
.getExtensions()
.find((ext) => ext.name === name);
if (!extension) {
debugLogger.error(`Extension "${name}" is not installed.`);
return { extension: null, extensionManager: null };
}
return { extension, extensionManager };
}