Download packages/cli/src/commands/extensions/install.ts from SaylorTwift/gemini-cli: direct link, hf CLI and curl.
- Browser
- Download file 7.76 kB
-
https://huggingface.co/SaylorTwift/gemini-cli/resolve/main/packages/cli/src/commands/extensions/install.ts
- Command line
-
hf download hf://SaylorTwift/gemini-cli/packages/cli/src/commands/extensions/install.ts
-
curl -L -o install.ts https://huggingface.co/SaylorTwift/gemini-cli/resolve/main/packages/cli/src/commands/extensions/install.ts
7.76 kB
| /** | |
| * @license | |
| * Copyright 2025 Google LLC | |
| * SPDX-License-Identifier: Apache-2.0 | |
| */ | |
| import type { CommandModule } from 'yargs'; | |
| import * as path from 'node:path'; | |
| import chalk from 'chalk'; | |
| import { | |
| debugLogger, | |
| FolderTrustDiscoveryService, | |
| getRealPath, | |
| getErrorMessage, | |
| } from '@google/gemini-cli-core'; | |
| import { | |
| INSTALL_WARNING_MESSAGE, | |
| promptForConsentNonInteractive, | |
| requestConsentNonInteractive, | |
| } from '../../config/extensions/consent.js'; | |
| import { | |
| ExtensionManager, | |
| inferInstallMetadata, | |
| } from '../../config/extension-manager.js'; | |
| import { loadSettings } from '../../config/settings.js'; | |
| import { | |
| isWorkspaceTrusted, | |
| loadTrustedFolders, | |
| TrustLevel, | |
| } from '../../config/trustedFolders.js'; | |
| import { promptForSetting } from '../../config/extensions/extensionSettings.js'; | |
| import { exitCli } from '../utils.js'; | |
| interface InstallArgs { | |
| source: string; | |
| ref?: string; | |
| autoUpdate?: boolean; | |
| allowPreRelease?: boolean; | |
| consent?: boolean; | |
| skipSettings?: boolean; | |
| } | |
| export async function handleInstall(args: InstallArgs) { | |
| try { | |
| const { source } = args; | |
| const installMetadata = await inferInstallMetadata(source, { | |
| ref: args.ref, | |
| autoUpdate: args.autoUpdate, | |
| allowPreRelease: args.allowPreRelease, | |
| }); | |
| const workspaceDir = process.cwd(); | |
| const settings = loadSettings(workspaceDir).merged; | |
| if (installMetadata.type === 'local' || installMetadata.type === 'link') { | |
| const absolutePath = path.resolve(source); | |
| const realPath = getRealPath(absolutePath); | |
| installMetadata.source = absolutePath; | |
| const trustResult = isWorkspaceTrusted(settings, absolutePath); | |
| if (trustResult.isTrusted !== true) { | |
| const discoveryResults = | |
| await FolderTrustDiscoveryService.discover(realPath); | |
| const hasDiscovery = | |
| discoveryResults.commands.length > 0 || | |
| discoveryResults.mcps.length > 0 || | |
| discoveryResults.hooks.length > 0 || | |
| discoveryResults.skills.length > 0 || | |
| discoveryResults.settings.length > 0; | |
| const promptLines = [ | |
| '', | |
| chalk.bold('Do you trust the files in this folder?'), | |
| '', | |
| `The extension source at "${absolutePath}" is not trusted.`, | |
| '', | |
| 'Trusting a folder allows Gemini CLI to load its local configurations,', | |
| 'including custom commands, hooks, MCP servers, agent skills, and', | |
| 'settings. These configurations could execute code on your behalf or', | |
| 'change the behavior of the CLI.', | |
| '', | |
| ]; | |
| if (discoveryResults.discoveryErrors.length > 0) { | |
| promptLines.push(chalk.red('❌ Discovery Errors:')); | |
| for (const error of discoveryResults.discoveryErrors) { | |
| promptLines.push(chalk.red(` • ${error}`)); | |
| } | |
| promptLines.push(''); | |
| } | |
| if (discoveryResults.securityWarnings.length > 0) { | |
| promptLines.push(chalk.yellow('⚠️ Security Warnings:')); | |
| for (const warning of discoveryResults.securityWarnings) { | |
| promptLines.push(chalk.yellow(` • ${warning}`)); | |
| } | |
| promptLines.push(''); | |
| } | |
| if (hasDiscovery) { | |
| promptLines.push(chalk.bold('This folder contains:')); | |
| const groups = [ | |
| { label: 'Commands', items: discoveryResults.commands ?? [] }, | |
| { label: 'MCP Servers', items: discoveryResults.mcps ?? [] }, | |
| { label: 'Hooks', items: discoveryResults.hooks ?? [] }, | |
| { label: 'Skills', items: discoveryResults.skills ?? [] }, | |
| { label: 'Agents', items: discoveryResults.agents ?? [] }, | |
| { | |
| label: 'Setting overrides', | |
| items: discoveryResults.settings ?? [], | |
| }, | |
| ].filter((g) => g.items.length > 0); | |
| for (const group of groups) { | |
| promptLines.push( | |
| ` • ${chalk.bold(group.label)} (${group.items.length}):`, | |
| ); | |
| for (const item of group.items) { | |
| promptLines.push(` - ${item}`); | |
| } | |
| } | |
| promptLines.push(''); | |
| } | |
| promptLines.push( | |
| chalk.yellow( | |
| 'Do you want to trust this folder and continue with the installation? [y/N]: ', | |
| ), | |
| ); | |
| const confirmed = await promptForConsentNonInteractive( | |
| promptLines.join('\n'), | |
| false, | |
| ); | |
| if (confirmed) { | |
| const trustedFolders = loadTrustedFolders(); | |
| await trustedFolders.setValue(realPath, TrustLevel.TRUST_FOLDER); | |
| } else { | |
| throw new Error( | |
| `Installation aborted: Folder "${absolutePath}" is not trusted.`, | |
| ); | |
| } | |
| } | |
| } | |
| const requestConsent = args.consent | |
| ? () => Promise.resolve(true) | |
| : requestConsentNonInteractive; | |
| if (args.consent) { | |
| debugLogger.log('You have consented to the following:'); | |
| debugLogger.log(INSTALL_WARNING_MESSAGE); | |
| } | |
| const extensionManager = new ExtensionManager({ | |
| workspaceDir, | |
| requestConsent, | |
| requestSetting: args.skipSettings ? null : promptForSetting, | |
| settings, | |
| }); | |
| await extensionManager.loadExtensions(); | |
| const extension = | |
| await extensionManager.installOrUpdateExtension(installMetadata); | |
| debugLogger.log( | |
| `Extension "${extension.name}" installed successfully and enabled.`, | |
| ); | |
| } catch (error) { | |
| debugLogger.error(getErrorMessage(error)); | |
| process.exit(1); | |
| } | |
| } | |
| export const installCommand: CommandModule = { | |
| command: 'install <source> [--auto-update] [--pre-release]', | |
| describe: 'Installs an extension from a git repository URL or a local path.', | |
| builder: (yargs) => | |
| yargs | |
| .positional('source', { | |
| describe: 'The github URL or local path of the extension to install.', | |
| type: 'string', | |
| demandOption: true, | |
| }) | |
| .option('ref', { | |
| describe: 'The git ref to install from.', | |
| type: 'string', | |
| }) | |
| .option('auto-update', { | |
| describe: 'Enable auto-update for this extension.', | |
| type: 'boolean', | |
| }) | |
| .option('pre-release', { | |
| describe: 'Enable pre-release versions for this extension.', | |
| type: 'boolean', | |
| }) | |
| .option('consent', { | |
| describe: | |
| 'Acknowledge the security risks of installing an extension and skip the confirmation prompt.', | |
| type: 'boolean', | |
| default: false, | |
| }) | |
| .option('skip-settings', { | |
| describe: 'Skip the configuration on install process.', | |
| type: 'boolean', | |
| default: false, | |
| }) | |
| .check((argv) => { | |
| if (!argv.source) { | |
| throw new Error('The source argument must be provided.'); | |
| } | |
| return true; | |
| }), | |
| handler: async (argv) => { | |
| await handleInstall({ | |
| // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion | |
| source: argv['source'] as string, | |
| // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion | |
| ref: argv['ref'] as string | undefined, | |
| // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion | |
| autoUpdate: argv['auto-update'] as boolean | undefined, | |
| // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion | |
| allowPreRelease: argv['pre-release'] as boolean | undefined, | |
| // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion | |
| consent: argv['consent'] as boolean | undefined, | |
| // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion | |
| skipSettings: argv['skip-settings'] as boolean | undefined, | |
| }); | |
| await exitCli(); | |
| }, | |
| }; | |