Conditionally use consent flow (#8551)

This commit is contained in:
christine betts
2025-09-17 09:24:38 -04:00
committed by GitHub
parent 1a6e4a119e
commit 0403375799
3 changed files with 47 additions and 27 deletions

View File

@@ -47,7 +47,7 @@ export async function handleInstall(args: InstallArgs) {
throw new Error('Either --source or --path must be provided.');
}
const name = await installExtension(installMetadata);
const name = await installExtension(installMetadata, true);
console.log(`Extension "${name}" installed successfully and enabled.`);
} catch (error) {
console.error(getErrorMessage(error));

View File

@@ -596,7 +596,7 @@ describe('extension tests', () => {
mockQuestion.mockImplementation((_query, callback) => callback('y'));
await expect(
installExtension({ source: sourceExtDir, type: 'local' }),
installExtension({ source: sourceExtDir, type: 'local' }, true),
).resolves.toBe('my-local-extension');
expect(consoleInfoSpy).toHaveBeenCalledWith(
@@ -629,7 +629,7 @@ describe('extension tests', () => {
mockQuestion.mockImplementation((_query, callback) => callback('y'));
await expect(
installExtension({ source: sourceExtDir, type: 'local' }),
installExtension({ source: sourceExtDir, type: 'local' }, true),
).resolves.toBe('my-local-extension');
expect(mockQuestion).toHaveBeenCalledWith(
@@ -654,7 +654,7 @@ describe('extension tests', () => {
mockQuestion.mockImplementation((_query, callback) => callback('n'));
await expect(
installExtension({ source: sourceExtDir, type: 'local' }),
installExtension({ source: sourceExtDir, type: 'local' }, true),
).rejects.toThrow('Installation cancelled by user.');
expect(mockQuestion).toHaveBeenCalledWith(
@@ -662,6 +662,24 @@ describe('extension tests', () => {
expect.any(Function),
);
});
it('should ignore consent flow if not required', async () => {
const sourceExtDir = createExtension({
extensionsDir: tempHomeDir,
name: 'my-local-extension',
version: '1.0.0',
mcpServers: {
'test-server': {
command: 'node',
args: ['server.js'],
},
},
});
await expect(
installExtension({ source: sourceExtDir, type: 'local' }, false),
).resolves.toBe('my-local-extension');
});
});
describe('uninstallExtension', () => {

View File

@@ -418,6 +418,7 @@ async function promptForContinuation(prompt: string): Promise<boolean> {
export async function installExtension(
installMetadata: ExtensionInstallMetadata,
askConsent: boolean = false,
cwd: string = process.cwd(),
): Promise<string> {
const logger = getClearcutLogger(cwd);
@@ -482,30 +483,9 @@ export async function installExtension(
`Extension "${newExtensionName}" is already installed. Please uninstall it first.`,
);
}
const mcpServerEntries = Object.entries(
newExtensionConfig.mcpServers || {},
);
if (mcpServerEntries.length) {
console.info('This extension will run the following MCP servers: ');
for (const [key, mcpServer] of mcpServerEntries) {
const isLocal = !!mcpServer.command;
console.info(
` * ${key} (${isLocal ? 'local' : 'remote'}): ${mcpServer.description}`,
);
}
console.info(
'The extension will append info to your gemini.md context',
);
const shouldContinue = await promptForContinuation(
'Do you want to continue? (y/n): ',
);
if (!shouldContinue) {
throw new Error('Installation cancelled by user.');
}
if (askConsent) {
await requestConsent(newExtensionConfig);
}
await fs.promises.mkdir(destinationPath, { recursive: true });
if (installMetadata.type === 'local' || installMetadata.type === 'git') {
@@ -556,6 +536,27 @@ export async function installExtension(
}
}
async function requestConsent(extensionConfig: ExtensionConfig) {
const mcpServerEntries = Object.entries(extensionConfig.mcpServers || {});
if (mcpServerEntries.length) {
console.info('This extension will run the following MCP servers: ');
for (const [key, mcpServer] of mcpServerEntries) {
const isLocal = !!mcpServer.command;
console.info(
` * ${key} (${isLocal ? 'local' : 'remote'}): ${mcpServer.description}`,
);
}
console.info('The extension will append info to your gemini.md context');
const shouldContinue = await promptForContinuation(
'Do you want to continue? (y/n): ',
);
if (!shouldContinue) {
throw new Error('Installation cancelled by user.');
}
}
}
export async function loadExtensionConfig(
context: LoadExtensionContext,
): Promise<ExtensionConfig | null> {
@@ -684,6 +685,7 @@ export async function updateExtension(
type: extension.type,
ref: extension.ref,
},
false,
cwd,
);