/** * @license * Copyright 2025 Google LLC * SPDX-License-Identifier: Apache-2.0 */ import type { ExtensionUpdateInfo } from '../../config/extension.js'; import { getErrorMessage } from '../../utils/errors.js'; import { MessageType } from '../types.js'; import { type CommandContext, type SlashCommand, CommandKind, } from './types.js'; async function listAction(context: CommandContext) { context.ui.addItem( { type: MessageType.EXTENSIONS_LIST, }, Date.now(), ); } function updateAction(context: CommandContext, args: string): Promise { const updateArgs = args.split(' ').filter((value) => value.length > 0); const all = updateArgs.length === 1 && updateArgs[0] === '--all'; const names = all ? null : updateArgs; if (!all && names?.length === 0) { context.ui.addItem( { type: MessageType.ERROR, text: 'Usage: /extensions update |--all', }, Date.now(), ); return Promise.resolve(); } let resolveUpdateComplete: (updateInfo: ExtensionUpdateInfo[]) => void; const updateComplete = new Promise( (resolve) => (resolveUpdateComplete = resolve), ); updateComplete.then((updateInfos) => { if (updateInfos.length === 0) { context.ui.addItem( { type: MessageType.INFO, text: 'No extensions to update.', }, Date.now(), ); } context.ui.addItem( { type: MessageType.EXTENSIONS_LIST, }, Date.now(), ); context.ui.setPendingItem(null); }); try { context.ui.setPendingItem({ type: MessageType.EXTENSIONS_LIST, }); context.ui.dispatchExtensionStateUpdate({ type: 'SCHEDULE_UPDATE', payload: { all, names, onComplete: (updateInfos) => { resolveUpdateComplete(updateInfos); }, }, }); if (names?.length) { const extensions = context.services.config!.getExtensions(); for (const name of names) { const extension = extensions.find( (extension) => extension.name === name, ); if (!extension) { context.ui.addItem( { type: MessageType.ERROR, text: `Extension ${name} not found.`, }, Date.now(), ); continue; } } } } catch (error) { resolveUpdateComplete!([]); context.ui.addItem( { type: MessageType.ERROR, text: getErrorMessage(error), }, Date.now(), ); } return updateComplete.then((_) => {}); } const listExtensionsCommand: SlashCommand = { name: 'list', description: 'List active extensions', kind: CommandKind.BUILT_IN, action: listAction, }; const updateExtensionsCommand: SlashCommand = { name: 'update', description: 'Update extensions. Usage: update |--all', kind: CommandKind.BUILT_IN, action: updateAction, completion: async (context, partialArg) => { const extensions = context.services.config?.getExtensions() ?? []; const extensionNames = extensions.map((ext) => ext.name); const suggestions = extensionNames.filter((name) => name.startsWith(partialArg), ); if ('--all'.startsWith(partialArg) || 'all'.startsWith(partialArg)) { suggestions.unshift('--all'); } return suggestions; }, }; export const extensionsCommand: SlashCommand = { name: 'extensions', description: 'Manage extensions', kind: CommandKind.BUILT_IN, subCommands: [listExtensionsCommand, updateExtensionsCommand], action: (context, args) => // Default to list if no subcommand is provided listExtensionsCommand.action!(context, args), };