Skip to content
Closed
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
8 changes: 8 additions & 0 deletions .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,14 @@
"./skills/lean-jev-usage",
"./skills/ask-jev"
]
},
{
"name": "lean-attention",
"source": "./plugins/lean-attention",
"description": "Desktop notification and a spoken line whenever your agent waits for you: a permission prompt, a question, or an idle session. The script is agent-agnostic. Linux and macOS.",
"skills": [
"./skills/lean-attention-usage"
]
}
]
}
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,12 @@ See [Discover and install plugins](https://code.claude.com/docs/en/discover-plug
| ------ | ------ | ----- |
| [`lean-jev`](plugins/lean-jev/) | Hands a judgment call to Jev, TypeSafe's System One model, and frames it the way Jev needs it framed: typed questions, numbers instead of prose, cheap enough to judge every item in a list | A TypeSafe API key |

### Workflow

| Plugin | Covers | Setup |
| ------ | ------ | ----- |
| [`lean-attention`](plugins/lean-attention/) | A desktop notification and a spoken line whenever your agent waits for you, so a permission prompt in a background terminal doesn't sit unnoticed | None on macOS; `libnotify-bin` and `speech-dispatcher` on Linux |

## Contributing

Fixes and new Flutter plugins are welcome. CI runs every check, so you need nothing installed locally. [`CONTRIBUTING.md`](CONTRIBUTING.md) has the steps, and [`AGENTS.md`](AGENTS.md) has the plugin conventions.
Expand Down
19 changes: 19 additions & 0 deletions plugins/lean-attention/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
"name": "lean-attention",
"displayName": "Lean Attention",
"description": "Desktop notification and a spoken line whenever your agent waits for you: a permission prompt, a question, or an idle session. The script is agent-agnostic. Linux and macOS.",
"version": "0.1.0",
"author": {
"name": "LeanCode"
},
"homepage": "https://github.com/leancodepl/ai-plugins",
"repository": "https://github.com/leancodepl/ai-plugins",
"keywords": [
"notifications",
"hooks",
"attention",
"voice"
],
"skills": "./skills/"
}
8 changes: 8 additions & 0 deletions plugins/lean-attention/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Changelog

## 0.1.0

- Initial `lean-attention` plugin.
- `hooks/hooks.json`: a `Notification` hook that runs `scripts/notify.sh`.
- `scripts/notify.sh`: desktop notification (`notify-send` / `osascript`) and a spoken line (`spd-say` / `say`), configurable through `LEAN_ATTENTION_*` env vars. Agent-agnostic input: JSON on stdin, JSON as the last argument (Codex `notify`), or plain text.
- `skills/lean-attention-usage/SKILL.md`: explains the plugin and troubleshoots a silent setup.
65 changes: 65 additions & 0 deletions plugins/lean-attention/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# lean-attention

Your agent pauses on a permission prompt, a question, or an idle session, and the built-in signal
is easy to miss when it runs in a background terminal or you juggle several sessions. This plugin
shows a desktop notification with the project name and the agent's message, and speaks a short
line.

## Setup

Install it like any other plugin from this marketplace and restart the agent so the hook loads.
There is nothing to configure for the defaults.

What the hook needs on the machine:

| | Notification | Voice | Message text |
| --- | --- | --- | --- |
| Linux | `notify-send` (`libnotify-bin`) | `spd-say` (`speech-dispatcher`) | `jq` |
| macOS | `osascript` (built in) | `say` (built in) | `jq` |

Each piece is optional: without `jq` the notification shows a generic message, and a missing
notifier or speech tool is skipped. On macOS, allow notifications for Script Editor the first time
one appears.

## Configuration

Set these in the agent's environment (the `env` block of its settings file):

| Variable | Default | Effect |
| --- | --- | --- |
| `LEAN_ATTENTION_SPEAK` | `1` | `0` turns the voice off and keeps the notification |
| `LEAN_ATTENTION_PHRASE` | `Your agent is waiting for you` | The spoken line |
| `LEAN_ATTENTION_TITLE` | `Agent` | Notification title, shown as `<title> · <project>` |
| `LEAN_ATTENTION_LANG` | speech-dispatcher default | Linux voice language, e.g. `pl` for a Polish phrase |

```json
{
"env": {
"LEAN_ATTENTION_PHRASE": "Agent czeka na ciebie",
"LEAN_ATTENTION_LANG": "pl"
}
}
```

## Other agents

`scripts/notify.sh` is agent-agnostic and takes its input in three shapes:

- a JSON payload on stdin with `message` (and optionally `cwd`), as hook-style agents send it;
- a JSON payload as the last argument, as Codex's `notify` sends it;
- plain text arguments, used as the message: `notify.sh "Build finished"`.

Codex, in `~/.codex/config.toml` (its payload has no `message`, so the notification says "Waiting for you"):

```toml
notify = ["bash", "/path/to/lean-attention/scripts/notify.sh"]
```

