fix: add Windows hooks support (PowerShell script + os.tmpdir state file)#2
fix: add Windows hooks support (PowerShell script + os.tmpdir state file)#2bertheto wants to merge 2 commits intoofershap:mainfrom
Conversation
…ile)
On Windows, the hooks mode was silently broken for two reasons:
1. The hook script is a bash `.sh` file — not executable on Windows.
2. The state file path was hardcoded to `/tmp/cursor-office-state.json`,
which does not exist on Windows.
Changes:
- hooks/cursor-office-hook.ps1 (new): PowerShell equivalent of the bash
hook script. Reads hook event JSON from stdin via $input, maps tool
names to activity states, writes JSON to %TEMP%\cursor-office-state.json.
- src/hooksInstaller.ts: detect Windows via os.platform() === 'win32'.
- STATE_FILE now uses os.tmpdir() on Windows, /tmp on Unix.
- getHookScriptPath() returns .ps1 on Windows, .sh on Unix.
- buildHookCommand() uses powershell.exe -NoProfile -ExecutionPolicy
Bypass -File on Windows, bash on Unix.
- IS_WINDOWS exported for use in cursorWatcher.ts.
- src/cursorWatcher.ts: skip fs.watch() on the state file directory on
Windows (%TEMP% is too busy and generates excessive noise). The 1s
polling interval started by startHooksWatcher() is sufficient on Windows.
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request addresses and resolves critical issues that prevented the Cursor Office hooks mode from functioning correctly on Windows. By introducing platform-specific scripts and path handling, and optimizing file system watching for Windows environments, the changes ensure that the office character now accurately reacts to agent activity, providing a consistent user experience across operating systems. Highlights
🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console. Changelog
Activity
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Code Review
This pull request adds support for Windows hooks by introducing a PowerShell script and making the necessary adjustments in the TypeScript code to handle platform differences. The changes are well-structured and address the issues described. I have one suggestion for the new PowerShell script to improve its robustness by using built-in JSON handling cmdlets instead of manual parsing and string construction.
hooks/cursor-office-hook.ps1
Outdated
| $input_data = $input | Out-String | ||
|
|
||
| function Get-JsonValue { | ||
| param([string]$json, [string]$key) | ||
| if ($json -match """$key""\s*:\s*""([^""]+)""") { return $Matches[1] } | ||
| return "" | ||
| } | ||
|
|
||
| $event = Get-JsonValue $input_data "hook_event_name" | ||
| $ts = [int][double]::Parse((Get-Date -UFormat %s)) | ||
|
|
||
| switch ($event) { | ||
| "preToolUse" { | ||
| $tool = Get-JsonValue $input_data "tool_name" | ||
| $activity = switch ($tool) { | ||
| { $_ -in "Read","Glob","SemanticSearch","Grep" } { "reading" } | ||
| { $_ -in "Write","StrReplace","EditNotebook","Delete" } { "editing" } | ||
| "Shell" { "running" } | ||
| "Task" { "phoning" } | ||
| default { "typing" } | ||
| } | ||
| "{""activity"":""$activity"",""tool"":""$tool"",""ts"":$ts}" | Set-Content $stateFile -Encoding UTF8 | ||
| } | ||
| "subagentStart" { | ||
| "{""activity"":""phoning"",""ts"":$ts}" | Set-Content $stateFile -Encoding UTF8 | ||
| } | ||
| "subagentStop" { | ||
| "{""activity"":""typing"",""ts"":$ts}" | Set-Content $stateFile -Encoding UTF8 | ||
| } | ||
| "stop" { | ||
| $status = Get-JsonValue $input_data "status" | ||
| $activity = switch ($status) { | ||
| "completed" { "celebrating" } | ||
| "error" { "error" } | ||
| default { "idle" } | ||
| } | ||
| "{""activity"":""$activity"",""ts"":$ts}" | Set-Content $stateFile -Encoding UTF8 | ||
| } | ||
| "beforeSubmitPrompt" { | ||
| "{""activity"":""idle"",""ts"":$ts}" | Set-Content $stateFile -Encoding UTF8 | ||
| } | ||
| } |
There was a problem hiding this comment.
Using a custom regex-based function to parse JSON and manual string building for output is fragile. It's better to use PowerShell's built-in ConvertFrom-Json and ConvertTo-Json cmdlets for robust JSON handling. This refactoring also centralizes the file-writing logic, making the script cleaner and easier to maintain.
$input_data = $input | Out-String
$data = $input_data | ConvertFrom-Json
$event = $data.hook_event_name
$ts = [int][double]::Parse((Get-Date -UFormat %s))
$state = $null
switch ($event) {
"preToolUse" {
$tool = $data.tool_name
$activity = switch ($tool) {
{ $_ -in "Read","Glob","SemanticSearch","Grep" } { "reading" }
{ $_ -in "Write","StrReplace","EditNotebook","Delete" } { "editing" }
"Shell" { "running" }
"Task" { "phoning" }
default { "typing" }
}
$state = @{ activity = $activity; tool = $tool; ts = $ts }
}
"subagentStart" {
$state = @{ activity = "phoning"; ts = $ts }
}
"subagentStop" {
$state = @{ activity = "typing"; ts = $ts }
}
"stop" {
$status = $data.status
$activity = switch ($status) {
"completed" { "celebrating" }
"error" { "error" }
default { "idle" }
}
$state = @{ activity = $activity; ts = $ts }
}
"beforeSubmitPrompt" {
$state = @{ activity = "idle"; ts = $ts }
}
}
if ($state) {
$state | ConvertTo-Json -Compress | Set-Content $stateFile -Encoding UTF8
}
Replace the custom regex-based JSON parsing and manual string building with PowerShell's built-in ConvertFrom-Json and ConvertTo-Json cmdlets. Centralizes file-writing via a $state hashtable, making the script more robust and easier to maintain. Suggested by Gemini Code Assist review on PR ofershap#2.
|
Fixed in 7103e52: replaced the regex-based \Get-JsonValue\ function and manual string building with \ConvertFrom-Json\ / \ConvertTo-Json -Compress. All switch branches now populate a \\ hashtable, and a single \ConvertTo-Json | Set-Content\ at the end writes the file. Much cleaner. |
Problem
On Windows, the hooks mode was completely broken for two separate reasons:
1. Bash script not executable on Windows
The hook script
hooks/cursor-office-hook.shrequires bash. On Windows there is no bash in PATH by default, sobuildHookCommand()produces a command that fails silently. The hooks are registered inhooks.jsonbut do nothing.2. Hardcoded
/tmppathhooksInstaller.tshardcodesSTATE_FILE = '/tmp/cursor-office-state.json'. On Windows/tmpdoes not exist, sostartHooksWatcher()polls a file that can never be created.The result: hooks mode appears active (the
.shmarker is present inhooks.json) but the office character never reacts to any agent activity.Solution
hooks/cursor-office-hook.ps1(new)PowerShell equivalent of
cursor-office-hook.sh. Reads the hook event JSON from$input(stdin), maps tool names to activity strings using a switch, writes JSON state to$env:TEMP\cursor-office-state.json.Supports the same events:
preToolUse,subagentStart,subagentStop,stop,beforeSubmitPrompt.src/hooksInstaller.tsIS_WINDOWS:os.platform() === 'win32', exported for use in the watcher.STATE_FILE: usesos.tmpdir()on Windows,/tmpon Unix (unchanged).getHookScriptPath(): returns.ps1on Windows,.shon Unix.buildHookCommand(): usespowershell.exe -NoProfile -ExecutionPolicy Bypass -File "..."on Windows,bash "..."on Unix.src/cursorWatcher.tsOn Windows,
%TEMP%is extremely busy —fs.watch()on that directory fires hundreds of events per second and would cause CPU spikes. The methodstartHooksWatcher()already starts a 1-second polling interval, which is sufficient. Thefs.watch()call is now skipped on Windows.Testing
Tested on Windows 10 (22H2) with Cursor 0.47. After:
cursor-office-hook.ps1into the extension'shooks/folder~/.cursor/hooks.jsonThe office character reacts correctly in real time: sitting at desk and typing when the agent is thinking, reading icon when using Read/Grep/Glob, running icon for Shell, editing for StrReplace/Write, phone call for Task (subagent delegation), and celebrating on task completion.