diff --git a/hooks/cursor-office-hook.ps1 b/hooks/cursor-office-hook.ps1 new file mode 100644 index 0000000..d25a72d --- /dev/null +++ b/hooks/cursor-office-hook.ps1 @@ -0,0 +1,45 @@ +# Cursor Office hook script for Windows +# Reads hook event JSON from stdin, writes activity state to a temp file. +$stateFile = Join-Path $env:TEMP "cursor-office-state.json" +$data = $input | Out-String | 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" { + $activity = switch ($data.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 +} + +exit 0 diff --git a/src/cursorWatcher.ts b/src/cursorWatcher.ts index 1abf84e..2348b0a 100644 --- a/src/cursorWatcher.ts +++ b/src/cursorWatcher.ts @@ -3,7 +3,7 @@ import * as path from 'path'; import * as os from 'os'; import * as vscode from 'vscode'; import { parseTranscriptLine, ParsedStatus } from './transcriptParser'; -import { isHooksInstalled, getStateFilePath } from './hooksInstaller'; +import { isHooksInstalled, getStateFilePath, IS_WINDOWS } from './hooksInstaller'; export class CursorWatcher implements vscode.Disposable { private watchers: fs.FSWatcher[] = []; @@ -105,14 +105,17 @@ export class CursorWatcher implements vscode.Disposable { } }; - try { - this.hooksWatcher = fs.watch(path.dirname(stateFile), { persistent: false }, (_event, filename) => { - if (filename === path.basename(stateFile)) { - pollState(); - } - }); - } catch { - this.log.appendLine('[hooks] fs.watch on /tmp failed, falling back to polling'); + // On Windows %TEMP% is too busy for fs.watch — polling at 1s is sufficient. + if (!IS_WINDOWS) { + try { + this.hooksWatcher = fs.watch(path.dirname(stateFile), { persistent: false }, (_event, filename) => { + if (filename === path.basename(stateFile)) { + pollState(); + } + }); + } catch { + this.log.appendLine('[hooks] fs.watch on state dir failed, falling back to polling'); + } } this.scanInterval = setInterval(pollState, 1000); diff --git a/src/hooksInstaller.ts b/src/hooksInstaller.ts index 298cec0..acf6a0e 100644 --- a/src/hooksInstaller.ts +++ b/src/hooksInstaller.ts @@ -6,7 +6,12 @@ import * as vscode from 'vscode'; const HOOKS_DIR = path.join(os.homedir(), '.cursor'); const HOOKS_JSON = path.join(HOOKS_DIR, 'hooks.json'); const HOOK_MARKER = 'cursor-office-hook'; -const STATE_FILE = '/tmp/cursor-office-state.json'; + +const IS_WINDOWS = os.platform() === 'win32'; + +const STATE_FILE = IS_WINDOWS + ? path.join(os.tmpdir(), 'cursor-office-state.json') + : '/tmp/cursor-office-state.json'; interface HooksConfig { version: number; @@ -14,11 +19,16 @@ interface HooksConfig { } function getHookScriptPath(extensionPath: string): string { - return path.join(extensionPath, 'hooks', 'cursor-office-hook.sh'); + const script = IS_WINDOWS ? 'cursor-office-hook.ps1' : 'cursor-office-hook.sh'; + return path.join(extensionPath, 'hooks', script); } function buildHookCommand(extensionPath: string): string { - return `bash "${getHookScriptPath(extensionPath)}"`; + const scriptPath = getHookScriptPath(extensionPath); + if (IS_WINDOWS) { + return `powershell.exe -NoProfile -ExecutionPolicy Bypass -File "${scriptPath}"`; + } + return `bash "${scriptPath}"`; } export function isHooksInstalled(): boolean { @@ -35,6 +45,8 @@ export function getStateFilePath(): string { return STATE_FILE; } +export { IS_WINDOWS }; + export function installHooks(extensionPath: string): { success: boolean; message: string } { const hookCmd = buildHookCommand(extensionPath);