Skip to content
Merged
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
6 changes: 3 additions & 3 deletions .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "claude-notifications-go",
"description": "Agent Notifications marketplace; the legacy Claude Code ID is retained for compatibility",
"description": "Agent Notifications marketplace; the legacy Claude ID is retained for compatibility",
"owner": {
"name": "777genius",
"email": "iliyazelenkog@gmail.com"
Expand All @@ -13,15 +13,15 @@
"name": "claude-notifications-go",
"displayName": "Agent Notifications",
"source": "./",
"description": "Agent Notifications for Claude Code and Codex CLI (Go implementation)",
"description": "Agent Notifications for Claude and Codex CLI (Go implementation)",
"version": "1.46.1",
"author": {
"name": "777genius",
"email": "iliyazelenkog@gmail.com"
},
"repository": "https://github.com/777genius/agent-notifications",
"metadata": {
"renamePolicy": "Legacy Claude Code install identity. Do not rename; see docs/CLAUDE_PLUGIN_IDENTITY.md."
"renamePolicy": "Legacy Claude install identity. Do not rename; see docs/CLAUDE_PLUGIN_IDENTITY.md."
},
"license": "GPL-3.0",
"keywords": [
Expand Down
4 changes: 2 additions & 2 deletions .claude-plugin/plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@
"name": "claude-notifications-go",
"displayName": "Agent Notifications",
"version": "1.46.1",
"description": "Agent Notifications for Claude Code and Codex CLI (Go implementation)",
"description": "Agent Notifications for Claude and Codex CLI (Go implementation)",
"author": {
"name": "777genius",
"email": "iliyazelenkog@gmail.com"
},
"license": "GPL-3.0",
"repository": "https://github.com/777genius/agent-notifications",
"metadata": {
"renamePolicy": "Legacy Claude Code identity. Do not rename; see docs/CLAUDE_PLUGIN_IDENTITY.md."
"renamePolicy": "Legacy Claude identity. Do not rename; see docs/CLAUDE_PLUGIN_IDENTITY.md."
},
"keywords": ["notifications", "alerts", "productivity", "go"],
"commands": [
Expand Down
4 changes: 2 additions & 2 deletions .github/ISSUE_TEMPLATE/bug_report.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ body:
id: terminal
attributes:
label: Terminal
description: What terminal are you using Claude Code in?
description: What terminal are you using Claude in?
options:
- VS Code (integrated terminal)
- iTerm2
Expand Down Expand Up @@ -138,7 +138,7 @@ body:
label: Steps to reproduce
description: How can we reproduce this issue?
placeholder: |
1. Open terminal and start Claude Code
1. Open terminal and start Claude
2. Ask Claude to do something that takes a while
3. Wait for task to complete
4. No notification appears
Expand Down
4 changes: 2 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ If the work belongs to your employer, say so in the PR. Do not include someone e

- **Go 1.25.0+** (CI covers the minimum Go 1.25 and Go 1.26; automatic toolchain switching is disabled)
- **Make** (for build commands)
- **Claude Code** (tested on v2.0.15)
- **Claude** (tested on v2.0.15)

## Getting Started

Expand All @@ -37,7 +37,7 @@ make build
# Install plugin
/plugin install claude-notifications-go@claude-notifications-go

# Restart Claude Code for hooks to take effect
# Restart Claude for hooks to take effect

# Download binary and configure settings
/claude-notifications-go:init
Expand Down
16 changes: 7 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<h1 align="center"><a href="https://777genius.github.io/agent-notifications/">Agent Notifications</a></h1>

<p align="center">
<a href="#install-or-update"><img src="landing/public/agents/claude.png" width="64" height="64" alt="Claude Code" title="Claude Code" /></a>
<a href="#install-or-update"><img src="landing/public/agents/claude.png" width="64" height="64" alt="Claude" title="Claude" /></a>
&nbsp;&nbsp;
<a href="docs/CODEX.md"><img src="brand/codex-logo.svg" width="64" height="64" alt="Codex CLI" title="Codex CLI" /></a>
&nbsp;&nbsp;
Expand All @@ -19,7 +19,7 @@
[![Go Reference](https://pkg.go.dev/badge/github.com/777genius/agent-notifications.svg)](https://pkg.go.dev/github.com/777genius/agent-notifications)
[![codecov](https://codecov.io/gh/777genius/agent-notifications/graph/badge.svg?branch=main)](https://codecov.io/gh/777genius/agent-notifications)

Desktop notifications for **Claude Code, Codex CLI and OpenCode**. Know when a task finishes, an agent needs input, or a tool needs approval. Claude and Codex also support sounds and click-to-focus.
Desktop notifications for **Claude, Codex CLI and OpenCode**. Know when a task finishes, an agent needs input, or a tool needs approval. Claude and Codex also support sounds and click-to-focus.

<p align="center">
<img width="100%" alt="macOS, Windows, Linux (left to right)" src="docs/images/notification-platform-labels.svg" />
Expand All @@ -28,23 +28,21 @@ Desktop notifications for **Claude Code, Codex CLI and OpenCode**. Know when a t
<a href="docs/images/notifications-linux.png"><img width="31%" align="top" alt="Linux notification preview" src="docs/images/notifications-linux.png" /></a>
</p>

*Illustrative notification previews. Appearance varies by desktop environment.*

## Install Or Update

**[Guided installer (stable)](https://777genius.github.io/agent-notifications/#install)** or run:
**[Guided installer](https://777genius.github.io/agent-notifications/#install)** or run:

```bash
curl -fsSL https://777genius.github.io/agent-notifications/install.sh | bash
```

Choose **Claude Code**, **Codex CLI**, **OpenCode**, or a combination. Install the selected agent CLIs first. Run the same command to update.
Choose **Claude**, **Codex CLI**, **OpenCode**, or a combination. Install the selected agent CLIs first. Run the same command to update.

**Windows:** use **Git Bash**, not WSL, for a native Windows installation.

After setup:

- **Claude:** restart Claude Code.
- **Claude:** restart Claude.
- **Codex:** restart Codex, then review and trust the installed hooks in `/hooks`.
- **OpenCode:** restart OpenCode; on macOS, [grant notification permission](docs/opencode-notifications.md#macos-notification-permission).

Expand Down Expand Up @@ -86,7 +84,7 @@ Claude/Codex setup also installs the `agent-notify` MCP server and `agent-notifi

| Agent | Alerts | Sounds / click-to-focus | Details |
| --- | --- | --- | --- |
| **Claude Code** | Completions, reviews, questions, plans, session limits and API errors | Yes | [Notification types](docs/NOTIFICATION_TYPES.md) |
| **Claude** | Completions, reviews, questions, plans, session limits and API errors | Yes | [Notification types](docs/NOTIFICATION_TYPES.md) |
| **Codex CLI** | Turn completion and tool permissions; questions and errors depend on host events or final-message detection | Yes | [Setup and limits](docs/CODEX.md) |
| **OpenCode** | Root-session completion, questions, permissions and errors | No | [Setup and limits](docs/opencode-notifications.md) |
| **Gemini CLI** | Turn completion and tool permissions | No | [Prerelease setup and qualification](docs/gemini-notifications.md) |
Expand All @@ -95,7 +93,7 @@ OpenCode is tested with **1.18.33**; V2 is unsupported. Gemini is tested with **

## Settings

In Claude Code, run `/claude-notifications-go:settings` for the settings wizard or `/claude-notifications-go:sounds` to preview sounds.
In Claude, run `/claude-notifications-go:settings` for the settings wizard or `/claude-notifications-go:sounds` to preview sounds.

Use the installed launcher to find or safely inspect settings. The commands below assume `agent-notifications` is on your `PATH`; otherwise, use its full path.

Expand Down
12 changes: 6 additions & 6 deletions bin/bootstrap.sh
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ abort_if_wsl_environment() {
echo -e "${YELLOW}This command is running inside WSL, so it would install Linux binaries under /home instead of Windows binaries.${NC}" >&2
echo -e "${YELLOW}If you started this from PowerShell or Windows Terminal, your bash command is probably WSL bash, not Git Bash.${NC}" >&2
echo "" >&2
echo -e "${YELLOW}For Windows Claude Code, open Git Bash and use the installer at:${NC}" >&2
echo -e "${YELLOW}For Windows Claude, open Git Bash and use the installer at:${NC}" >&2
echo -e " https://777genius.github.io/agent-notifications/#install" >&2
echo "" >&2
echo -e "${YELLOW}For an intentional WSL install, set CLAUDE_NOTIFICATIONS_ALLOW_WSL=1 on the final bash command.${NC}" >&2
Expand All @@ -183,7 +183,7 @@ check_prerequisites() {
if { [ "${PRODUCT:-claude}" = claude ] || [ "$PRODUCT" = both ]; } && ! command -v "$_CLAUDE_EXEC" &>/dev/null; then
echo -e "${RED}✗ claude CLI not found in PATH${NC}" >&2
echo "" >&2
echo -e "${YELLOW}Install Claude Code first:${NC}" >&2
echo -e "${YELLOW}Install Claude first:${NC}" >&2
echo -e " npm install -g @anthropic-ai/claude-code" >&2
echo "" >&2
exit 1
Expand Down Expand Up @@ -825,7 +825,7 @@ install_plugin() {
cat > "$tmp_path" <<'SHIMEOF' 2>/dev/null || true
#!/bin/sh
# claude-notifications-go shim: forwards old cached hook path to current plugin installPath.
# This file is auto-generated by bootstrap.sh and is safe to delete after restarting Claude Code.
# This file is auto-generated by bootstrap.sh and is safe to delete after restarting Claude.
#
# Behavior:
# - Find current installPath from ~/.claude/plugins/installed_plugins.json
Expand Down Expand Up @@ -974,7 +974,7 @@ find_plugin_root() {

if [ ! -f "$INSTALLED_JSON" ]; then
echo -e "${RED}✗ installed_plugins.json not found at ${INSTALLED_JSON}${NC}" >&2
echo -e "${YELLOW} Try restarting Claude Code and running this script again.${NC}" >&2
echo -e "${YELLOW} Try restarting Claude and running this script again.${NC}" >&2
return 1
fi

Expand Down Expand Up @@ -1124,7 +1124,7 @@ print_success() {
{
case "$PRODUCT" in
claude|both)
printf ' Claude Code - installed; restart required.\n'
printf ' Claude - installed; restart required.\n'
printf ' Agent notification tool: %s.\n' "$CLAUDE_AGENT_NOTIFY_STATUS" ;;
esac
case "$PRODUCT" in
Expand Down Expand Up @@ -1990,7 +1990,7 @@ install_legacy_products() {
case ",$_PRODUCT_CSV," in *",$selected,"*) ;; *) continue ;; esac
progress_phase "$selected" registration state_requires_inspection
if [ "$selected" = claude ]; then
CN_PRODUCT=claude run_setup_stage "Installing Claude Code notifications" install_claude || return $?
CN_PRODUCT=claude run_setup_stage "Installing Claude notifications" install_claude || return $?
else
run_setup_stage "Installing Codex notifications" install_codex || status=$?
if [ "$status" -eq 3 ]; then
Expand Down
4 changes: 2 additions & 2 deletions bin/bootstrap_opencode_test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ def run_tty(answer, channels=None):
assert persistent_state() == before, 'cancel/empty mutated persistent product roots'
else:
assert consent_sent and approved, 'successful selection omitted channel selection or final consent'
for label in (b'Claude Code',b'Codex',b'OpenCode',b'Gemini CLI'):
for label in (b'Claude',b'Codex',b'OpenCode',b'Gemini CLI'):
assert label in transcript, transcript.decode(errors='replace')
finally:
if status is None:
Expand Down Expand Up @@ -323,7 +323,7 @@ shutil.copytree(os.environ['TEST_SOURCE'],plugin,dirs_exist_ok=True)
text=True,capture_output=True,timeout=90)
assert result.returncode == 0, result.stdout+'\n'+result.stderr
assert result.stdout.count('Installation complete') == 1, result.stdout
for client in ('Claude Code', 'Codex', 'OpenCode', 'Gemini CLI'):
for client in ('Claude', 'Codex', 'OpenCode', 'Gemini CLI'):
assert client + ' - installed;' in result.stdout, result.stdout
assert 'Delivery has not been verified.' in result.stdout, result.stdout
claude_home, codex_home = pathlib.Path(all_env['CLAUDE_CONFIG_DIR']), pathlib.Path(all_env['CODEX_HOME'])
Expand Down
6 changes: 3 additions & 3 deletions bin/bootstrap_product_test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -1004,7 +1004,7 @@ for product, success in [('claude',True), ('codex',True), ('both',True), ('inval
result = subprocess.run([bash,'-c',command],env=env,stdout=subprocess.PIPE,stderr=subprocess.STDOUT,timeout=20)
output = result.stdout
assert (result.returncode==0)==success, output.decode()
if product in ['claude','both']: assert b'Claude Code - installed' in output
if product in ['claude','both']: assert b'Claude - installed' in output
if product == 'claude': assert b'Codex - installed' not in output
if product == 'both': assert b'Codex - installed' in output
if product == 'invalid': assert b'CLAUDE_ADAPTER' not in output and b'CODEX_ADAPTER' not in output
Expand Down Expand Up @@ -1315,7 +1315,7 @@ PY
run_setup_stage 'Locating fixture plugin' find_plugin_root > "$SANDBOX/root-fail.out" 2> "$SANDBOX/root-fail.err" || status=$?
[ "$status" -eq 1 ]
grep -F 'installed_plugins.json not found' "$SANDBOX/root-fail.err"
grep -F 'Try restarting Claude Code' "$SANDBOX/root-fail.err"
grep -F 'Try restarting Claude' "$SANDBOX/root-fail.err"
emit_failure() { echo 'diagnostic stdout'; echo 'diagnostic stderr' >&2; return 3; }
status=0
run_setup_stage 'Failing fixture' emit_failure > "$SANDBOX/stage-fail.out" 2> "$SANDBOX/stage-fail.err" || status=$?
Expand All @@ -1327,7 +1327,7 @@ PY
PRODUCT=both CLAUDE_AGENT_NOTIFY_STATUS='not configured by this run' CODEX_AGENT_NOTIFY_STATUS='not configured by this run'
print_iterm2_python_api_notice() { :; }
print_success > "$SANDBOX/summary.out"
grep -F 'Claude Code - installed; restart required.' "$SANDBOX/summary.out"
grep -F 'Claude - installed; restart required.' "$SANDBOX/summary.out"
grep -F 'Codex - installed; restart required.' "$SANDBOX/summary.out"
grep -F 'Run /hooks in Codex' "$SANDBOX/summary.out"
grep -F 'not configured by this run' "$SANDBOX/summary.out"
Expand Down
6 changes: 3 additions & 3 deletions bin/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ abort_if_wsl_environment() {
echo -e "${YELLOW}This installer is running inside WSL, so it would install Linux binaries under /home instead of Windows binaries.${NC}" >&2
echo -e "${YELLOW}If you started this from PowerShell or Windows Terminal, your bash command is probably WSL bash, not Git Bash.${NC}" >&2
echo "" >&2
echo -e "${YELLOW}For Windows Claude Code, open Git Bash and use the installer at:${NC}" >&2
echo -e "${YELLOW}For Windows Claude, open Git Bash and use the installer at:${NC}" >&2
echo -e " https://777genius.github.io/agent-notifications/#install" >&2
echo "" >&2
echo -e "${YELLOW}For an intentional WSL install, set CLAUDE_NOTIFICATIONS_ALLOW_WSL=1 on the final bash command.${NC}" >&2
Expand Down Expand Up @@ -1640,7 +1640,7 @@ configure_windows_native_hooks() {
guard_install_paths "$hooks_path" "$tmp_hooks"
if printf '%s\n' "$hooks_json" > "$tmp_hooks" 2>/dev/null && mv "$tmp_hooks" "$hooks_path" 2>/dev/null; then
echo -e "${GREEN}✓${NC} Windows exec-form hooks configured"
echo -e "${YELLOW} Restart Claude Code to apply the Windows hook update.${NC}"
echo -e "${YELLOW} Restart Claude to apply the Windows hook update.${NC}"
else
guard_install_paths "$tmp_hooks"
rm -f "$tmp_hooks" 2>/dev/null || true
Expand Down Expand Up @@ -2568,7 +2568,7 @@ main() {
echo -e "${YELLOW}────────────────────────────────────────${NC}"
echo -e "${YELLOW}★${NC} ${BOLD}Boost your productivity${NC}"
echo -e " Check out Agent Teams AI, a desktop app for AI agent teams"
echo -e " with Claude Code, Codex and more, from the creator of this plugin:"
echo -e " with Claude, Codex and more, from the creator of this plugin:"
echo -e " ${GREEN}https://github.com/777genius/agent-teams-ai${NC}"
echo -e "${YELLOW}────────────────────────────────────────${NC}"
echo ""
Expand Down
4 changes: 2 additions & 2 deletions bin/setup_test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ if [ "$status" -eq 0 ] && [ -n "${BOOTSTRAP_SUMMARY_FILE:-}" ]; then
[ "$selection" != both ] || selection=claude,codex
for client in ${selection//,/ }; do
case "$client" in
claude) label="Claude Code" ;;
claude) label="Claude" ;;
codex) label=Codex ;;
opencode) label=OpenCode ;;
gemini) label="Gemini CLI" ;;
Expand Down Expand Up @@ -254,7 +254,7 @@ def run_case(name, tag=None, commit=None, fail='', status=0, expected=None, pipe
for call in calls:
selected = ['claude', 'codex'] if call[1] == 'both' else call[1].split(',')
for client in selected:
label = {'claude': 'Claude Code', 'codex': 'Codex', 'opencode': 'OpenCode', 'gemini': 'Gemini CLI'}[client]
label = {'claude': 'Claude', 'codex': 'Codex', 'opencode': 'OpenCode', 'gemini': 'Gemini CLI'}[client]
assert label + ' - installed; restart required.' in result.stdout, (name, result.stdout)
elif expected is None:
assert result.returncode != 0, (name, result.stdout, result.stderr)
Expand Down
4 changes: 2 additions & 2 deletions cmd/claude-notifications/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -810,7 +810,7 @@ func parseFocusWindowOptions(args []string) (notifier.FocusWindowOptions, error)
}

func printUsage() {
fmt.Println("agent-notifications - Smart notifications for Claude Code and Codex")
fmt.Println("agent-notifications - Smart notifications for Claude and Codex")
fmt.Println()
fmt.Printf("Version: %s\n", version)
fmt.Println()
Expand All @@ -830,7 +830,7 @@ func printUsage() {
fmt.Println(" agent-notifications help")
fmt.Println()
fmt.Println("Commands:")
fmt.Println(" handle-hook <HookName> Handle a Claude Code hook event")
fmt.Println(" handle-hook <HookName> Handle a Claude hook event")
fmt.Println(" HookName: PreToolUse, Stop, SubagentStop, Notification")
fmt.Println(" daemon Run the notification daemon (Linux only)")
fmt.Println(" For click-to-focus support on desktop notifications")
Expand Down
2 changes: 1 addition & 1 deletion cmd/claude-notifications/setup_products_args.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
)

var productOrder = []string{"claude", "codex", "opencode", "gemini"}
var productLabels = map[string]string{"claude": "Claude Code", "codex": "Codex", "opencode": "OpenCode", "gemini": "Gemini CLI"}
var productLabels = map[string]string{"claude": "Claude", "codex": "Codex", "opencode": "OpenCode", "gemini": "Gemini CLI"}
var scopeKeys = []string{"claude-config", "codex-home", "opencode-config-dir", "gemini-home", "gemini-config-root", "claude-executable", "codex-executable", "opencode-executable", "gemini-executable", "control-root"}

type setupProductsArgs struct {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ func TestSetupProductsScopedPresence(t *testing.T) {
if result.code != 0 {
t.Fatalf("cancel: %+v", result)
}
for _, label := range []string{"Claude Code", "OpenCode", "Gemini CLI"} {
for _, label := range []string{"Claude", "OpenCode", "Gemini CLI"} {
if !strings.Contains(result.screen, "[*] "+label+" (CLI present)") {
t.Fatalf("unrelated CLI lost its default: %s", result.screen)
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/claude-notifications/setup_products_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ func TestSetupProductsSelect(t *testing.T) {
if code != tc.code || selected.String() != tc.want {
t.Fatalf("selection: code=%d output=%q prompts=%q", code, selected.String(), prompts.String())
}
for _, label := range []string{"Claude Code", "Codex", "OpenCode", "Gemini CLI", "comma-separated"} {
for _, label := range []string{"Claude", "Codex", "OpenCode", "Gemini CLI", "comma-separated"} {
if !strings.Contains(prompts.String(), label) {
t.Fatalf("product multiselect omitted %q: %s", label, prompts.String())
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/claude-notifications/setup_wizard_cli_contract_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,7 @@ func TestSetupWizardTTYShowsDiscoverCapabilities(t *testing.T) {
if code := executeSetupWizardWith(ctx, []string{"--control-root", root}, &out, io.Discard, strings.NewReader("\n"), true); code != 0 {
t.Fatalf("tty discover cancel: %d %s", code, out.String())
}
if !strings.Contains(out.String(), "Claude Code (executable not found)") || !strings.Contains(out.String(), "Codex (executable present)") {
if !strings.Contains(out.String(), "Claude (executable not found)") || !strings.Contains(out.String(), "Codex (executable present)") {
t.Fatalf("discover labels: %s", out.String())
}
}
Expand Down
Loading
Loading