diff --git a/packages/types/src/vscode-extension-host.ts b/packages/types/src/vscode-extension-host.ts
index c7bc472c68..87aec143b5 100644
--- a/packages/types/src/vscode-extension-host.ts
+++ b/packages/types/src/vscode-extension-host.ts
@@ -623,6 +623,7 @@ export interface WebviewMessage {
| "hooksOpenConfigFolder"
| "hooksDeleteHook"
| "hooksOpenHookFile"
+ | "hooksCreateNew"
text?: string
editedMessageContent?: string
tab?: "settings" | "history" | "mcp" | "modes" | "chat" | "marketplace" | "cloud"
diff --git a/src/core/webview/webviewMessageHandler.ts b/src/core/webview/webviewMessageHandler.ts
index 3aa92e84b7..06287acaf3 100644
--- a/src/core/webview/webviewMessageHandler.ts
+++ b/src/core/webview/webviewMessageHandler.ts
@@ -3601,6 +3601,62 @@ export const webviewMessageHandler = async (
break
}
+ case "hooksCreateNew": {
+ // Check if hooks experiment is enabled
+ const hooksExperimentsState = getGlobalState("experiments") ?? experimentDefault
+ if (!experiments.isEnabled(hooksExperimentsState, EXPERIMENT_IDS.HOOKS)) {
+ break
+ }
+
+ try {
+ const cwd = provider.cwd
+ const hooksPath = path.join(cwd, ".roo", "hooks")
+
+ // Create directory if it doesn't exist
+ await fs.mkdir(hooksPath, { recursive: true })
+
+ const exampleFilePath = path.join(hooksPath, "example.yaml")
+
+ // Check if file already exists
+ const exists = await fileExistsAtPath(exampleFilePath)
+ if (exists) {
+ // Open existing file instead of overwriting
+ const uri = vscode.Uri.file(exampleFilePath)
+ const doc = await vscode.workspace.openTextDocument(uri)
+ await vscode.window.showTextDocument(doc)
+ break
+ }
+
+ // Create the example hook file
+ const exampleContent = `version: "1"
+hooks:
+ PreToolUse:
+ - id: example-hook
+ matcher: "write_to_file|edit_file|apply_diff|apply_patch"
+ enabled: true
+ command: 'echo "Verification hook triggered"'
+ timeout: 5
+`
+ await safeWriteText(exampleFilePath, exampleContent)
+
+ // Open the file in the editor
+ const uri = vscode.Uri.file(exampleFilePath)
+ const doc = await vscode.workspace.openTextDocument(uri)
+ await vscode.window.showTextDocument(doc)
+
+ // Reload hooks config to pick up the new file
+ const hookManager = provider.getHookManager()
+ if (hookManager) {
+ await hookManager.reloadHooksConfig()
+ await provider.postStateToWebview()
+ }
+ } catch (error) {
+ provider.log(`Failed to create hook file: ${error instanceof Error ? error.message : String(error)}`)
+ vscode.window.showErrorMessage("Failed to create hook configuration file")
+ }
+ break
+ }
+
default: {
// console.log(`Unhandled message type: ${message.type}`)
//
diff --git a/webview-ui/src/components/settings/HooksSettings.tsx b/webview-ui/src/components/settings/HooksSettings.tsx
index 0856ec3c40..49ba78b798 100644
--- a/webview-ui/src/components/settings/HooksSettings.tsx
+++ b/webview-ui/src/components/settings/HooksSettings.tsx
@@ -1,5 +1,5 @@
import React, { useCallback, useEffect, useState } from "react"
-import { RefreshCw, FolderOpen, AlertTriangle, Clock, Zap, X } from "lucide-react"
+import { RefreshCw, FolderOpen, AlertTriangle, Clock, Zap, X, Plus } from "lucide-react"
import { VSCodePanels, VSCodePanelTab, VSCodePanelView } from "@vscode/webview-ui-toolkit/react"
import { useAppTranslation } from "@src/i18n/TranslationContext"
import { useExtensionState } from "@src/context/ExtensionStateContext"
@@ -73,6 +73,10 @@ export const HooksSettings: React.FC = () => {
setTimeout(() => setIsUpdatingAllEnabled(false), 500)
}, [])
+ const handleCreateNewHook = useCallback(() => {
+ vscode.postMessage({ type: "hooksCreateNew" })
+ }, [])
+
const enabledHooks = hooks?.enabledHooks || []
const hasProjectHooks = hooks?.hasProjectHooks || false
const snapshotTimestamp = hooks?.snapshotTimestamp
@@ -179,7 +183,7 @@ export const HooksSettings: React.FC = () => {
{/* Hook Activity Log */}