shelley-hooks

SkillDocs & knowledge

Use when the user wants to customize Shelley by injecting behavior at lifecycle events. It documents Shelley's hooks.

Use shelley-hooks in Claude, ChatGPT or Ahel Desktop

Free. Sign in, add shelley-hooks and connect your AI. About a minute.

Also: Claude Code · Cursor · Codex

Then ask your AI: use the shelley-hooks skill

Details

Instructions available. Your AI can read the instructions. Execution depends on the setup they require.

Add ahel to your AI once: Claude, ChatGPT, Cursor, Claude Code or Codex. Then ask it to use this.

shelley-hooksStart free

What this skill tells your AI

The instructions your AI receives, as published by boldsoftware/shelley in skills/builtin/shelley-hooks/SKILL.md and read by ahel’s review.

Executable files at ~/.config/shelley/hooks/<name>. Missing or non-executable files are ignored. 30s timeout. Any hook failure (non-zero exit, invalid output, etc.) aborts the operation it belongs to — except end-of-turn, where the operation is already finished, so failures are just logged.

Auth-bearing headers (Cookie, Set-Cookie, Authorization, Proxy-Authorization) are stripped from the headers fields before hooks see them.

system-prompt

Runs on every system prompt (main, subagent).

  • stdin: prompt text
  • stdout: replacement prompt text (non-empty)

new-conversation

Runs once when a conversation is created: user-initiated or the first run of a new subagent.

stdin JSON:

{
  "prompt": "...", "model": "...", "cwd": "...",
  "readonly": {
    "conversation_id": "cXXXXXX",
    "is_subagent": false, "parent_id": "...",
    "headers": [["X-Exedev-Email", "user@example.com"]]
  }
}

parent_id is omitempty. headers is a sorted list of [name, value] pairs (multi-valued headers produce multiple pairs); omitted for subagent and other non-HTTP entry points.

stdout: same top-level shape. Only prompt/model/cwd/slug are read; empty fields mean no change; readonly is ignored. Empty stdout = no-op.

Applied when non-empty and changed:

  • cwd → conversation's working directory
  • model → re-resolves LLM service; falls back to original if unsupported
  • prompt → first user message (ignored on distillation paths)
  • slug → sanitized to a slug-safe form; falls back to async slug on collision

chat-message

Fires when the user posts a follow-up chat message to an existing conversation (POST /api/conversation/<id>/chat). For the first message of a brand-new conversation, use new-conversation. Not fired for subagent conversations.

stdin JSON:

{
  "message": "the user's chat message",
  "readonly": {
    "conversation_id": "cXXXXXX",
    "model": "claude-sonnet-4.5",
    "reasoning_level": "high",
    "queued": false,
    "headers": [["X-Exedev-Email", "user@example.com"]]
  }
}

reasoning_level is the conversation's explicit reasoning level (off, minimal, low, medium, high, or xhigh), or the service's configured default when no override is set. It is empty only when the provider chooses the default dynamically and Shelley cannot know it in advance. queued is true when the message will be queued (client requested queue mode or the agent is distilling) rather than interrupting the current turn.

stdout: {"message": "..."}. Empty stdout, empty message, or an identical message means no change.

end-of-turn

Fires when an agent finishes a turn — the same signal that drives end-of-turn notifications (notification channels, push notifications, conversation-hook webhooks). Suppressed for subagent conversations. Stdout is ignored.

stdin JSON:

{
  "type": "end_of_turn",
  "conversation_id": "cXXXXXX",
  "timestamp": "2024-01-02T03:04:05Z",
  "hostname": "host.exe.xyz",
  "model": "claude-sonnet-4.5",
  "slug": "my-slug",
  "conversation_url": "https://host.exe.xyz/c/my-slug",
  "vm_name": "host",
  "final_response": "agent's last text or tool-call summary"
}

Typical uses: play a sound, post a desktop notification, ping a local script.

slash/<command>

Pluggable slash commands. When a user sends a message that starts with /<command> (where <command> matches [a-zA-Z0-9_][a-zA-Z0-9_-]*), Shelley looks for an executable at ~/.config/shelley/hooks/slash/<command>. If present, it is run synchronously before the message is recorded or sent to the LLM. Its stdout replaces the user-message body. Empty stdout leaves the original message unchanged. Applies to both new conversations and follow-up messages.

No matching executable → the message is treated as a normal user message (no special handling).

stdin JSON:

{
  "command": "foo",
  "args": "the rest of the message after /foo",
  "raw_message": "/foo the rest of the message after /foo",
  "conversation_id": "cXXXXXX",
  "is_new_conversation": false,
  "cwd": "/home/user/project",
  "model": "claude-sonnet-4.5",
  "user_email": "you@example.com"
}

The same context is also exposed via environment variables for shell-friendly hooks: SHELLEY_SLASH_COMMAND, SHELLEY_SLASH_ARGS, SHELLEY_CONVERSATION_ID, SHELLEY_CWD, SHELLEY_MODEL, SHELLEY_USER_EMAIL.

stdout: replacement user-message text. Empty stdout keeps the original message (useful for hooks that only have side effects). Failure (non-zero exit) surfaces as a 400 to the client and the message is not recorded.

Example: a ~/.config/shelley/hooks/slash/files hook that injects the contents of files matched by a glob:

#!/bin/sh
set -e
printf 'Here are the files you asked about:\n\n'
for f in $SHELLEY_SLASH_ARGS; do
  printf '=== %s ===\n' "$f"
  cat "$f"
  printf '\n'
done

Then /files src/main.go src/util.go please summarize becomes a normal user message with file contents inlined.

Signals

GitHub stars
676
Forks
111
Last commit
Oct 2026
Advanced
Item type
skill
Key
shelley-hooks
Source
github.com/boldsoftware/shelley