feat: fix Cursor hook delivery and improve notify workflow

Route hook notifications through /dev/tty and client_tty so OSC reaches
Ghostty when Cursor captures stdout. Add afterAgentResponse hook, hook
logging, debounce, split test modes, and use directory name for titles.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-05-26 12:46:50 +08:00
parent aa9c2b8ea5
commit 021fc3a828
20 changed files with 757 additions and 76 deletions
+77 -6
View File
@@ -9,6 +9,7 @@ import (
"github.com/longbin/agent-notify/internal/context"
"github.com/longbin/agent-notify/internal/hook"
"github.com/longbin/agent-notify/internal/install"
"github.com/longbin/agent-notify/internal/logx"
"github.com/longbin/agent-notify/internal/notify"
"github.com/longbin/agent-notify/internal/tmux"
)
@@ -33,9 +34,11 @@ func run(cmd string, args []string) error {
case "install":
return cmdInstall(args)
case "test":
return cmdTest()
return cmdTest(args)
case "doctor":
return cmdDoctor()
case "logs":
return cmdLogs(args)
case "help", "-h", "--help":
printUsage()
return nil
@@ -80,7 +83,7 @@ func cmdHook(args []string) error {
case "claude":
return hook.RunClaude(os.Stdin, cfg, event, os.Stdout)
default:
return fmt.Errorf("unknown agent %q", agent)
return fmt.Errorf("unknown agent %q (use cursor stop|response|tool or claude stop|idle)", agent)
}
}
@@ -95,14 +98,79 @@ func cmdInstall(args []string) error {
return fmt.Errorf("use --all")
}
func cmdTest() error {
return notify.TestNotification("agent-notify", "测试通知 — 如果你看到这条,说明配置正确")
func cmdTest(args []string) error {
mode := "cursor"
apply := false
tryAll := false
for _, arg := range args {
switch arg {
case "-v", "--verbose":
// accepted for compatibility; status always prints to stderr now
case "--apply":
apply = true
case "--try-all":
tryAll = true
case "cursor", "claude":
mode = arg
case "help", "-h", "--help":
fmt.Fprint(os.Stderr, `Usage: agent-notify test [cursor|claude] [flags]
Flags:
--apply Claude only: emit terminalSequence to terminal
--try-all Cursor only: try every delivery method (debug)
Examples:
agent-notify test cursor
agent-notify test cursor -v
agent-notify test cursor --try-all
agent-notify test claude --apply
`)
return nil
default:
return fmt.Errorf("unknown test argument %q (try: agent-notify test help)", arg)
}
}
switch mode {
case "cursor":
_, err := notify.TestCursor("", "", tryAll)
return err
case "claude":
_, err := notify.TestClaude("", "", apply, os.Stdout)
return err
default:
return fmt.Errorf("usage: agent-notify test [cursor|claude] [--apply] [--try-all]")
}
}
func cmdLogs(args []string) error {
fs := flag.NewFlagSet("logs", flag.ExitOnError)
tail := fs.Int("tail", 30, "number of recent lines")
_ = fs.Parse(args)
lines, err := logx.Tail(*tail)
if err != nil {
return err
}
if len(lines) == 0 {
fmt.Printf("no log entries yet (log file: %s)\n", logx.Path())
return nil
}
fmt.Printf("# %s\n", logx.Path())
for _, line := range lines {
fmt.Println(line)
}
return nil
}
func cmdDoctor() error {
fmt.Println("agent-notify doctor")
if tmux.InTmux() {
fmt.Println("✓ running inside tmux")
if tmux.IsSSHSession() {
fmt.Println("✓ SSH session detected (prefers passthrough delivery)")
}
ok, val, err := tmux.AllowPassthroughEnabled()
if err != nil {
fmt.Printf("✗ allow-passthrough check failed: %v\n", err)
@@ -118,6 +186,7 @@ func cmdDoctor() error {
}
cfgPath := config.DefaultPath()
fmt.Printf(" config=%s\n", cfgPath)
fmt.Printf(" hook_log=%s\n", logx.Path())
return nil
}
@@ -125,10 +194,12 @@ func printUsage() {
fmt.Fprint(os.Stderr, `Usage: agent-notify <command>
Commands:
send [--title T] [--body B] [--event stop|idle|tool]
hook cursor stop|tool
hook cursor stop|response|tool
hook claude stop|idle
install [--all] [--force]
test
test cursor [--try-all]
test claude [--apply]
logs [--tail 30]
doctor
`)
}