feat: implement agent-notify CLI with hooks and tmux passthrough
Add OSC 777 notification sender, Cursor/Claude hook adapters, config/install commands, and README for Ghostty + tmux setup. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+113
-11
@@ -1,8 +1,16 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/longbin/agent-notify/internal/config"
|
||||
"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/notify"
|
||||
"github.com/longbin/agent-notify/internal/tmux"
|
||||
)
|
||||
|
||||
func main() {
|
||||
@@ -10,23 +18,117 @@ func main() {
|
||||
printUsage()
|
||||
os.Exit(1)
|
||||
}
|
||||
switch os.Args[1] {
|
||||
case "send", "hook", "install", "test", "doctor", "help", "-h", "--help":
|
||||
fmt.Fprintf(os.Stderr, "agent-notify: %s not implemented yet\n", os.Args[1])
|
||||
os.Exit(1)
|
||||
default:
|
||||
printUsage()
|
||||
if err := run(os.Args[1], os.Args[2:]); err != nil {
|
||||
fmt.Fprintln(os.Stderr, "agent-notify:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func run(cmd string, args []string) error {
|
||||
switch cmd {
|
||||
case "send":
|
||||
return cmdSend(args)
|
||||
case "hook":
|
||||
return cmdHook(args)
|
||||
case "install":
|
||||
return cmdInstall(args)
|
||||
case "test":
|
||||
return cmdTest()
|
||||
case "doctor":
|
||||
return cmdDoctor()
|
||||
case "help", "-h", "--help":
|
||||
printUsage()
|
||||
return nil
|
||||
default:
|
||||
printUsage()
|
||||
return fmt.Errorf("unknown command %q", cmd)
|
||||
}
|
||||
}
|
||||
|
||||
func cmdSend(args []string) error {
|
||||
fs := flag.NewFlagSet("send", flag.ExitOnError)
|
||||
title := fs.String("title", "", "notification title")
|
||||
body := fs.String("body", "", "notification body")
|
||||
event := fs.String("event", "stop", "event type")
|
||||
_ = fs.Parse(args)
|
||||
cfg, err := config.LoadDefault()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if *title == "" {
|
||||
meta := context.MetaFromEnv("Agent", *event)
|
||||
*title = context.Render(cfg.Notify.TitleTemplate, meta)
|
||||
}
|
||||
if *body == "" {
|
||||
*body = cfg.BodyForEvent(*event)
|
||||
}
|
||||
return notify.SendAuto(cfg.Notify.Protocol, *title, *body)
|
||||
}
|
||||
|
||||
func cmdHook(args []string) error {
|
||||
if len(args) < 2 {
|
||||
return fmt.Errorf("usage: agent-notify hook <cursor|claude> <event>")
|
||||
}
|
||||
agent, event := args[0], args[1]
|
||||
cfg, err := config.LoadDefault()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch agent {
|
||||
case "cursor":
|
||||
return hook.RunCursor(os.Stdin, cfg, event, os.Stdout)
|
||||
case "claude":
|
||||
return hook.RunClaude(os.Stdin, cfg, event, os.Stdout)
|
||||
default:
|
||||
return fmt.Errorf("unknown agent %q", agent)
|
||||
}
|
||||
}
|
||||
|
||||
func cmdInstall(args []string) error {
|
||||
fs := flag.NewFlagSet("install", flag.ExitOnError)
|
||||
all := fs.Bool("all", false, "install all")
|
||||
force := fs.Bool("force", false, "overwrite existing hooks")
|
||||
_ = fs.Parse(args)
|
||||
if *all || len(args) == 0 {
|
||||
return install.InstallAll(*force)
|
||||
}
|
||||
return fmt.Errorf("use --all")
|
||||
}
|
||||
|
||||
func cmdTest() error {
|
||||
return notify.TestNotification("agent-notify", "测试通知 — 如果你看到这条,说明配置正确")
|
||||
}
|
||||
|
||||
func cmdDoctor() error {
|
||||
fmt.Println("agent-notify doctor")
|
||||
if tmux.InTmux() {
|
||||
fmt.Println("✓ running inside tmux")
|
||||
ok, val, err := tmux.AllowPassthroughEnabled()
|
||||
if err != nil {
|
||||
fmt.Printf("✗ allow-passthrough check failed: %v\n", err)
|
||||
} else if ok {
|
||||
fmt.Printf("✓ allow-passthrough=%s\n", val)
|
||||
} else {
|
||||
fmt.Printf("✗ allow-passthrough=%q — add to ~/.tmux.conf: set -g allow-passthrough on\n", val)
|
||||
}
|
||||
tty, _ := tmux.ClientTTY()
|
||||
fmt.Printf(" client_tty=%s\n", tty)
|
||||
} else {
|
||||
fmt.Println(" not in tmux (direct Ghostty mode)")
|
||||
}
|
||||
cfgPath := config.DefaultPath()
|
||||
fmt.Printf(" config=%s\n", cfgPath)
|
||||
return nil
|
||||
}
|
||||
|
||||
func printUsage() {
|
||||
fmt.Fprint(os.Stderr, `Usage: agent-notify <command>
|
||||
Commands:
|
||||
send Send a notification
|
||||
hook Agent hook entrypoint
|
||||
install Install hook configs
|
||||
test Send test notification
|
||||
doctor Check environment
|
||||
send [--title T] [--body B] [--event stop|idle|tool]
|
||||
hook cursor stop|tool
|
||||
hook claude stop|idle
|
||||
install [--all] [--force]
|
||||
test
|
||||
doctor
|
||||
`)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user