mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-18 01:51:20 -07:00
117 lines
3.1 KiB
TypeScript
117 lines
3.1 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright 2025 Google LLC
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
import * as fs from 'node:fs';
|
|
import * as path from 'node:path';
|
|
import { Storage } from '../config/storage.js';
|
|
import {
|
|
getHookKey,
|
|
type HookDefinition,
|
|
type HookEventName,
|
|
} from './types.js';
|
|
import { debugLogger } from '../utils/debugLogger.js';
|
|
|
|
interface TrustedHooksConfig {
|
|
[projectPath: string]: string[]; // Array of trusted hook keys (name:command)
|
|
}
|
|
|
|
export class TrustedHooksManager {
|
|
private configPath: string;
|
|
private trustedHooks: TrustedHooksConfig = {};
|
|
|
|
constructor() {
|
|
this.configPath = path.join(
|
|
Storage.getGlobalGeminiDir(),
|
|
'trusted_hooks.json',
|
|
);
|
|
this.load();
|
|
}
|
|
|
|
private load(): void {
|
|
try {
|
|
if (fs.existsSync(this.configPath)) {
|
|
const content = fs.readFileSync(this.configPath, 'utf-8');
|
|
this.trustedHooks = JSON.parse(content);
|
|
}
|
|
} catch (error) {
|
|
debugLogger.warn('Failed to load trusted hooks config', error);
|
|
this.trustedHooks = {};
|
|
}
|
|
}
|
|
|
|
private save(): void {
|
|
try {
|
|
const dir = path.dirname(this.configPath);
|
|
if (!fs.existsSync(dir)) {
|
|
fs.mkdirSync(dir, { recursive: true });
|
|
}
|
|
fs.writeFileSync(
|
|
this.configPath,
|
|
JSON.stringify(this.trustedHooks, null, 2),
|
|
);
|
|
} catch (error) {
|
|
debugLogger.warn('Failed to save trusted hooks config', error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get untrusted hooks for a project
|
|
* @param projectPath Absolute path to the project root
|
|
* @param hooks The hooks configuration to check
|
|
* @returns List of untrusted hook commands/names
|
|
*/
|
|
getUntrustedHooks(
|
|
projectPath: string,
|
|
hooks: { [K in HookEventName]?: HookDefinition[] },
|
|
): string[] {
|
|
const trustedKeys = new Set(this.trustedHooks[projectPath] || []);
|
|
const untrusted: string[] = [];
|
|
|
|
for (const eventName of Object.keys(hooks)) {
|
|
const definitions = hooks[eventName as HookEventName];
|
|
if (!Array.isArray(definitions)) continue;
|
|
|
|
for (const def of definitions) {
|
|
if (!def || !Array.isArray(def.hooks)) continue;
|
|
for (const hook of def.hooks) {
|
|
const key = getHookKey(hook);
|
|
if (!trustedKeys.has(key)) {
|
|
// Return friendly name or command
|
|
untrusted.push(hook.name || hook.command || 'unknown-hook');
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return Array.from(new Set(untrusted)); // Deduplicate
|
|
}
|
|
|
|
/**
|
|
* Trust all provided hooks for a project
|
|
*/
|
|
trustHooks(
|
|
projectPath: string,
|
|
hooks: { [K in HookEventName]?: HookDefinition[] },
|
|
): void {
|
|
const currentTrusted = new Set(this.trustedHooks[projectPath] || []);
|
|
|
|
for (const eventName of Object.keys(hooks)) {
|
|
const definitions = hooks[eventName as HookEventName];
|
|
if (!Array.isArray(definitions)) continue;
|
|
|
|
for (const def of definitions) {
|
|
if (!def || !Array.isArray(def.hooks)) continue;
|
|
for (const hook of def.hooks) {
|
|
currentTrusted.add(getHookKey(hook));
|
|
}
|
|
}
|
|
}
|
|
|
|
this.trustedHooks[projectPath] = Array.from(currentTrusted);
|
|
this.save();
|
|
}
|
|
}
|