Merge pull request #1735 from entireio/fix/windows-cursor-hooks-scoop-1424 · Entire
Merge pull request #1735 from entireio/fix/windows-cursor-hooks-scoop-1424
b29d739→main·
karthik-rameshkumar·4d ago·5 files·+218 added/-39 removed
Fix Windows Cursor hooks + Scoop app-dir naming (#1424)
Changes
5
M.goreleaser.yaml+8/-1
cmd/entire/cli
agent/cursor
Mhooks.go+43/-36
Mhooks_test.go+90
strategy
Mhooks.go+24/-2
Mhooks_test.go+53
99 unmodified lines
100
101
102
103
103
104
105
106
107
108
109
110
111
112
113
99 unmodified lines
prerelease: auto
scoops:
- repository:
# Name the manifest (and therefore the Scoop app directory) "entire". Without
# this, goreleaser defaults the manifest name to the project name, which
# resolves to the repo name ("cli"), so `scoop install` lands the binary in
# …\scoop\apps\cli\current\entire.exe. That mismatched app-dir name is
# surprising ("scoop install cli"?) and fed the Windows hook-path bug in
# https://github.com/entireio/cli/issues/1424.
- name: entire
repository:
owner: entireio
name: scoop-bucket
token: "{{ .Env.TAP_GITHUB_TOKEN }}"
M.goreleaser.yaml+8/-1
129 unmodified lines
130
131
132
133
134
135
136
137
138
139
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
175 unmodified lines
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
129 unmodified lines
subagentStartCmd := cmdPrefix + HookNameSubagentStart
subagentEndCmd := cmdPrefix + HookNameSubagentStop
if !localDev {
sessionStartCmd = agent.WrapProductionSilentHookCommand(sessionStartCmd)
sessionEndCmd = agent.WrapProductionSilentHookCommand(sessionEndCmd)
beforeSubmitPromptCmd = agent.WrapProductionSilentHookCommand(beforeSubmitPromptCmd)
stopCmd = agent.WrapProductionSilentHookCommand(stopCmd)
preCompactCmd = agent.WrapProductionSilentHookCommand(preCompactCmd)
subagentStartCmd = agent.WrapProductionSilentHookCommand(subagentStartCmd)
subagentEndCmd = agent.WrapProductionSilentHookCommand(subagentEndCmd)
// Cursor spawns hook commands through the native OS shell (cmd.exe on
// Windows), so a `sh -c '…'` wrapper silently fails to launch on a
// Windows host without a working POSIX sh — no hook fires and, because
// this is the *silent* wrapper, no error surfaces (issue #1424).
// UseWindowsProductionHooks probes for a runnable sh and only swaps in
// the native cmd.exe wrapper when one is absent, so this is a no-op on
// hosts (incl. all non-Windows) where the sh wrapper already works.
useWindowsHooks := agent.UseWindowsProductionHooks(ctx, localDev)
sessionStartCmd = agent.WrapProductionSilentHookCommandForOS(sessionStartCmd, useWindowsHooks)
sessionEndCmd = agent.WrapProductionSilentHookCommandForOS(sessionEndCmd, useWindowsHooks)
beforeSubmitPromptCmd = agent.WrapProductionSilentHookCommandForOS(beforeSubmitPromptCmd, useWindowsHooks)
stopCmd = agent.WrapProductionSilentHookCommandForOS(stopCmd, useWindowsHooks)
preCompactCmd = agent.WrapProductionSilentHookCommandForOS(preCompactCmd, useWindowsHooks)
subagentStartCmd = agent.WrapProductionSilentHookCommandForOS(subagentStartCmd, useWindowsHooks)
subagentEndCmd = agent.WrapProductionSilentHookCommandForOS(subagentEndCmd, useWindowsHooks)
}
count := 0
// Add hooks if they don't exist
if !hookCommandExists(sessionStart, sessionStartCmd) {
sessionStart = append(sessionStart, CursorHookEntry{Command: sessionStartCmd})
count++
}
if !hookCommandExists(sessionEnd, sessionEndCmd) {
sessionEnd = append(sessionEnd, CursorHookEntry{Command: sessionEndCmd})
count++
}
if !hookCommandExists(beforeSubmitPrompt, beforeSubmitPromptCmd) {
beforeSubmitPrompt = append(beforeSubmitPrompt, CursorHookEntry{Command: beforeSubmitPromptCmd})
count++
}
if !hookCommandExists(stop, stopCmd) {
stop = append(stop, CursorHookEntry{Command: stopCmd})
count++
}
if !hookCommandExists(preCompact, preCompactCmd) {
preCompact = append(preCompact, CursorHookEntry{Command: preCompactCmd})
count++
}
if !hookCommandExists(subagentStart, subagentStartCmd) {
subagentStart = append(subagentStart, CursorHookEntry{Command: subagentStartCmd})
count++
}
if !hookCommandExists(subagentStop, subagentEndCmd) {
subagentStop = append(subagentStop, CursorHookEntry{Command: subagentEndCmd})
count++
}
// Sync each hook to its desired command. syncEntireHook replaces any
// stale-form Entire hook (e.g. an sh-wrapped entry from a previous install)
// with the current command even without --force, so a wrapper-form change —
// notably the sh↔cmd.exe migration driven by UseWindowsProductionHooks when
// a Windows host gains or loses a working POSIX sh — cleanly replaces rather
// than leaving a dead duplicate entry that could double-fire (issue #1424).
sessionStart, count = syncEntireHook(sessionStart, sessionStartCmd, count)
sessionEnd, count = syncEntireHook(sessionEnd, sessionEndCmd, count)
beforeSubmitPrompt, count = syncEntireHook(beforeSubmitPrompt, beforeSubmitPromptCmd, count)
stop, count = syncEntireHook(stop, stopCmd, count)
preCompact, count = syncEntireHook(preCompact, preCompactCmd, count)
subagentStart, count = syncEntireHook(subagentStart, subagentStartCmd, count)
subagentStop, count = syncEntireHook(subagentStop, subagentEndCmd, count)
if count == 0 {
return 0, nil
}
175 unmodified lines
// Helper functions for hook management
// syncEntireHook ensures entries contains exactly the given Entire hook command
// for this hook type. If command is already present it is a no-op. Otherwise any
// existing Entire hook (in any wrapper form) is removed before appending command,
// so a changed wrapper form replaces the stale one rather than duplicating it.
// Non-Entire entries are preserved. count is incremented when a change is made.
func syncEntireHook(entries []CursorHookEntry, command string, count int) ([]CursorHookEntry, int) {
if hookCommandExists(entries, command) {
return entries, count
}
if hasEntireHook(entries) {
entries = removeEntireHooks(entries)
}
return append(entries, CursorHookEntry{Command: command}), count + 1
}
func hookCommandExists(entries []CursorHookEntry, command string) bool {
for _, entry := range entries {
if entry.Command == command {