{"slug": "show-hn-pleasantries", "title": "Show HN: Pleasantries", "summary": "A developer released Pleasantries, an open-source pre-hook script that blocks pleasantry-only prompts like \"hi\" and \"thank you\" before they reach AI coding CLIs, saving tokens and preventing wasted model calls. The tool supports 15 CLIs including Claude Code, Codex CLI, Gemini CLI, Cursor, and Copilot Chat, with per-CLI blocking mechanisms such as exit code 2 or JSON deny responses. It normalizes input to catch greetings while allowing prompts that contain real tasks, and can be installed via a Python script that auto-detects installed CLIs.", "body_md": "Pre-hook scripts for AI coding CLIs that block pleasantry-only prompts like \"hi\", \"hello\", \"ok\", \"thank you\", etc. Send a real task, not a greeting.\n\nAI coding assistants are task tools, not chat buddies. Every \"hello\" wastes a model call, burns tokens, and breaks your flow. This hook intercepts the prompt **before** it reaches the model and rejects it with a nudge.\n\n``` php\nYou type \"hi\" --> Hook reads prompt --> Regex fullmatch --> Blocked\nYou type \"fix auth bug\" --> Hook reads prompt --> No match --> Prompt proceeds\n```\n\nEach CLI has its own blocking mechanism:\n\n| Block method | CLIs |\n|---|---|\n| stderr + exit code 2 | Claude, Kiro, Copilot Chat, Copilot CLI, Cursor, Factory Droid, Kimi Code, Devin |\nJSON `{\"decision\": \"block\"}` + exit 0 |\nCodex |\nJSON `{\"decision\": \"deny\"}` + exit 0 |\nGemini |\n\nThe matcher normalizes input (lowercase, strip punctuation, collapse whitespace) and checks if the **entire** prompt is a pleasantry. Prompts that *contain* pleasantry words but include a real task pass through fine:\n\n| Prompt | Result |\n|---|---|\n`hi` |\nBlocked |\n`Hello!!` |\nBlocked |\n`thank you so much` |\nBlocked |\n`ok` |\nBlocked |\n`see ya later` |\nBlocked |\n`fix the login bug` |\nAllowed |\n`hello world program in python` |\nAllowed |\n`please refactor auth.py` |\nAllowed |\n\n| CLI | Hook Event | Config Location | Blocking |\n|---|---|---|---|\n| Claude Code | `UserPromptSubmit` |\n`~/.claude/settings.json` |\nexit 2 |\n| Codex CLI | `UserPromptSubmit` |\n`~/.codex/hooks.json` |\nJSON block |\n| Gemini CLI | `BeforeAgent` |\n`~/.gemini/settings.json` |\nJSON deny |\n| Cursor | `beforeSubmitPrompt` |\n`~/.cursor/hooks.json` |\nexit 2 |\n| Copilot Chat | `UserPromptSubmit` |\n`~/.copilot/hooks/` |\nexit 2 |\n| Copilot CLI | `userPromptSubmitted` |\n`~/.copilot/hooks/` |\nexit 2 |\n| Qwen Code | `UserPromptSubmit` |\n`~/.qwen/settings.json` |\nexit 2 |\n| Junie CLI | `UserPromptSubmit` |\n`~/.junie/config.json` |\nexit 2 |\n| Factory Droid | `UserPromptSubmit` |\n`~/.factory/hooks.json` |\nexit 2 |\n| Kimi Code | `UserPromptSubmit` |\n`~/.kimi-code/config.toml` |\nexit 2 |\n| grok-cli | `UserPromptSubmit` |\n`~/.grok/user-settings.json` |\nexit 2 |\n| Kun | `UserPromptSubmit` |\n`~/.kun/data/config.json` |\nexit 2 |\n| Open Interpreter | `UserPromptSubmit` |\n`~/.openinterpreter/hooks.json` |\nexit 2 |\n| Kiro | `UserPromptSubmit` |\n`.kiro/hooks/*.json` (workspace) |\nexit 2 |\n| Devin CLI | `UserPromptSubmit` |\n`.devin/hooks.v1.json` (workspace) |\nexit 2 |\n\nCodeBuddy, OpenCode (archived), Aider, Kilo Code, Trae, Trae CN (unreleased), Hermes, Pi, OpenClaw, Amp (docs private), Google Antigravity (no prompt access), Cline (SDK-only), oh-my-pi (SDK-only), Freebuff (no config), Command Code (no pre-prompt hook).\n\n```\npython install.py\n```\n\nThe installer will:\n\n- Copy\n`block_pleasantries.py`\n\nto`~/.pleasantries/`\n\n- Detect which AI coding CLIs are installed on your machine\n- Let you select which ones to hook\n- Merge the hook into each CLI's config (skips if already installed)\n\nTo remove all hooks:\n\n```\npython install.py --uninstall\n```\n\nIf you prefer to configure each CLI by hand:\n\n`~/.claude/settings.json`\n\n:\n\n```\n{\n  \"hooks\": {\n    \"UserPromptSubmit\": [\n      { \"hooks\": [{ \"type\": \"command\",\n          \"command\": \"python3 /path/to/block_pleasantries.py claude\",\n          \"timeout\": 5 }] }\n    ]\n  }\n}\n```\n\n**Step 1:** Enable hooks in `~/.codex/config.toml`\n\n:\n\n```\n[features]\nhooks = true\n```\n\n**Step 2:** Add to `~/.codex/hooks.json`\n\n:\n\n```\n{\n  \"hooks\": {\n    \"UserPromptSubmit\": [\n      { \"hooks\": [{ \"type\": \"command\",\n          \"command\": \"python3 /path/to/block_pleasantries.py codex\",\n          \"timeout\": 5, \"statusMessage\": \"Checking prompt\" }] }\n    ]\n  }\n}\n```\n\n`~/.gemini/settings.json`\n\n:\n\n```\n{\n  \"hooks\": {\n    \"BeforeAgent\": [\n      { \"matcher\": \"*\", \"hooks\": [{ \"type\": \"command\",\n          \"command\": \"python3 /path/to/block_pleasantries.py gemini\",\n          \"timeout\": 5000 }] }\n    ]\n  }\n}\n```\n\n`.kiro/hooks/block-pleasantries.json`\n\n(workspace-level):\n\n```\n{\n  \"version\": \"v1\",\n  \"hooks\": [{\n    \"name\": \"block-pleasantries\",\n    \"trigger\": \"UserPromptSubmit\",\n    \"action\": { \"type\": \"command\",\n      \"command\": \"python3 /path/to/block_pleasantries.py kiro\" },\n    \"timeout\": 30, \"enabled\": true\n  }]\n}\n```\n\n`.cursor/hooks.json`\n\nor `~/.cursor/hooks.json`\n\n:\n\n```\n{\n  \"version\": 1,\n  \"hooks\": {\n    \"beforeSubmitPrompt\": [{\n      \"command\": \"python3 /path/to/block_pleasantries.py cursor\",\n      \"timeout\": 5\n    }]\n  }\n}\n```\n\n`.github/hooks/pleasantries.json`\n\nor `~/.copilot/hooks/`\n\n:\n\n```\n{\n  \"version\": 1,\n  \"hooks\": {\n    \"UserPromptSubmit\": [{\n      \"type\": \"command\",\n      \"command\": \"python3 /path/to/block_pleasantries.py copilot-chat\",\n      \"timeout\": 5\n    }]\n  }\n}\n```\n\n`~/.factory/hooks.json`\n\nor `.factory/hooks.json`\n\n:\n\n```\n{\n  \"hooks\": {\n    \"UserPromptSubmit\": [\n      { \"hooks\": [{ \"type\": \"command\",\n          \"command\": \"python3 /path/to/block_pleasantries.py factory-droid\",\n          \"timeout\": 5 }] }\n    ]\n  }\n}\n```\n\n`~/.kimi-code/config.toml`\n\n:\n\n```\n[[hooks]]\nevent = \"UserPromptSubmit\"\ncommand = \"python3 /path/to/block_pleasantries.py kimi-code\"\ntimeout = 5\n```\n\n`.devin/hooks.v1.json`\n\n:\n\n```\n{\n  \"hooks\": {\n    \"UserPromptSubmit\": [\n      { \"hooks\": [{ \"type\": \"command\",\n          \"command\": \"python3 /path/to/block_pleasantries.py devin\",\n          \"timeout\": 5 }] }\n    ]\n  }\n}\n```\n\nReplace `/path/to/`\n\nwith the actual path to this repo. Use `python`\n\ninstead of `python3`\n\non Windows.\n\n- Python 3.10+\n- No external dependencies (stdlib only:\n`json`\n\n,`re`\n\n,`sys`\n\n) - Works on macOS, Linux, and Windows\n\n```\nblock_pleasantries.py\n+-- Core matcher (CLI-agnostic)\n|   +-- PLEASANTRY_PATTERNS      - compiled regex\n|   +-- normalize()              - lowercase, strip punctuation\n|   +-- is_pleasantry()          - fullmatch check\n|\n+-- CLI adapters\n|   +-- _json_prompt_extract()   - shared JSON stdin parser\n|   +-- _exit2_block()           - stderr + exit code 2 (8 CLIs)\n|   +-- _codex_block()           - JSON stdout \"block\" + exit 0\n|   +-- _gemini_block()          - JSON stdout \"deny\" + exit 0\n|   +-- ADAPTERS dict            - {name: {extract, block}}\n|\n+-- main()                       - dispatch via argv[1]\n```\n\nAdding a new CLI:\n\n``` php\ndef _newcli_block(msg: str) -> None:\n    print(msg, file=sys.stderr)\n    sys.exit(2)\n\nADAPTERS[\"newcli\"] = {\"extract\": _json_prompt_extract, \"block\": _newcli_block}\n```\n\nThen: `python3 block_pleasantries.py newcli`\n\nEdit the `PLEASANTRY_PATTERNS`\n\nregex in `block_pleasantries.py`\n\n. Patterns are grouped by category:\n\n**Greetings**: hi, hello, hey, howdy, good morning, ...** Acknowledgments**: ok, sure, yeah, got it, ...** Thanks**: thank you, thanks, thx, ty, ...** Please**: please, pls, plz** Farewells**: bye, goodbye, see ya, later, ...\n\nMIT", "url": "https://wpnews.pro/news/show-hn-pleasantries", "canonical_source": "https://github.com/QAInsights/pleasantries", "published_at": "2026-08-04 21:59:30+00:00", "updated_at": "2026-08-04 22:23:33.023765+00:00", "lang": "en", "topics": ["developer-tools", "ai-tools", "ai-agents"], "entities": ["Pleasantries", "Claude Code", "Codex CLI", "Gemini CLI", "Cursor", "Copilot Chat", "Kiro", "Devin CLI"], "alternates": {"html": "https://wpnews.pro/news/show-hn-pleasantries", "markdown": "https://wpnews.pro/news/show-hn-pleasantries.md", "text": "https://wpnews.pro/news/show-hn-pleasantries.txt", "jsonld": "https://wpnews.pro/news/show-hn-pleasantries.jsonld"}}