Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions hooks/cursor-office-hook.ps1
Original file line number Diff line number Diff line change
@@ -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
21 changes: 12 additions & 9 deletions src/cursorWatcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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[] = [];
Expand Down Expand Up @@ -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);
Expand Down
18 changes: 15 additions & 3 deletions src/hooksInstaller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,29 @@ 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;
hooks: Record<string, Array<{ command: string; matcher?: string }>>;
}

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 {
Expand All @@ -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);

Expand Down