diff --git a/packages/cli/src/commands/extensions/list.test.ts b/packages/cli/src/commands/extensions/list.test.ts index f0f0168f79..e72427d5d2 100644 --- a/packages/cli/src/commands/extensions/list.test.ts +++ b/packages/cli/src/commands/extensions/list.test.ts @@ -9,7 +9,9 @@ import { coreEvents } from '@google/gemini-cli-core'; import { handleList, listCommand } from './list.js'; import { ExtensionManager } from '../../config/extension-manager.js'; import { loadSettings, type LoadedSettings } from '../../config/settings.js'; +import { loadCliConfig, type CliArgs } from '../../config/config.js'; import { getErrorMessage } from '../../utils/errors.js'; +import type { Config } from '@google/gemini-cli-core'; vi.mock('@google/gemini-cli-core', async (importOriginal) => { const { mockCoreDebugLogger } = await import( @@ -25,6 +27,7 @@ vi.mock('@google/gemini-cli-core', async (importOriginal) => { vi.mock('../../config/extension-manager.js'); vi.mock('../../config/settings.js'); +vi.mock('../../config/config.js'); vi.mock('../../utils/errors.js'); vi.mock('../../config/extensions/consent.js', () => ({ requestConsentNonInteractive: vi.fn(), @@ -40,6 +43,7 @@ describe('extensions list command', () => { const mockLoadSettings = vi.mocked(loadSettings); const mockGetErrorMessage = vi.mocked(getErrorMessage); const mockExtensionManager = vi.mocked(ExtensionManager); + const mockLoadCliConfig = vi.mocked(loadCliConfig); beforeEach(async () => { vi.clearAllMocks(); @@ -55,10 +59,17 @@ describe('extensions list command', () => { describe('handleList', () => { it('should log a message if no extensions are installed', async () => { const mockCwd = vi.spyOn(process, 'cwd').mockReturnValue('/test/dir'); + const mockConfig = { + initialize: vi.fn().mockResolvedValue(undefined), + getExtensionLoader: vi + .fn() + .mockReturnValue(mockExtensionManager.prototype), + }; + mockLoadCliConfig.mockResolvedValue(mockConfig as unknown as Config); mockExtensionManager.prototype.loadExtensions = vi .fn() .mockResolvedValue([]); - await handleList(); + await handleList({} as unknown as CliArgs); expect(coreEvents.emitConsoleLog).toHaveBeenCalledWith( 'log', @@ -69,10 +80,17 @@ describe('extensions list command', () => { it('should output empty JSON array if no extensions are installed and output-format is json', async () => { const mockCwd = vi.spyOn(process, 'cwd').mockReturnValue('/test/dir'); + const mockConfig = { + initialize: vi.fn().mockResolvedValue(undefined), + getExtensionLoader: vi + .fn() + .mockReturnValue(mockExtensionManager.prototype), + }; + mockLoadCliConfig.mockResolvedValue(mockConfig as unknown as Config); mockExtensionManager.prototype.loadExtensions = vi .fn() .mockResolvedValue([]); - await handleList({ outputFormat: 'json' }); + await handleList({} as unknown as CliArgs, { outputFormat: 'json' }); expect(coreEvents.emitConsoleLog).toHaveBeenCalledWith('log', '[]'); mockCwd.mockRestore(); @@ -80,6 +98,14 @@ describe('extensions list command', () => { it('should list all installed extensions', async () => { const mockCwd = vi.spyOn(process, 'cwd').mockReturnValue('/test/dir'); + const mockConfig = { + initialize: vi.fn().mockResolvedValue(undefined), + getExtensionLoader: vi + .fn() + .mockReturnValue(mockExtensionManager.prototype), + }; + mockLoadCliConfig.mockResolvedValue(mockConfig as unknown as Config); + const extensions = [ { name: 'ext1', version: '1.0.0' }, { name: 'ext2', version: '2.0.0' }, @@ -90,7 +116,7 @@ describe('extensions list command', () => { mockExtensionManager.prototype.toOutputString = vi.fn( (ext) => `${ext.name}@${ext.version}`, ); - await handleList(); + await handleList({} as unknown as CliArgs); expect(coreEvents.emitConsoleLog).toHaveBeenCalledWith( 'log', @@ -101,6 +127,14 @@ describe('extensions list command', () => { it('should list all installed extensions in JSON format', async () => { const mockCwd = vi.spyOn(process, 'cwd').mockReturnValue('/test/dir'); + const mockConfig = { + initialize: vi.fn().mockResolvedValue(undefined), + getExtensionLoader: vi + .fn() + .mockReturnValue(mockExtensionManager.prototype), + }; + mockLoadCliConfig.mockResolvedValue(mockConfig as unknown as Config); + const extensions = [ { name: 'ext1', version: '1.0.0' }, { name: 'ext2', version: '2.0.0' }, @@ -108,7 +142,7 @@ describe('extensions list command', () => { mockExtensionManager.prototype.loadExtensions = vi .fn() .mockResolvedValue(extensions); - await handleList({ outputFormat: 'json' }); + await handleList({} as unknown as CliArgs, { outputFormat: 'json' }); expect(coreEvents.emitConsoleLog).toHaveBeenCalledWith( 'log', @@ -124,12 +158,11 @@ describe('extensions list command', () => { code?: string | number | null | undefined, ) => never); const error = new Error('List failed'); - mockExtensionManager.prototype.loadExtensions = vi - .fn() - .mockRejectedValue(error); + mockLoadCliConfig.mockRejectedValue(error); mockGetErrorMessage.mockReturnValue('List failed message'); - await handleList(); + + await handleList({} as unknown as CliArgs); expect(coreEvents.emitConsoleLog).toHaveBeenCalledWith( 'error', @@ -167,6 +200,13 @@ describe('extensions list command', () => { }); it('handler should call handleList with parsed arguments', async () => { + const mockConfig = { + initialize: vi.fn().mockResolvedValue(undefined), + getExtensionLoader: vi + .fn() + .mockReturnValue(mockExtensionManager.prototype), + }; + mockLoadCliConfig.mockResolvedValue(mockConfig as unknown as Config); mockExtensionManager.prototype.loadExtensions = vi .fn() .mockResolvedValue([]); @@ -177,7 +217,7 @@ describe('extensions list command', () => { )({ 'output-format': 'json', }); - expect(mockExtensionManager.prototype.loadExtensions).toHaveBeenCalled(); + expect(mockLoadCliConfig).toHaveBeenCalled(); }); }); }); diff --git a/packages/cli/src/commands/extensions/list.ts b/packages/cli/src/commands/extensions/list.ts index 9b4789ca55..167e87dee7 100644 --- a/packages/cli/src/commands/extensions/list.ts +++ b/packages/cli/src/commands/extensions/list.ts @@ -7,21 +7,29 @@ import type { CommandModule } from 'yargs'; import { getErrorMessage } from '../../utils/errors.js'; import { debugLogger } from '@google/gemini-cli-core'; -import { ExtensionManager } from '../../config/extension-manager.js'; -import { requestConsentNonInteractive } from '../../config/extensions/consent.js'; +import { loadCliConfig, type CliArgs } from '../../config/config.js'; import { loadSettings } from '../../config/settings.js'; -import { promptForSetting } from '../../config/extensions/extensionSettings.js'; +import type { ExtensionManager } from '../../config/extension-manager.js'; import { exitCli } from '../utils.js'; - -export async function handleList(options?: { outputFormat?: 'text' | 'json' }) { +export async function handleList( + argv: CliArgs, + options?: { outputFormat?: 'text' | 'json' }, +) { try { const workspaceDir = process.cwd(); - const extensionManager = new ExtensionManager({ - workspaceDir, - requestConsent: requestConsentNonInteractive, - requestSetting: promptForSetting, - settings: loadSettings(workspaceDir).merged, - }); + const settings = loadSettings(workspaceDir); + const config = await loadCliConfig( + settings.merged, + 'extensions-list-session', + argv, + { cwd: workspaceDir }, + ); + + // Initialize to trigger extension loading (and profile filtering) + await config.initialize(); + + // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion + const extensionManager = config.getExtensionLoader() as ExtensionManager; const extensions = await extensionManager.loadExtensions(); if (extensions.length === 0) { if (options?.outputFormat === 'json') { @@ -61,7 +69,8 @@ export const listCommand: CommandModule = { default: 'text', }), handler: async (argv) => { - await handleList({ + // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion + await handleList(argv as unknown as CliArgs, { // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion outputFormat: argv['output-format'] as 'text' | 'json', }); diff --git a/packages/cli/src/commands/mcp/enableDisable.ts b/packages/cli/src/commands/mcp/enableDisable.ts index b47e259eca..33be54a81e 100644 --- a/packages/cli/src/commands/mcp/enableDisable.ts +++ b/packages/cli/src/commands/mcp/enableDisable.ts @@ -14,30 +14,42 @@ import { import { loadSettings } from '../../config/settings.js'; import { exitCli } from '../utils.js'; import { getMcpServersFromConfig } from './list.js'; +import { loadCliConfig, type CliArgs } from '../../config/config.js'; +import type { ExtensionManager } from '../../config/extension-manager.js'; const GREEN = '\x1b[32m'; const YELLOW = '\x1b[33m'; const RED = '\x1b[31m'; const RESET = '\x1b[0m'; -interface Args { +interface Args extends CliArgs { name: string; session?: boolean; } -async function handleEnable(args: Args): Promise { +async function handleEnable(argv: Args): Promise { const manager = McpServerEnablementManager.getInstance(); - const name = normalizeServerId(args.name); + const name = normalizeServerId(argv.name); // Check settings blocks const settings = loadSettings(); + const config = await loadCliConfig(settings.merged, 'mcp-enable', argv, { + cwd: process.cwd(), + }); + await config.initialize(); + // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion + const extensionManager = config.getExtensionLoader() as ExtensionManager; + // Get all servers including extensions - const servers = await getMcpServersFromConfig(); + const { mcpServers: servers } = await getMcpServersFromConfig( + settings.merged, + extensionManager, + ); const normalizedServerNames = Object.keys(servers).map(normalizeServerId); if (!normalizedServerNames.includes(name)) { debugLogger.log( - `${RED}Error:${RESET} Server '${args.name}' not found. Use 'gemini mcp' to see available servers.`, + `${RED}Error:${RESET} Server '${argv.name}' not found. Use 'gemini mcp' to see available servers.`, ); return; } @@ -56,7 +68,7 @@ async function handleEnable(args: Args): Promise { return; } - if (args.session) { + if (argv.session) { manager.clearSessionDisable(name); debugLogger.log(`${GREEN}✓${RESET} Session disable cleared for '${name}'.`); } else { @@ -71,21 +83,34 @@ async function handleEnable(args: Args): Promise { } } -async function handleDisable(args: Args): Promise { +async function handleDisable(argv: Args): Promise { const manager = McpServerEnablementManager.getInstance(); - const name = normalizeServerId(args.name); + const name = normalizeServerId(argv.name); + + // Check settings blocks + const settings = loadSettings(); + + const config = await loadCliConfig(settings.merged, 'mcp-disable', argv, { + cwd: process.cwd(), + }); + await config.initialize(); + // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion + const extensionManager = config.getExtensionLoader() as ExtensionManager; // Get all servers including extensions - const servers = await getMcpServersFromConfig(); + const { mcpServers: servers } = await getMcpServersFromConfig( + settings.merged, + extensionManager, + ); const normalizedServerNames = Object.keys(servers).map(normalizeServerId); if (!normalizedServerNames.includes(name)) { debugLogger.log( - `${RED}Error:${RESET} Server '${args.name}' not found. Use 'gemini mcp' to see available servers.`, + `${RED}Error:${RESET} Server '${argv.name}' not found. Use 'gemini mcp' to see available servers.`, ); return; } - if (args.session) { + if (argv.session) { manager.disableForSession(name); debugLogger.log( `${GREEN}✓${RESET} MCP server '${name}' disabled for this session.`, @@ -100,6 +125,7 @@ export const enableCommand: CommandModule = { command: 'enable ', describe: 'Enable an MCP server', builder: (yargs) => + /* eslint-disable @typescript-eslint/no-unsafe-type-assertion, @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-return */ yargs .positional('name', { describe: 'MCP server name to enable', @@ -110,7 +136,8 @@ export const enableCommand: CommandModule = { describe: 'Clear session-only disable', type: 'boolean', default: false, - }), + }) as any, + /* eslint-enable @typescript-eslint/no-unsafe-type-assertion, @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-return */ handler: async (argv) => { await handleEnable(argv as Args); await exitCli(); @@ -121,6 +148,7 @@ export const disableCommand: CommandModule = { command: 'disable ', describe: 'Disable an MCP server', builder: (yargs) => + /* eslint-disable @typescript-eslint/no-unsafe-type-assertion, @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-return */ yargs .positional('name', { describe: 'MCP server name to disable', @@ -131,7 +159,8 @@ export const disableCommand: CommandModule = { describe: 'Disable for current session only', type: 'boolean', default: false, - }), + }) as any, + /* eslint-enable @typescript-eslint/no-unsafe-type-assertion, @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-return */ handler: async (argv) => { await handleDisable(argv as Args); await exitCli(); diff --git a/packages/cli/src/commands/mcp/list.test.ts b/packages/cli/src/commands/mcp/list.test.ts index 54534961dd..2e679d0bf5 100644 --- a/packages/cli/src/commands/mcp/list.test.ts +++ b/packages/cli/src/commands/mcp/list.test.ts @@ -24,6 +24,8 @@ import { Client } from '@modelcontextprotocol/sdk/client/index.js'; import { ExtensionStorage } from '../../config/extensions/storage.js'; import { ExtensionManager } from '../../config/extension-manager.js'; import { McpServerEnablementManager } from '../../config/mcp/index.js'; +import { loadCliConfig, type CliArgs } from '../../config/config.js'; +import type { Config } from '@google/gemini-cli-core'; vi.mock('../../config/settings.js', async (importOriginal) => { const actual = @@ -33,6 +35,7 @@ vi.mock('../../config/settings.js', async (importOriginal) => { loadSettings: vi.fn(), }; }); +vi.mock('../../config/config.js'); vi.mock('../../config/extensions/storage.js', () => ({ ExtensionStorage: { getUserExtensionsDir: vi.fn(), @@ -62,6 +65,7 @@ vi.mock('@google/gemini-cli-core', async (importOriginal) => { { getGlobalSettingsPath: () => '/tmp/gemini/settings.json', getGlobalGeminiDir: () => '/tmp/gemini', + getGlobalTempDir: () => '/tmp/gemini/tmp', }, ), GEMINI_DIR: '.gemini', @@ -138,7 +142,13 @@ describe('mcp list command', () => { merged: { ...defaultMergedSettings, mcpServers: {} }, }); - await listMcpServers(); + const mockConfig = { + initialize: vi.fn().mockResolvedValue(undefined), + getExtensionLoader: vi.fn().mockReturnValue(mockExtensionManager), + }; + (loadCliConfig as Mock).mockResolvedValue(mockConfig as unknown as Config); + + await listMcpServers({} as unknown as CliArgs); expect(debugLogger.log).toHaveBeenCalledWith('No MCP servers configured.'); }); @@ -162,10 +172,16 @@ describe('mcp list command', () => { isTrusted: true, }); + const mockConfig = { + initialize: vi.fn().mockResolvedValue(undefined), + getExtensionLoader: vi.fn().mockReturnValue(mockExtensionManager), + }; + (loadCliConfig as Mock).mockResolvedValue(mockConfig as unknown as Config); + mockClient.connect.mockResolvedValue(undefined); mockClient.ping.mockResolvedValue(undefined); - await listMcpServers(); + await listMcpServers({} as unknown as CliArgs); expect(debugLogger.log).toHaveBeenCalledWith('Configured MCP servers:\n'); expect(debugLogger.log).toHaveBeenCalledWith( @@ -206,9 +222,15 @@ describe('mcp list command', () => { }, }); + const mockConfig = { + initialize: vi.fn().mockResolvedValue(undefined), + getExtensionLoader: vi.fn().mockReturnValue(mockExtensionManager), + }; + (loadCliConfig as Mock).mockResolvedValue(mockConfig as unknown as Config); + mockClient.connect.mockRejectedValue(new Error('Connection failed')); - await listMcpServers(); + await listMcpServers({} as unknown as CliArgs); expect(debugLogger.log).toHaveBeenCalledWith( expect.stringContaining( @@ -236,10 +258,16 @@ describe('mcp list command', () => { }, ]); + const mockConfig = { + initialize: vi.fn().mockResolvedValue(undefined), + getExtensionLoader: vi.fn().mockReturnValue(mockExtensionManager), + }; + (loadCliConfig as Mock).mockResolvedValue(mockConfig as unknown as Config); + mockClient.connect.mockResolvedValue(undefined); mockClient.ping.mockResolvedValue(undefined); - await listMcpServers(); + await listMcpServers({} as unknown as CliArgs); expect(debugLogger.log).toHaveBeenCalledWith( expect.stringContaining( @@ -276,13 +304,22 @@ describe('mcp list command', () => { merged: settingsWithAllowlist, }); + const mockConfig = { + initialize: vi.fn().mockResolvedValue(undefined), + getExtensionLoader: vi.fn().mockReturnValue(mockExtensionManager), + }; + (loadCliConfig as Mock).mockResolvedValue(mockConfig as unknown as Config); + mockClient.connect.mockResolvedValue(undefined); mockClient.ping.mockResolvedValue(undefined); - await listMcpServers({ - merged: settingsWithAllowlist, - isTrusted: true, - } as unknown as LoadedSettings); + await listMcpServers( + {} as unknown as CliArgs, + { + merged: settingsWithAllowlist, + isTrusted: true, + } as unknown as LoadedSettings, + ); expect(debugLogger.log).toHaveBeenCalledWith( expect.stringContaining('allowed-server'), @@ -310,10 +347,16 @@ describe('mcp list command', () => { isTrusted: false, }); + const mockConfig = { + initialize: vi.fn().mockResolvedValue(undefined), + getExtensionLoader: vi.fn().mockReturnValue(mockExtensionManager), + }; + (loadCliConfig as Mock).mockResolvedValue(mockConfig as unknown as Config); + // createTransport will throw in core if not trusted mockedCreateTransport.mockRejectedValue(new Error('Folder not trusted')); - await listMcpServers(); + await listMcpServers({} as unknown as CliArgs); expect(debugLogger.log).toHaveBeenCalledWith( expect.stringContaining( @@ -337,7 +380,13 @@ describe('mcp list command', () => { isTrusted: true, }); - await listMcpServers(); + const mockConfig = { + initialize: vi.fn().mockResolvedValue(undefined), + getExtensionLoader: vi.fn().mockReturnValue(mockExtensionManager), + }; + (loadCliConfig as Mock).mockResolvedValue(mockConfig as unknown as Config); + + await listMcpServers({} as unknown as CliArgs); expect(debugLogger.log).toHaveBeenCalledWith( expect.stringContaining( @@ -359,12 +408,18 @@ describe('mcp list command', () => { isTrusted: true, }); + const mockConfig = { + initialize: vi.fn().mockResolvedValue(undefined), + getExtensionLoader: vi.fn().mockReturnValue(mockExtensionManager), + }; + (loadCliConfig as Mock).mockResolvedValue(mockConfig as unknown as Config); + vi.spyOn( McpServerEnablementManager.prototype, 'isFileEnabled', ).mockResolvedValue(false); - await listMcpServers(); + await listMcpServers({} as unknown as CliArgs); expect(debugLogger.log).toHaveBeenCalledWith( expect.stringContaining( diff --git a/packages/cli/src/commands/mcp/list.ts b/packages/cli/src/commands/mcp/list.ts index a1df1a8027..eb415010a4 100644 --- a/packages/cli/src/commands/mcp/list.ts +++ b/packages/cli/src/commands/mcp/list.ts @@ -17,36 +17,32 @@ import { debugLogger, applyAdminAllowlist, getAdminBlockedMcpServersMessage, + type GeminiCLIExtension, + type MCPServerConfig, } from '@google/gemini-cli-core'; -import type { MCPServerConfig } from '@google/gemini-cli-core'; import { Client } from '@modelcontextprotocol/sdk/client/index.js'; -import { ExtensionManager } from '../../config/extension-manager.js'; +import type { ExtensionManager } from '../../config/extension-manager.js'; import { canLoadServer, McpServerEnablementManager, } from '../../config/mcp/index.js'; -import { requestConsentNonInteractive } from '../../config/extensions/consent.js'; -import { promptForSetting } from '../../config/extensions/extensionSettings.js'; +import { loadCliConfig, type CliArgs } from '../../config/config.js'; import { exitCli } from '../utils.js'; import chalk from 'chalk'; export async function getMcpServersFromConfig( - settings?: MergedSettings, + settings: MergedSettings, + extensionManager: ExtensionManager, ): Promise<{ mcpServers: Record; blockedServerNames: string[]; }> { - if (!settings) { - settings = loadSettings().merged; + let extensions: GeminiCLIExtension[]; + try { + extensions = extensionManager.getExtensions(); + } catch { + extensions = await extensionManager.loadExtensions(); } - - const extensionManager = new ExtensionManager({ - settings, - workspaceDir: process.cwd(), - requestConsent: requestConsentNonInteractive, - requestSetting: promptForSetting, - }); - const extensions = await extensionManager.loadExtensions(); const mcpServers = { ...settings.mcpServers }; for (const extension of extensions) { Object.entries(extension.mcpServers || {}).forEach(([key, server]) => { @@ -170,13 +166,23 @@ async function getServerStatus( } export async function listMcpServers( + argv: CliArgs, loadedSettingsArg?: LoadedSettings, ): Promise { const loadedSettings = loadedSettingsArg ?? loadSettings(); const activeSettings = loadedSettings.merged; - const { mcpServers, blockedServerNames } = - await getMcpServersFromConfig(activeSettings); + const config = await loadCliConfig(activeSettings, 'mcp-list-session', argv, { + cwd: process.cwd(), + }); + await config.initialize(); + // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion + const extensionManager = config.getExtensionLoader() as ExtensionManager; + + const { mcpServers, blockedServerNames } = await getMcpServersFromConfig( + activeSettings, + extensionManager, + ); const serverNames = Object.keys(mcpServers); if (blockedServerNames.length > 0) { @@ -257,7 +263,8 @@ export const listCommand: CommandModule = { command: 'list', describe: 'List all configured MCP servers', handler: async (argv) => { - await listMcpServers(argv.loadedSettings); + // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion + await listMcpServers(argv as unknown as CliArgs, argv.loadedSettings); await exitCli(); }, }; diff --git a/packages/cli/src/commands/skills/list.test.ts b/packages/cli/src/commands/skills/list.test.ts index c330af75ba..b97a00ff78 100644 --- a/packages/cli/src/commands/skills/list.test.ts +++ b/packages/cli/src/commands/skills/list.test.ts @@ -8,7 +8,7 @@ import { vi, describe, it, expect, beforeEach, afterEach } from 'vitest'; import { coreEvents } from '@google/gemini-cli-core'; import { handleList, listCommand } from './list.js'; import { loadSettings, type LoadedSettings } from '../../config/settings.js'; -import { loadCliConfig } from '../../config/config.js'; +import { loadCliConfig, type CliArgs } from '../../config/config.js'; import type { Config } from '@google/gemini-cli-core'; import chalk from 'chalk'; @@ -55,7 +55,7 @@ describe('skills list command', () => { }; mockLoadCliConfig.mockResolvedValue(mockConfig as unknown as Config); - await handleList({}); + await handleList({} as unknown as CliArgs, {}); expect(coreEvents.emitConsoleLog).toHaveBeenCalledWith( 'log', @@ -86,7 +86,7 @@ describe('skills list command', () => { }; mockLoadCliConfig.mockResolvedValue(mockConfig as unknown as Config); - await handleList({}); + await handleList({} as unknown as CliArgs, {}); expect(coreEvents.emitConsoleLog).toHaveBeenCalledWith( 'log', @@ -135,7 +135,7 @@ describe('skills list command', () => { mockLoadCliConfig.mockResolvedValue(mockConfig as unknown as Config); // Default - await handleList({ all: false }); + await handleList({} as unknown as CliArgs, { all: false }); expect(coreEvents.emitConsoleLog).toHaveBeenCalledWith( 'log', expect.stringContaining('regular'), @@ -148,7 +148,7 @@ describe('skills list command', () => { vi.clearAllMocks(); // With all: true - await handleList({ all: true }); + await handleList({} as unknown as CliArgs, { all: true }); expect(coreEvents.emitConsoleLog).toHaveBeenCalledWith( 'log', expect.stringContaining('regular'), @@ -166,7 +166,9 @@ describe('skills list command', () => { it('should throw an error when listing fails', async () => { mockLoadCliConfig.mockRejectedValue(new Error('List failed')); - await expect(handleList({})).rejects.toThrow('List failed'); + await expect(handleList({} as unknown as CliArgs, {})).rejects.toThrow( + 'List failed', + ); }); }); diff --git a/packages/cli/src/commands/skills/list.ts b/packages/cli/src/commands/skills/list.ts index 49fc3a54f1..24a315c4ad 100644 --- a/packages/cli/src/commands/skills/list.ts +++ b/packages/cli/src/commands/skills/list.ts @@ -11,17 +11,14 @@ import { loadCliConfig, type CliArgs } from '../../config/config.js'; import { exitCli } from '../utils.js'; import chalk from 'chalk'; -export async function handleList(args: { all?: boolean }) { +export async function handleList(argv: CliArgs, args: { all?: boolean }) { const workspaceDir = process.cwd(); const settings = loadSettings(workspaceDir); const config = await loadCliConfig( settings.merged, 'skills-list-session', - // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion - { - debug: false, - } as Partial as CliArgs, + argv, { cwd: workspaceDir }, ); @@ -73,8 +70,9 @@ export const listCommand: CommandModule = { default: false, }), handler: async (argv) => { + const args = { all: Boolean(argv['all']) }; // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion - await handleList({ all: argv['all'] as boolean }); + await handleList(argv as unknown as CliArgs, args); await exitCli(); }, }; diff --git a/packages/cli/src/config/config.ts b/packages/cli/src/config/config.ts index 316c846029..07ea19418a 100755 --- a/packages/cli/src/config/config.ts +++ b/packages/cli/src/config/config.ts @@ -109,6 +109,13 @@ export async function parseArguments( .usage( 'Usage: gemini [options] [command]\n\nGemini CLI - Defaults to interactive mode. Use -p/--prompt for non-interactive (headless) mode.', ) + .option('profile', { + alias: ['profiles', 'P'], + type: 'string', + nargs: 1, + global: true, + description: 'The name of the profile to use for this session.', + }) .option('debug', { alias: 'd', type: 'boolean', @@ -146,13 +153,6 @@ export async function parseArguments( type: 'boolean', description: 'Run in sandbox?', }) - .option('profile', { - alias: ['profiles', 'P'], - type: 'string', - nargs: 1, - description: 'The name of the profile to use for this session.', - }) - .option('yolo', { alias: 'y', type: 'boolean',