diff --git a/packages/cli/src/commands/extensions/list.ts b/packages/cli/src/commands/extensions/list.ts index 57139cebeb..f6689a3c0c 100644 --- a/packages/cli/src/commands/extensions/list.ts +++ b/packages/cli/src/commands/extensions/list.ts @@ -5,12 +5,7 @@ */ import type { CommandModule } from 'yargs'; -import { - loadUserExtensions, - toOutputString, - ExtensionStorage, -} from '../../config/extension.js'; -import { ExtensionEnablementManager } from '../../config/extensions/extensionEnablement.js'; +import { loadUserExtensions, toOutputString } from '../../config/extension.js'; import { getErrorMessage } from '../../utils/errors.js'; export async function handleList() { @@ -20,16 +15,9 @@ export async function handleList() { console.log('No extensions installed.'); return; } - const manager = new ExtensionEnablementManager( - ExtensionStorage.getUserExtensionsDir(), - ); - const cwd = process.cwd(); console.log( extensions - .map((extension): string => { - const isEnabled = manager.isEnabled(extension.config.name, cwd); - return toOutputString(extension, isEnabled); - }) + .map((extension, _): string => toOutputString(extension, process.cwd())) .join('\n\n'), ); } catch (error) { diff --git a/packages/cli/src/config/extension.test.ts b/packages/cli/src/config/extension.test.ts index 6f3e62c137..19d22a5002 100644 --- a/packages/cli/src/config/extension.test.ts +++ b/packages/cli/src/config/extension.test.ts @@ -1235,6 +1235,12 @@ This extension will run the following MCP servers: describe('disableExtension', () => { it('should disable an extension at the user scope', () => { + createExtension({ + extensionsDir: userExtensionsDir, + name: 'my-extension', + version: '1.0.0', + }); + disableExtension('my-extension', SettingScope.User); expect( isEnabled({ @@ -1246,6 +1252,12 @@ This extension will run the following MCP servers: }); it('should disable an extension at the workspace scope', () => { + createExtension({ + extensionsDir: userExtensionsDir, + name: 'my-extension', + version: '1.0.0', + }); + disableExtension( 'my-extension', SettingScope.Workspace, @@ -1268,6 +1280,12 @@ This extension will run the following MCP servers: }); it('should handle disabling the same extension twice', () => { + createExtension({ + extensionsDir: userExtensionsDir, + name: 'my-extension', + version: '1.0.0', + }); + disableExtension('my-extension', SettingScope.User); disableExtension('my-extension', SettingScope.User); expect( @@ -1286,6 +1304,12 @@ This extension will run the following MCP servers: }); it('should log a disable event', () => { + createExtension({ + extensionsDir: userExtensionsDir, + name: 'ext1', + version: '1.0.0', + }); + disableExtension('ext1', SettingScope.Workspace); expect(mockLogExtensionDisable).toHaveBeenCalled(); diff --git a/packages/cli/src/config/extension.ts b/packages/cli/src/config/extension.ts index 3cf2dad33e..2effae1d9f 100644 --- a/packages/cli/src/config/extension.ts +++ b/packages/cli/src/config/extension.ts @@ -260,6 +260,32 @@ export function loadExtension(context: LoadExtensionContext): Extension | null { } } +export function loadExtensionByName( + name: string, + workspaceDir: string = process.cwd(), +): Extension | null { + const userExtensionsDir = ExtensionStorage.getUserExtensionsDir(); + if (!fs.existsSync(userExtensionsDir)) { + return null; + } + + for (const subdir of fs.readdirSync(userExtensionsDir)) { + const extensionDir = path.join(userExtensionsDir, subdir); + if (!fs.statSync(extensionDir).isDirectory()) { + continue; + } + const extension = loadExtension({ extensionDir, workspaceDir }); + if ( + extension && + extension.config.name.toLowerCase() === name.toLowerCase() + ) { + return extension; + } + } + + return null; +} + function filterMcpConfig(original: MCPServerConfig): MCPServerConfig { // eslint-disable-next-line @typescript-eslint/no-unused-vars const { trust, ...rest } = original; @@ -701,9 +727,18 @@ export async function uninstallExtension( export function toOutputString( extension: Extension, - isEnabled: boolean, + workspaceDir: string, ): string { - const status = isEnabled ? chalk.green('✓') : chalk.red('✗'); + const manager = new ExtensionEnablementManager( + ExtensionStorage.getUserExtensionsDir(), + ); + const userEnabled = manager.isEnabled(extension.config.name, os.homedir()); + const workspaceEnabled = manager.isEnabled( + extension.config.name, + workspaceDir, + ); + + const status = workspaceEnabled ? chalk.green('✓') : chalk.red('✗'); let output = `${status} ${extension.config.name} (${extension.config.version})`; output += `\n Path: ${extension.path}`; if (extension.installMetadata) { @@ -715,6 +750,8 @@ export function toOutputString( output += `\n Release tag: ${extension.installMetadata.releaseTag}`; } } + output += `\n Enabled (User): ${userEnabled}`; + output += `\n Enabled (Workspace): ${workspaceEnabled}`; if (extension.contextFiles.length > 0) { output += `\n Context files:`; extension.contextFiles.forEach((contextFile) => { @@ -745,6 +782,10 @@ export function disableExtension( if (scope === SettingScope.System || scope === SettingScope.SystemDefaults) { throw new Error('System and SystemDefaults scopes are not supported.'); } + const extension = loadExtensionByName(name, cwd); + if (!extension) { + throw new Error(`Extension with name ${name} does not exist.`); + } const manager = new ExtensionEnablementManager( ExtensionStorage.getUserExtensionsDir(), @@ -762,6 +803,10 @@ export function enableExtension( if (scope === SettingScope.System || scope === SettingScope.SystemDefaults) { throw new Error('System and SystemDefaults scopes are not supported.'); } + const extension = loadExtensionByName(name, cwd); + if (!extension) { + throw new Error(`Extension with name ${name} does not exist.`); + } const manager = new ExtensionEnablementManager( ExtensionStorage.getUserExtensionsDir(), );