feat: enhance hook templates management by adding protection category and improving command safety

This commit is contained in:
catlog22
2026-03-03 11:33:00 +08:00
parent 9cfd5c05fc
commit 08564d487a
6 changed files with 117 additions and 132 deletions

View File

@@ -274,7 +274,8 @@ function getCategoryName(category: TemplateCategory, formatMessage: ReturnType<t
notification: formatMessage({ id: 'cliHooks.templates.categories.notification' }),
indexing: formatMessage({ id: 'cliHooks.templates.categories.indexing' }),
automation: formatMessage({ id: 'cliHooks.templates.categories.automation' }),
utility: formatMessage({ id: 'cliHooks.templates.categories.utility' })
utility: formatMessage({ id: 'cliHooks.templates.categories.utility' }),
protection: formatMessage({ id: 'cliHooks.templates.categories.protection' }),
};
return names[category];
}
@@ -304,7 +305,7 @@ export function HookQuickTemplates({
}, []);
// Define category order
const categoryOrder: TemplateCategory[] = ['notification', 'indexing', 'automation'];
const categoryOrder: TemplateCategory[] = ['notification', 'indexing', 'automation', 'protection', 'utility'];
const handleInstall = async (templateId: string) => {
await onInstallTemplate(templateId);

View File

@@ -79,12 +79,6 @@ interface SkillContextConfig {
// All templates use `ccw hook template exec <id> --stdin` format
// This avoids Windows Git Bash quote handling issues
interface HookTemplate {
event: string;
matcher: string;
timeout?: number;
}
// Template IDs that map to backend templates
const TEMPLATE_IDS = {
'memory-update-queue': 'memory-auto-compress',
@@ -106,72 +100,6 @@ const DANGER_OPTIONS = [
{ id: 'permission-change', templateId: 'danger-permission-change', labelKey: 'cliHooks.wizards.dangerProtection.options.permissionChange', descKey: 'cliHooks.wizards.dangerProtection.options.permissionChangeDesc' },
] as const;
// ========== convertToClaudeCodeFormat (ported from old hook-manager.js) ==========
function convertToClaudeCodeFormat(hookData: {
command: string;
args: string[];
matcher?: string;
timeout?: number;
}): Record<string, unknown> {
let commandStr = hookData.command || '';
if (hookData.args && Array.isArray(hookData.args)) {
if (commandStr === 'bash' && hookData.args.length >= 2 && hookData.args[0] === '-c') {
const script = hookData.args[1];
const escapedScript = script.replace(/'/g, "'\\''");
commandStr = `bash -c '${escapedScript}'`;
if (hookData.args.length > 2) {
const additionalArgs = hookData.args.slice(2).map(arg =>
arg.includes(' ') && !arg.startsWith('"') && !arg.startsWith("'")
? `"${arg.replace(/"/g, '\\"')}"`
: arg
);
commandStr += ' ' + additionalArgs.join(' ');
}
} else if (commandStr === 'node' && hookData.args.length >= 2 && hookData.args[0] === '-e') {
const script = hookData.args[1];
const isWindows = typeof navigator !== 'undefined' && navigator.userAgent.includes('Win');
if (isWindows) {
const escapedScript = script.replace(/"/g, '\\"');
commandStr = `node -e "${escapedScript}"`;
} else {
const escapedScript = script.replace(/'/g, "'\\''");
commandStr = `node -e '${escapedScript}'`;
}
if (hookData.args.length > 2) {
const additionalArgs = hookData.args.slice(2).map(arg =>
arg.includes(' ') && !arg.startsWith('"') && !arg.startsWith("'")
? `"${arg.replace(/"/g, '\\"')}"`
: arg
);
commandStr += ' ' + additionalArgs.join(' ');
}
} else {
const quotedArgs = hookData.args.map(arg =>
arg.includes(' ') && !arg.startsWith('"') && !arg.startsWith("'")
? `"${arg.replace(/"/g, '\\"')}"`
: arg
);
commandStr = `${commandStr} ${quotedArgs.join(' ')}`.trim();
}
}
const converted: Record<string, unknown> = {
hooks: [{
type: 'command',
command: commandStr,
...(hookData.timeout ? { timeout: Math.ceil(hookData.timeout / 1000) } : {}),
}],
};
if (hookData.matcher) {
converted.matcher = hookData.matcher;
}
return converted;
}
// ========== Wizard Definitions ==========
const WIZARD_METADATA = {

View File

@@ -28,7 +28,6 @@ import { Card } from '@/components/ui/Card';
import { Badge } from '@/components/ui/Badge';
import { HookCard, HookFormDialog, HookQuickTemplates, HookWizard, type HookCardData, type HookFormData, type HookTriggerType, HOOK_TEMPLATES, type WizardType } from '@/components/hook';
import { useHooks, useToggleHook } from '@/hooks';
import { installHookTemplate } from '@/lib/api';
import { cn } from '@/lib/utils';
// ========== Types ==========