For any other agent, point its "run a command when the agent stops or needs input" hook at the
script and send one of the shapes above. Only the bundled `Notification` hook is tested so far.

## Assets

- `hooks/hooks.json`: the `Notification` hook that runs the script.
- `scripts/notify.sh`: picks the notifier and voice for the platform; always exits 0, so it never blocks the agent.
- `/lean-attention-usage`: what the plugin does, how to fix a silent setup, and how to wire it to another agent.
15 changes: 15 additions & 0 deletions plugins/lean-attention/hooks/hooks.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"hooks": {
"Notification": [
{
"hooks": [
{
"type": "command",
"command": "\"${CLAUDE_PLUGIN_ROOT}/scripts/notify.sh\"",
"timeout": 10
}
]
}
]
}
}
46 changes: 46 additions & 0 deletions plugins/lean-attention/scripts/notify.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
#!/usr/bin/env bash
# Agent-agnostic "waiting for you" alert: a desktop notification plus a spoken line.
# Input: a JSON payload on stdin (hook-style agents) or as the last argument (Codex `notify`),
# or plain text arguments. Always exits 0 so it never blocks the agent.
set -u

default_message="Waiting for you"
payload=""
if [ $# -gt 0 ]; then
last="${!#}"
case "$last" in
\{*) payload="$last" ;;
*) default_message="$*" ;;
esac
elif [ ! -t 0 ]; then
payload=$(cat)
fi

message="$default_message"
project=""
if [ -n "$payload" ] && command -v jq >/dev/null 2>&1; then
message=$(jq -r --arg d "$default_message" '.message // $d' <<<"$payload" 2>/dev/null || echo "$default_message")
cwd=$(jq -r '.cwd // empty' <<<"$payload" 2>/dev/null || true)
[ -n "$cwd" ] && project=$(basename "$cwd")
fi
[ -z "$project" ] && project=$(basename "$PWD")

title="${LEAN_ATTENTION_TITLE:-Agent} · $project"
phrase="${LEAN_ATTENTION_PHRASE:-Your agent is waiting for you}"
speak="${LEAN_ATTENTION_SPEAK:-1}"

case "$(uname -s)" in
Darwin)
# Passed as argv, so quotes in the message can't break the AppleScript.
osascript -e 'on run argv' -e 'display notification (item 2 of argv) with title (item 1 of argv)' -e 'end run' \
"$title" "$message" >/dev/null 2>&1 || true
if [ "$speak" != "0" ]; then say "$phrase" >/dev/null 2>&1 & fi
;;
Linux)
command -v notify-send >/dev/null 2>&1 && notify-send -a "${LEAN_ATTENTION_TITLE:-Agent}" "$title" "$message" >/dev/null 2>&1
if [ "$speak" != "0" ] && command -v spd-say >/dev/null 2>&1; then
spd-say ${LEAN_ATTENTION_LANG:+-l "$LEAN_ATTENTION_LANG"} "$phrase" >/dev/null 2>&1
fi
;;
esac
exit 0
26 changes: 26 additions & 0 deletions plugins/lean-attention/skills/lean-attention-usage/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
---
name: lean-attention-usage
description: Explain the lean-attention plugin (desktop notification and spoken line when a coding agent waits for input) and troubleshoot it or wire it to another agent. Use when the user invokes `/lean-attention-usage`, asks why they got no notification or sound, wants to change or mute the spoken phrase, or wants the same alert in another agent. Manual-only entry point — does not auto-fire.
disable-model-invocation: true
---

# Lean Attention Usage

## What it does

A `Notification` hook runs `scripts/notify.sh` whenever the agent waits for the user (permission
prompt, question, idle session). The script shows a desktop notification titled with the project
name and speaks a short line; it is agent-agnostic and other agents can call it too. Setup, dependencies and the `LEAN_ATTENTION_*` variables are in the
plugin `README.md`; read it rather than restating it.

## How to respond

1. To explain the plugin, summarize the paragraph above and point at the plugin `README.md`.
2. To change or mute the voice, show the `env` example from the README with the variable the user needs.
3. To use it from another agent, point at the "Other agents" section of the README; do not invent hook config for an agent the README does not cover.
4. To troubleshoot silence, check in this order and stop at the first failure:
- The hook is loaded: ask the user to open `/hooks` and look for a `Notification` entry from `lean-attention`. If it is missing, restart the agent.
- The script runs: `echo '{"message":"test","cwd":"'"$PWD"'"}' | "<plugin root>/scripts/notify.sh"`.
- The tools exist: Linux `command -v notify-send spd-say jq`, macOS `command -v osascript say jq`.
- On Linux with no voice: `spd-say -w "test"`; if that is silent, speech-dispatcher has no output module (install `speech-dispatcher-espeak-ng`).
- On macOS with no banner: System Settings → Notifications → Script Editor.
Loading