diff --git a/packages/cli/src/config/extension-manager.ts b/packages/cli/src/config/extension-manager.ts index 5111c28c0b..cc2388585e 100644 --- a/packages/cli/src/config/extension-manager.ts +++ b/packages/cli/src/config/extension-manager.ts @@ -303,7 +303,7 @@ export class ExtensionManager extends ExtensionLoader { throw new Error(`Extension not found`); } if (isUpdate) { - logExtensionUpdateEvent( + await logExtensionUpdateEvent( this.telemetryConfig, new ExtensionUpdateEvent( hashValue(newExtensionConfig.name), @@ -315,7 +315,7 @@ export class ExtensionManager extends ExtensionLoader { ), ); } else { - logExtensionInstallEvent( + await logExtensionInstallEvent( this.telemetryConfig, new ExtensionInstallEvent( hashValue(newExtensionConfig.name), @@ -348,7 +348,7 @@ export class ExtensionManager extends ExtensionLoader { ? getExtensionId(config, installMetadata) : undefined; if (isUpdate) { - logExtensionUpdateEvent( + await logExtensionUpdateEvent( this.telemetryConfig, new ExtensionUpdateEvent( hashValue(config?.name ?? ''), @@ -360,7 +360,7 @@ export class ExtensionManager extends ExtensionLoader { ), ); } else { - logExtensionInstallEvent( + await logExtensionInstallEvent( this.telemetryConfig, new ExtensionInstallEvent( hashValue(newExtensionConfig?.name ?? ''), @@ -403,7 +403,7 @@ export class ExtensionManager extends ExtensionLoader { this.extensionEnablementManager.remove(extension.name); - logExtensionUninstall( + await logExtensionUninstall( this.telemetryConfig, new ExtensionUninstallEvent( hashValue(extension.name), @@ -569,6 +569,8 @@ export class ExtensionManager extends ExtensionLoader { const status = workspaceEnabled ? chalk.green('✓') : chalk.red('✗'); let output = `${status} ${extension.name} (${extension.version})`; output += `\n ID: ${extension.id}`; + output += `\n name: ${hashValue(extension.name)}`; + output += `\n Path: ${extension.path}`; if (extension.installMetadata) { output += `\n Source: ${extension.installMetadata.source} (Type: ${extension.installMetadata.type})`; @@ -621,7 +623,7 @@ export class ExtensionManager extends ExtensionLoader { scope === SettingScope.Workspace ? this.workspaceDir : os.homedir(); this.extensionEnablementManager.disable(name, true, scopePath); } - logExtensionDisable( + await logExtensionDisable( this.telemetryConfig, new ExtensionDisableEvent(hashValue(name), extension.id, scope), ); @@ -656,7 +658,7 @@ export class ExtensionManager extends ExtensionLoader { scope === SettingScope.Workspace ? this.workspaceDir : os.homedir(); this.extensionEnablementManager.enable(name, true, scopePath); } - logExtensionEnable( + await logExtensionEnable( this.telemetryConfig, new ExtensionEnableEvent(hashValue(name), extension.id, scope), ); diff --git a/packages/cli/src/services/FileCommandLoader.test.ts b/packages/cli/src/services/FileCommandLoader.test.ts index 7d853b19ef..a3c3018f83 100644 --- a/packages/cli/src/services/FileCommandLoader.test.ts +++ b/packages/cli/src/services/FileCommandLoader.test.ts @@ -842,6 +842,52 @@ describe('FileCommandLoader', () => { assert.fail('Incorrect action type'); } }); + + it('correctly loads extensionId for extension commands', async () => { + const extensionId = 'my-test-ext-id-123'; + const extensionDir = path.join( + process.cwd(), + GEMINI_DIR, + 'extensions', + 'my-test-ext', + ); + + mock({ + [extensionDir]: { + 'gemini-extension.json': JSON.stringify({ + name: 'my-test-ext', + id: extensionId, + version: '1.0.0', + }), + commands: { + 'my-cmd.toml': 'prompt = "My test command"', + }, + }, + }); + + const mockConfig = { + getProjectRoot: vi.fn(() => process.cwd()), + getExtensions: vi.fn(() => [ + { + name: 'my-test-ext', + id: extensionId, + version: '1.0.0', + isActive: true, + path: extensionDir, + }, + ]), + getFolderTrust: vi.fn(() => false), + isTrustedFolder: vi.fn(() => false), + } as unknown as Config; + const loader = new FileCommandLoader(mockConfig); + const commands = await loader.loadCommands(signal); + + expect(commands).toHaveLength(1); + const command = commands[0]; + expect(command.name).toBe('my-cmd'); + expect(command.extensionName).toBe('my-test-ext'); + expect(command.extensionId).toBe(extensionId); + }); }); describe('Argument Handling Integration (via ShellProcessor)', () => { diff --git a/packages/cli/src/services/FileCommandLoader.ts b/packages/cli/src/services/FileCommandLoader.ts index e770b360ab..cf91af7d89 100644 --- a/packages/cli/src/services/FileCommandLoader.ts +++ b/packages/cli/src/services/FileCommandLoader.ts @@ -37,6 +37,7 @@ import { AtFileProcessor } from './prompt-processors/atFileProcessor.js'; interface CommandDirectory { path: string; extensionName?: string; + extensionId?: string; } /** @@ -110,6 +111,7 @@ export class FileCommandLoader implements ICommandLoader { path.join(dirInfo.path, file), dirInfo.path, dirInfo.extensionName, + dirInfo.extensionId, ), ); @@ -158,6 +160,7 @@ export class FileCommandLoader implements ICommandLoader { const extensionCommandDirs = activeExtensions.map((ext) => ({ path: path.join(ext.path, 'commands'), extensionName: ext.name, + extensionId: ext.id, })); dirs.push(...extensionCommandDirs); @@ -177,6 +180,7 @@ export class FileCommandLoader implements ICommandLoader { filePath: string, baseDir: string, extensionName?: string, + extensionId?: string, ): Promise { let fileContent: string; try { @@ -265,6 +269,7 @@ export class FileCommandLoader implements ICommandLoader { description, kind: CommandKind.FILE, extensionName, + extensionId, action: async ( context: CommandContext, _args: string, diff --git a/packages/core/src/telemetry/clearcut-logger/clearcut-logger.ts b/packages/core/src/telemetry/clearcut-logger/clearcut-logger.ts index bd3dfb4109..6050314313 100644 --- a/packages/core/src/telemetry/clearcut-logger/clearcut-logger.ts +++ b/packages/core/src/telemetry/clearcut-logger/clearcut-logger.ts @@ -940,7 +940,7 @@ export class ClearcutLogger { this.flushIfNeeded(); } - logExtensionInstallEvent(event: ExtensionInstallEvent): void { + async logExtensionInstallEvent(event: ExtensionInstallEvent): Promise { const data: EventValue[] = [ { gemini_cli_key: EventMetadataKey.GEMINI_CLI_EXTENSION_NAME, @@ -967,12 +967,14 @@ export class ClearcutLogger { this.enqueueLogEvent( this.createBasicLogEvent(EventNames.EXTENSION_INSTALL, data), ); - this.flushToClearcut().catch((error) => { + await this.flushToClearcut().catch((error) => { debugLogger.debug('Error flushing to Clearcut:', error); }); } - logExtensionUninstallEvent(event: ExtensionUninstallEvent): void { + async logExtensionUninstallEvent( + event: ExtensionUninstallEvent, + ): Promise { const data: EventValue[] = [ { gemini_cli_key: EventMetadataKey.GEMINI_CLI_EXTENSION_NAME, @@ -991,12 +993,12 @@ export class ClearcutLogger { this.enqueueLogEvent( this.createBasicLogEvent(EventNames.EXTENSION_UNINSTALL, data), ); - this.flushToClearcut().catch((error) => { + await this.flushToClearcut().catch((error) => { debugLogger.debug('Error flushing to Clearcut:', error); }); } - logExtensionUpdateEvent(event: ExtensionUpdateEvent): void { + async logExtensionUpdateEvent(event: ExtensionUpdateEvent): Promise { const data: EventValue[] = [ { gemini_cli_key: EventMetadataKey.GEMINI_CLI_EXTENSION_NAME, @@ -1027,7 +1029,7 @@ export class ClearcutLogger { this.enqueueLogEvent( this.createBasicLogEvent(EventNames.EXTENSION_UPDATE, data), ); - this.flushToClearcut().catch((error) => { + await this.flushToClearcut().catch((error) => { debugLogger.debug('Error flushing to Clearcut:', error); }); } @@ -1096,7 +1098,7 @@ export class ClearcutLogger { this.flushIfNeeded(); } - logExtensionEnableEvent(event: ExtensionEnableEvent): void { + async logExtensionEnableEvent(event: ExtensionEnableEvent): Promise { const data: EventValue[] = [ { gemini_cli_key: EventMetadataKey.GEMINI_CLI_EXTENSION_NAME, @@ -1116,7 +1118,7 @@ export class ClearcutLogger { this.enqueueLogEvent( this.createBasicLogEvent(EventNames.EXTENSION_ENABLE, data), ); - this.flushToClearcut().catch((error) => { + await this.flushToClearcut().catch((error) => { debugLogger.debug('Error flushing to Clearcut:', error); }); } @@ -1135,7 +1137,7 @@ export class ClearcutLogger { this.flushIfNeeded(); } - logExtensionDisableEvent(event: ExtensionDisableEvent): void { + async logExtensionDisableEvent(event: ExtensionDisableEvent): Promise { const data: EventValue[] = [ { gemini_cli_key: EventMetadataKey.GEMINI_CLI_EXTENSION_NAME, @@ -1155,7 +1157,7 @@ export class ClearcutLogger { this.enqueueLogEvent( this.createBasicLogEvent(EventNames.EXTENSION_DISABLE, data), ); - this.flushToClearcut().catch((error) => { + await this.flushToClearcut().catch((error) => { debugLogger.debug('Error flushing to Clearcut:', error); }); } diff --git a/packages/core/src/telemetry/loggers.test.ts b/packages/core/src/telemetry/loggers.test.ts index 825666055e..5a8cb12da7 100644 --- a/packages/core/src/telemetry/loggers.test.ts +++ b/packages/core/src/telemetry/loggers.test.ts @@ -1516,10 +1516,10 @@ describe('loggers', () => { }); afterEach(() => { - vi.resetAllMocks(); + vi.clearAllMocks(); }); - it('should log extension install event', () => { + it('should log extension install event', async () => { const event = new ExtensionInstallEvent( 'testing', 'testing-id', @@ -1528,7 +1528,7 @@ describe('loggers', () => { 'success', ); - logExtensionInstallEvent(mockConfig, event); + await logExtensionInstallEvent(mockConfig, event); expect( ClearcutLogger.prototype.logExtensionInstallEvent, @@ -1551,7 +1551,7 @@ describe('loggers', () => { }); }); - describe('logExtensionUpdate', () => { + describe('logExtensionUpdate', async () => { const mockConfig = { getSessionId: () => 'test-session-id', getUsageStatisticsEnabled: () => true, @@ -1565,10 +1565,10 @@ describe('loggers', () => { }); afterEach(() => { - vi.resetAllMocks(); + vi.clearAllMocks(); }); - it('should log extension update event', () => { + it('should log extension update event', async () => { const event = new ExtensionUpdateEvent( 'testing', 'testing-id', @@ -1578,7 +1578,7 @@ describe('loggers', () => { 'success', ); - logExtensionUpdateEvent(mockConfig, event); + await logExtensionUpdateEvent(mockConfig, event); expect( ClearcutLogger.prototype.logExtensionUpdateEvent, @@ -1602,7 +1602,7 @@ describe('loggers', () => { }); }); - describe('logExtensionUninstall', () => { + describe('logExtensionUninstall', async () => { const mockConfig = { getSessionId: () => 'test-session-id', getUsageStatisticsEnabled: () => true, @@ -1616,17 +1616,16 @@ describe('loggers', () => { }); afterEach(() => { - vi.resetAllMocks(); + vi.clearAllMocks(); }); - - it('should log extension uninstall event', () => { + it('should log extension uninstall event', async () => { const event = new ExtensionUninstallEvent( 'testing', 'testing-id', 'success', ); - logExtensionUninstall(mockConfig, event); + await logExtensionUninstall(mockConfig, event); expect( ClearcutLogger.prototype.logExtensionUninstallEvent, @@ -1647,7 +1646,7 @@ describe('loggers', () => { }); }); - describe('logExtensionEnable', () => { + describe('logExtensionEnable', async () => { const mockConfig = { getSessionId: () => 'test-session-id', getUsageStatisticsEnabled: () => true, @@ -1658,13 +1657,13 @@ describe('loggers', () => { }); afterEach(() => { - vi.resetAllMocks(); + vi.clearAllMocks(); }); - it('should log extension enable event', () => { + it('should log extension enable event', async () => { const event = new ExtensionEnableEvent('testing', 'testing-id', 'user'); - logExtensionEnable(mockConfig, event); + await logExtensionEnable(mockConfig, event); expect( ClearcutLogger.prototype.logExtensionEnableEvent, @@ -1696,13 +1695,13 @@ describe('loggers', () => { }); afterEach(() => { - vi.resetAllMocks(); + vi.clearAllMocks(); }); - it('should log extension disable event', () => { + it('should log extension disable event', async () => { const event = new ExtensionDisableEvent('testing', 'testing-id', 'user'); - logExtensionDisable(mockConfig, event); + await logExtensionDisable(mockConfig, event); expect( ClearcutLogger.prototype.logExtensionDisableEvent, diff --git a/packages/core/src/telemetry/loggers.ts b/packages/core/src/telemetry/loggers.ts index 6d2412d2ff..c2d6180c67 100644 --- a/packages/core/src/telemetry/loggers.ts +++ b/packages/core/src/telemetry/loggers.ts @@ -507,11 +507,11 @@ export function logModelSlashCommand( recordModelSlashCommand(config, event); } -export function logExtensionInstallEvent( +export async function logExtensionInstallEvent( config: Config, event: ExtensionInstallEvent, -): void { - ClearcutLogger.getInstance(config)?.logExtensionInstallEvent(event); +): Promise { + await ClearcutLogger.getInstance(config)?.logExtensionInstallEvent(event); if (!isTelemetrySdkInitialized()) return; const logger = logs.getLogger(SERVICE_NAME); @@ -522,11 +522,11 @@ export function logExtensionInstallEvent( logger.emit(logRecord); } -export function logExtensionUninstall( +export async function logExtensionUninstall( config: Config, event: ExtensionUninstallEvent, -): void { - ClearcutLogger.getInstance(config)?.logExtensionUninstallEvent(event); +): Promise { + await ClearcutLogger.getInstance(config)?.logExtensionUninstallEvent(event); if (!isTelemetrySdkInitialized()) return; const logger = logs.getLogger(SERVICE_NAME); @@ -537,11 +537,11 @@ export function logExtensionUninstall( logger.emit(logRecord); } -export function logExtensionUpdateEvent( +export async function logExtensionUpdateEvent( config: Config, event: ExtensionUpdateEvent, -): void { - ClearcutLogger.getInstance(config)?.logExtensionUpdateEvent(event); +): Promise { + await ClearcutLogger.getInstance(config)?.logExtensionUpdateEvent(event); if (!isTelemetrySdkInitialized()) return; const logger = logs.getLogger(SERVICE_NAME); @@ -552,11 +552,11 @@ export function logExtensionUpdateEvent( logger.emit(logRecord); } -export function logExtensionEnable( +export async function logExtensionEnable( config: Config, event: ExtensionEnableEvent, -): void { - ClearcutLogger.getInstance(config)?.logExtensionEnableEvent(event); +): Promise { + await ClearcutLogger.getInstance(config)?.logExtensionEnableEvent(event); if (!isTelemetrySdkInitialized()) return; const logger = logs.getLogger(SERVICE_NAME); @@ -567,11 +567,11 @@ export function logExtensionEnable( logger.emit(logRecord); } -export function logExtensionDisable( +export async function logExtensionDisable( config: Config, event: ExtensionDisableEvent, -): void { - ClearcutLogger.getInstance(config)?.logExtensionDisableEvent(event); +): Promise { + await ClearcutLogger.getInstance(config)?.logExtensionDisableEvent(event); if (!isTelemetrySdkInitialized()) return; const logger = logs.getLogger(SERVICE_NAME);