claude-dev-env 1.38.1 → 1.39.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (270) hide show
  1. package/CLAUDE.md +10 -36
  2. package/_shared/pr-loop/audit-reply-template.md +147 -0
  3. package/_shared/pr-loop/fix-protocol.md +25 -4
  4. package/_shared/pr-loop/gh-payloads.md +37 -50
  5. package/_shared/pr-loop/scripts/code_rules_gate.py +0 -60
  6. package/_shared/pr-loop/scripts/config/post_audit_thread_constants.py +189 -0
  7. package/_shared/pr-loop/scripts/post_audit_thread.py +947 -0
  8. package/_shared/pr-loop/scripts/tests/test_code_rules_gate.py +0 -19
  9. package/_shared/pr-loop/scripts/tests/test_post_audit_thread.py +923 -0
  10. package/_shared/pr-loop/scripts/tests/test_post_audit_thread_constants.py +127 -0
  11. package/_shared/pr-loop/state-schema.md +1 -1
  12. package/agents/clean-coder.md +2 -2
  13. package/bin/install.mjs +6 -7
  14. package/bin/install.test.mjs +8 -0
  15. package/commands/doc-gist.md +16 -0
  16. package/commands/plan.md +0 -2
  17. package/commands/review-plan.md +1 -1
  18. package/docs/CODE_RULES.md +122 -2
  19. package/hooks/blocking/bot_mention_comment_blocker.py +75 -0
  20. package/hooks/blocking/code_rules_enforcer.py +1143 -129
  21. package/hooks/blocking/convergence_gate_blocker.py +130 -0
  22. package/hooks/blocking/destructive_command_blocker.py +74 -0
  23. package/hooks/blocking/gh_body_arg_blocker.py +30 -0
  24. package/hooks/blocking/md_to_html_blocker.py +119 -0
  25. package/hooks/blocking/test_bot_mention_comment_blocker.py +131 -0
  26. package/hooks/blocking/test_code_rules_enforcer.py +21 -0
  27. package/hooks/blocking/test_code_rules_enforcer_any_exempt_files.py +70 -0
  28. package/hooks/blocking/test_code_rules_enforcer_any_imports_and_cast.py +92 -0
  29. package/hooks/blocking/test_code_rules_enforcer_banned_import_alias.py +143 -0
  30. package/hooks/blocking/test_code_rules_enforcer_banned_prefixes.py +152 -0
  31. package/hooks/blocking/test_code_rules_enforcer_bare_except.py +120 -0
  32. package/hooks/blocking/test_code_rules_enforcer_boundary_types.py +175 -0
  33. package/hooks/blocking/test_code_rules_enforcer_cap_meta.py +0 -1
  34. package/hooks/blocking/test_code_rules_enforcer_collection_prefix.py +50 -0
  35. package/hooks/blocking/test_code_rules_enforcer_docstring_format.py +255 -0
  36. package/hooks/blocking/test_code_rules_enforcer_inline_tuple_string_magic.py +130 -0
  37. package/hooks/blocking/test_code_rules_enforcer_stub_implementations.py +141 -0
  38. package/hooks/blocking/test_code_rules_enforcer_test_branching.py +143 -0
  39. package/hooks/blocking/test_code_rules_enforcer_thin_wrapper_files.py +169 -0
  40. package/hooks/blocking/test_code_rules_enforcer_todo_markers.py +99 -0
  41. package/hooks/blocking/test_code_rules_enforcer_typed_dict_pairs.py +141 -0
  42. package/hooks/blocking/test_convergence_gate_blocker.py +63 -0
  43. package/hooks/blocking/test_destructive_command_blocker.py +146 -0
  44. package/hooks/blocking/test_destructive_command_blocker_no_verify.py +102 -0
  45. package/hooks/blocking/test_gh_body_arg_blocker.py +45 -0
  46. package/hooks/blocking/test_md_to_html_blocker.py +317 -0
  47. package/hooks/config/any_type_config.py +7 -0
  48. package/hooks/config/banned_identifiers_constants.py +11 -0
  49. package/hooks/config/blocking_check_limits.py +38 -0
  50. package/hooks/config/bot_mention_comment_blocker_constants.py +20 -0
  51. package/hooks/config/code_rules_enforcer_constants.py +53 -0
  52. package/hooks/config/convergence_branch_constants.py +9 -0
  53. package/hooks/config/doc_gist_auto_publish_constants.py +18 -0
  54. package/hooks/config/html_companion_constants.py +20 -0
  55. package/hooks/config/inline_tuple_string_magic_constants.py +22 -0
  56. package/hooks/config/test_banned_identifiers_constants.py +17 -0
  57. package/hooks/hooks.json +28 -20
  58. package/hooks/pyproject.toml +69 -0
  59. package/hooks/validators/mypy_integration.py +47 -1
  60. package/hooks/validators/run_all_validators.py +3 -3
  61. package/hooks/validators/test_mypy_integration.py +50 -1
  62. package/hooks/workflow/doc_gist_auto_publish.py +144 -0
  63. package/hooks/workflow/md_to_html_companion.py +365 -0
  64. package/hooks/workflow/test_doc_gist_auto_publish.py +117 -0
  65. package/hooks/workflow/test_md_to_html_companion.py +452 -0
  66. package/package.json +1 -1
  67. package/rules/gh-body-file.md +2 -0
  68. package/scripts/Install-SweepEmptyDirs.ps1 +111 -0
  69. package/scripts/check.ps1 +106 -0
  70. package/scripts/config/timing.py +11 -0
  71. package/scripts/sweep_empty_dirs.py +138 -0
  72. package/scripts/sync_to_cursor/rules.py +1 -1
  73. package/scripts/test_sweep_empty_dirs.py +183 -0
  74. package/skills/_shared/pr-loop/prompts/pr-consistency-audit.xml +323 -0
  75. package/skills/_shared/pr-loop/scripts/_cli_utils.py +22 -0
  76. package/skills/_shared/pr-loop/scripts/_path_resolver.py +165 -0
  77. package/skills/_shared/pr-loop/scripts/_xml_utils.py +20 -0
  78. package/skills/_shared/pr-loop/scripts/build_audit_prompt.py +182 -0
  79. package/skills/_shared/pr-loop/scripts/build_fix_prompt.py +185 -0
  80. package/skills/_shared/pr-loop/scripts/config/__init__.py +0 -0
  81. package/skills/_shared/pr-loop/scripts/config/path_resolver_constants.py +78 -0
  82. package/skills/_shared/pr-loop/scripts/init_loop_state.py +135 -0
  83. package/skills/_shared/pr-loop/scripts/teardown_worktrees.py +175 -0
  84. package/skills/_shared/pr-loop/scripts/write_audit_outcomes.py +182 -0
  85. package/skills/_shared/pr-loop/scripts/write_fix_outcomes.py +206 -0
  86. package/skills/bugteam/CONSTRAINTS.md +21 -22
  87. package/skills/bugteam/EXAMPLES.md +3 -3
  88. package/skills/bugteam/PROMPTS.md +227 -67
  89. package/skills/bugteam/SKILL.md +114 -455
  90. package/skills/bugteam/reference/README.md +1 -1
  91. package/skills/bugteam/reference/audit-and-teammates.md +112 -39
  92. package/skills/bugteam/reference/audit-contract.md +4 -22
  93. package/skills/bugteam/reference/copilot-gap-analysis.md +8 -5
  94. package/skills/bugteam/reference/design-rationale.md +2 -2
  95. package/skills/bugteam/reference/github-pr-reviews.md +50 -57
  96. package/skills/bugteam/reference/obstacles/audit-assign-ids.md +13 -0
  97. package/skills/bugteam/reference/obstacles/audit-capture-excerpts.md +13 -0
  98. package/skills/bugteam/reference/obstacles/audit-walk-categories.md +13 -0
  99. package/skills/bugteam/reference/obstacles/audit-write-xml.md +13 -0
  100. package/skills/bugteam/reference/obstacles/fix-append-summary.md +13 -0
  101. package/skills/bugteam/reference/obstacles/fix-apply-fixes.md +13 -0
  102. package/skills/bugteam/reference/obstacles/fix-git-add-commit.md +13 -0
  103. package/skills/bugteam/reference/obstacles/fix-git-push.md +13 -0
  104. package/skills/bugteam/reference/obstacles/fix-post-reply.md +13 -0
  105. package/skills/bugteam/reference/obstacles/fix-publish-summary.md +13 -0
  106. package/skills/bugteam/reference/obstacles/fix-py-compile.md +13 -0
  107. package/skills/bugteam/reference/obstacles/fix-read-files.md +13 -0
  108. package/skills/bugteam/reference/obstacles/fix-resolve-thread.md +13 -0
  109. package/skills/bugteam/reference/obstacles/fix-test-suite.md +13 -0
  110. package/skills/bugteam/reference/obstacles/fix-violation-count.md +13 -0
  111. package/skills/bugteam/reference/obstacles/fix-write-xml.md +13 -0
  112. package/skills/bugteam/reference/team-setup.md +106 -9
  113. package/skills/bugteam/reference/teardown-publish-permissions.md +39 -8
  114. package/skills/bugteam/scripts/README.md +60 -0
  115. package/skills/bugteam/scripts/_claude_permissions_common.py +358 -0
  116. package/skills/bugteam/scripts/bugteam_code_rules_gate.py +976 -0
  117. package/skills/bugteam/scripts/bugteam_fix_hookspath.py +375 -0
  118. package/skills/bugteam/scripts/bugteam_preflight.py +294 -0
  119. package/skills/bugteam/scripts/config/bugteam_code_rules_gate_constants.py +25 -0
  120. package/skills/bugteam/scripts/config/bugteam_fix_hookspath_constants.py +26 -0
  121. package/skills/bugteam/scripts/config/bugteam_preflight_constants.py +35 -0
  122. package/skills/bugteam/scripts/config/claude_permissions_common_constants.py +20 -0
  123. package/skills/bugteam/scripts/config/probe_code_rules_enforcer_check_constants.py +12 -0
  124. package/skills/bugteam/scripts/config/windows_safe_rmtree_constants.py +7 -0
  125. package/skills/bugteam/scripts/grant_project_claude_permissions.py +175 -0
  126. package/skills/bugteam/scripts/probe_code_rules_enforcer_check.py +107 -0
  127. package/skills/bugteam/scripts/revoke_project_claude_permissions.py +220 -0
  128. package/skills/bugteam/scripts/test__claude_permissions_common.py +112 -0
  129. package/skills/bugteam/scripts/test_bugteam_code_rules_gate.py +400 -0
  130. package/skills/bugteam/scripts/test_bugteam_fix_hookspath.py +384 -0
  131. package/skills/bugteam/scripts/test_bugteam_preflight.py +268 -0
  132. package/skills/bugteam/scripts/test_claude_permissions_common.py +195 -0
  133. package/skills/bugteam/scripts/test_grant_project_claude_permissions.py +55 -0
  134. package/skills/bugteam/scripts/test_probe_code_rules_enforcer_check.py +76 -0
  135. package/skills/bugteam/scripts/test_revoke_project_claude_permissions.py +55 -0
  136. package/skills/bugteam/scripts/test_windows_safe_rmtree.py +108 -0
  137. package/skills/bugteam/scripts/windows_safe_rmtree.py +100 -0
  138. package/skills/bugteam/test_skill_additions.py +1 -11
  139. package/skills/code/SKILL.md +176 -0
  140. package/skills/doc-gist/SKILL.md +99 -0
  141. package/skills/doc-gist/references/examples/01-exploration-code-approaches.html +453 -0
  142. package/skills/doc-gist/references/examples/02-exploration-visual-designs.html +515 -0
  143. package/skills/doc-gist/references/examples/03-code-review-pr.html +638 -0
  144. package/skills/doc-gist/references/examples/04-code-understanding.html +491 -0
  145. package/skills/doc-gist/references/examples/05-design-system.html +629 -0
  146. package/skills/doc-gist/references/examples/06-component-variants.html +605 -0
  147. package/skills/doc-gist/references/examples/07-prototype-animation.html +455 -0
  148. package/skills/doc-gist/references/examples/08-prototype-interaction.html +396 -0
  149. package/skills/doc-gist/references/examples/09-slide-deck.html +592 -0
  150. package/skills/doc-gist/references/examples/10-svg-illustrations.html +492 -0
  151. package/skills/doc-gist/references/examples/11-status-report.html +528 -0
  152. package/skills/doc-gist/references/examples/12-incident-report.html +596 -0
  153. package/skills/doc-gist/references/examples/13-flowchart-diagram.html +395 -0
  154. package/skills/doc-gist/references/examples/14-research-feature-explainer.html +381 -0
  155. package/skills/doc-gist/references/examples/15-research-concept-explainer.html +368 -0
  156. package/skills/doc-gist/references/examples/16-implementation-plan.html +702 -0
  157. package/skills/doc-gist/references/examples/17-pr-writeup.html +595 -0
  158. package/skills/doc-gist/references/examples/18-editor-triage-board.html +573 -0
  159. package/skills/doc-gist/references/examples/19-editor-feature-flags.html +663 -0
  160. package/skills/doc-gist/references/examples/20-editor-prompt-tuner.html +722 -0
  161. package/skills/doc-gist/references/examples/README.md +5 -0
  162. package/skills/doc-gist/scripts/config/__init__.py +0 -0
  163. package/skills/doc-gist/scripts/config/gist_upload_constants.py +16 -0
  164. package/skills/doc-gist/scripts/gist_upload.py +177 -0
  165. package/skills/doc-gist/scripts/test_gist_upload.py +51 -0
  166. package/skills/findbugs/SKILL.md +68 -2
  167. package/skills/monitor-open-prs/SKILL.md +13 -32
  168. package/skills/monitor-open-prs/test_skill_contract.py +0 -11
  169. package/skills/pr-consistency-audit/SKILL.md +112 -0
  170. package/skills/pr-consistency-audit/reference/detection-rules.md +96 -0
  171. package/skills/pr-consistency-audit/reference/illustrations.md +78 -0
  172. package/skills/pr-converge/SKILL.md +227 -23
  173. package/skills/pr-converge/config/__init__.py +0 -0
  174. package/skills/pr-converge/config/constants.py +62 -0
  175. package/skills/pr-converge/reference/convergence-gates.md +138 -44
  176. package/skills/pr-converge/reference/examples.md +43 -11
  177. package/skills/pr-converge/reference/fix-protocol.md +6 -5
  178. package/skills/pr-converge/reference/ground-rules.md +5 -3
  179. package/skills/pr-converge/reference/multi-pr-orchestration.md +44 -19
  180. package/skills/pr-converge/reference/obstacles/fix-post-replies.md +13 -0
  181. package/skills/pr-converge/reference/obstacles/fix-publish-summary.md +13 -0
  182. package/skills/pr-converge/reference/obstacles/fix-push.md +13 -0
  183. package/skills/pr-converge/reference/obstacles/fix-read-filelines.md +13 -0
  184. package/skills/pr-converge/reference/obstacles/fix-reset-state.md +13 -0
  185. package/skills/pr-converge/reference/obstacles/fix-resolve-threads.md +13 -0
  186. package/skills/pr-converge/reference/obstacles/fix-spawn-clean-coder.md +13 -0
  187. package/skills/pr-converge/reference/obstacles/fix-stage-commit.md +13 -0
  188. package/skills/pr-converge/reference/obstacles/fix-trigger-bugbot.md +13 -0
  189. package/skills/pr-converge/reference/obstacles/fix-write-test.md +13 -0
  190. package/skills/pr-converge/reference/per-tick.md +90 -31
  191. package/skills/pr-converge/reference/state-schema.md +22 -1
  192. package/skills/pr-converge/reference/stop-conditions.md +9 -7
  193. package/skills/pr-converge/scripts/README.md +34 -46
  194. package/skills/pr-converge/scripts/check_bugbot_ci.py +174 -0
  195. package/skills/pr-converge/scripts/check_convergence.py +497 -0
  196. package/skills/pr-converge/scripts/check_pending_reviews.py +154 -0
  197. package/skills/pr-converge/scripts/config/pr_converge_constants.py +118 -0
  198. package/skills/pr-converge/scripts/fetch_copilot_reviews.py +134 -0
  199. package/skills/pr-converge/scripts/post_fix_reply.py +168 -0
  200. package/skills/pr-converge/workflows/schedule-wakeup-loop.md +5 -12
  201. package/skills/qbug/SKILL.md +132 -27
  202. package/skills/session-log/SKILL.md +216 -114
  203. package/skills/session-tidy/SKILL.md +1 -1
  204. package/skills/skill-builder/SKILL.md +138 -56
  205. package/skills/skill-builder/references/delegation-map.md +72 -113
  206. package/skills/skill-builder/references/progressive-disclosure.md +122 -0
  207. package/skills/skill-builder/references/self-audit-checklist.md +92 -0
  208. package/skills/skill-builder/references/skill-types.md +228 -0
  209. package/skills/skill-builder/references/thariq-x-post-skills.json +33 -0
  210. package/skills/skill-builder/templates/gap-analysis.md +15 -8
  211. package/skills/skill-builder/workflows/improve-skill.md +86 -57
  212. package/skills/skill-builder/workflows/new-skill.md +80 -168
  213. package/skills/skill-builder/workflows/polish-skill.md +78 -54
  214. package/skills/structure-prompt/SKILL.md +50 -0
  215. package/skills/structure-prompt/reference/adversarial-tuning.md +62 -0
  216. package/skills/structure-prompt/reference/block-classification.md +27 -0
  217. package/skills/structure-prompt/reference/canonical-case.md +48 -0
  218. package/skills/structure-prompt/reference/citation-depth.md +70 -0
  219. package/skills/structure-prompt/reference/cleanup.md +33 -0
  220. package/skills/structure-prompt/reference/constraints.md +33 -0
  221. package/skills/structure-prompt/reference/directives.md +37 -0
  222. package/skills/structure-prompt/reference/examples.md +72 -0
  223. package/skills/structure-prompt/reference/instantiation.md +51 -0
  224. package/skills/structure-prompt/reference/output-contract.md +72 -0
  225. package/skills/structure-prompt/reference/per-category.md +23 -0
  226. package/skills/structure-prompt/reference/persona.md +38 -0
  227. package/skills/structure-prompt/reference/research.md +33 -0
  228. package/skills/structure-prompt/reference/structure.md +28 -0
  229. package/agents/code-standards-agent.md +0 -93
  230. package/agents/groq-coder.md +0 -113
  231. package/agents/plan-executor.md +0 -226
  232. package/agents/project-docs-analyzer.md +0 -53
  233. package/agents/project-structure-organizer-agent.md +0 -72
  234. package/agents/skill-to-agent-converter.md +0 -370
  235. package/agents/skill-writer-agent.md +0 -470
  236. package/agents/user-docs-writer.md +0 -67
  237. package/agents/workflow-visual-documenter.md +0 -82
  238. package/commands/readability-review.md +0 -20
  239. package/hooks/mypy.ini +0 -2
  240. package/hooks/notification/attention_needed_notify.py +0 -71
  241. package/hooks/notification/claude_notification_handler.py +0 -67
  242. package/hooks/notification/notification_utils.py +0 -267
  243. package/hooks/notification/subagent_complete_notify.py +0 -381
  244. package/hooks/notification/test_attention_needed_notify.py +0 -47
  245. package/hooks/notification/test_claude_notification_handler.py +0 -54
  246. package/hooks/notification/test_notification_utils.py +0 -91
  247. package/hooks/notification/test_subagent_complete_notify.py +0 -79
  248. package/scripts/config/groq_bugteam_config.py +0 -230
  249. package/scripts/config/test_groq_bugteam_config.py +0 -83
  250. package/scripts/config/test_spec_implementer_prompt.py +0 -32
  251. package/scripts/groq_bugteam.README.md +0 -131
  252. package/scripts/groq_bugteam.py +0 -647
  253. package/scripts/groq_bugteam_dotenv.py +0 -40
  254. package/scripts/groq_bugteam_spec.py +0 -226
  255. package/scripts/test_groq_bugteam.py +0 -529
  256. package/scripts/test_groq_bugteam_apply_fix_from_spec.py +0 -426
  257. package/scripts/test_groq_bugteam_dotenv.py +0 -66
  258. package/scripts/test_groq_bugteam_spec.py +0 -338
  259. package/skills/bugteam/SKILL_EVALS.md +0 -309
  260. package/skills/dream/SKILL.md +0 -118
  261. package/skills/ingest/SKILL.md +0 -40
  262. package/skills/npm-creator/SKILL.md +0 -187
  263. package/skills/readability-review/SKILL.md +0 -127
  264. package/skills/resume-review/SKILL.md +0 -261
  265. package/skills/rule-audit/SKILL.md +0 -307
  266. package/skills/rule-creator/SKILL.md +0 -150
  267. package/skills/searching-obsidian-vault/SKILL.md +0 -131
  268. package/skills/skill-writer/REFERENCE.md +0 -284
  269. package/skills/skill-writer/SKILL.md +0 -222
  270. package/skills/tdd-team/SKILL.md +0 -128
@@ -1,71 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- Notification hook - cross-platform (Windows/Linux/WSL)
4
- Plays chimes sound + shows desktop notification when Claude needs user input.
5
- """
6
-
7
- import json
8
- import os
9
- import platform
10
- import sys
11
-
12
- sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
13
- from notification_utils import (
14
- notify_ntfy,
15
- notify_discord,
16
- is_wsl,
17
- notify_windows,
18
- notify_wsl,
19
- notify_linux,
20
- sound_windows,
21
- sound_wsl,
22
- sound_linux,
23
- get_project_name,
24
- )
25
-
26
- DEFAULT_MESSAGE = "Input needed"
27
- ATTENTION_WEBHOOK_SECRET_ID = os.environ.get("BWS_DISCORD_ATTENTION_SECRET_ID", "")
28
-
29
-
30
- def get_question_from_stdin() -> str:
31
- """Extract question text from hook input JSON."""
32
- try:
33
- hook_input = json.load(sys.stdin)
34
- tool_input = hook_input.get("tool_input", {})
35
- questions = tool_input.get("questions", [])
36
- if questions:
37
- return questions[0].get("question", DEFAULT_MESSAGE)
38
- except (json.JSONDecodeError, KeyError, IndexError):
39
- pass
40
- return DEFAULT_MESSAGE
41
-
42
-
43
- def main() -> None:
44
- system = platform.system()
45
- wsl_mode = is_wsl()
46
-
47
- project_name = get_project_name()
48
- question_text = get_question_from_stdin()
49
-
50
- notify_ntfy(title=project_name, message=question_text)
51
- notify_discord(
52
- title=project_name,
53
- message=question_text,
54
- webhook_secret_id=ATTENTION_WEBHOOK_SECRET_ID,
55
- )
56
-
57
- if system == "Windows":
58
- sound_windows()
59
- notify_windows(project_name, question_text)
60
- elif wsl_mode:
61
- sound_wsl()
62
- notify_wsl(project_name, question_text)
63
- elif system == "Linux":
64
- sound_linux()
65
- notify_linux()
66
- else:
67
- print("\a", end="", flush=True)
68
-
69
-
70
- if __name__ == "__main__":
71
- main()
@@ -1,67 +0,0 @@
1
- #!/usr/bin/env python3
2
-
3
- import json
4
- import os
5
- import platform
6
- import sys
7
-
8
- sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
9
- from notification_utils import (
10
- notify_ntfy,
11
- notify_discord,
12
- is_wsl,
13
- notify_windows,
14
- notify_wsl,
15
- sound_wsl,
16
- sound_windows,
17
- get_project_name,
18
- )
19
-
20
- ATTENTION_WEBHOOK_SECRET_ID = os.environ.get("BWS_DISCORD_ATTENTION_SECRET_ID", "")
21
-
22
-
23
- def send_desktop_and_push_notification(
24
- project_name: str,
25
- notification_message: str,
26
- ntfy_priority: str,
27
- ) -> None:
28
- notify_ntfy(title=project_name, message=notification_message, priority=ntfy_priority)
29
- notify_discord(
30
- title=project_name,
31
- message=notification_message,
32
- webhook_secret_id=ATTENTION_WEBHOOK_SECRET_ID,
33
- )
34
- system = platform.system()
35
- if system == "Windows":
36
- sound_windows()
37
- notify_windows(project_name, notification_message)
38
- elif is_wsl():
39
- sound_wsl()
40
- notify_wsl(project_name, notification_message)
41
-
42
-
43
- def main() -> None:
44
- try:
45
- hook_input = json.load(sys.stdin)
46
- except (json.JSONDecodeError, ValueError):
47
- sys.exit(0)
48
-
49
- notification_type = hook_input.get("notification_type", "")
50
- notification_message = hook_input.get("message", "Claude needs attention")
51
- project_name = get_project_name()
52
-
53
- if notification_type == "idle_prompt":
54
- send_desktop_and_push_notification(project_name, notification_message, ntfy_priority="default")
55
- elif notification_type == "permission_prompt":
56
- permission_message = f"[PERMISSION] {notification_message}"
57
- send_desktop_and_push_notification(project_name, permission_message, ntfy_priority="high")
58
- elif notification_type == "auth_success":
59
- print(f"auth_success: {notification_message}", file=sys.stderr)
60
- elif notification_type == "elicitation_dialog":
61
- print(f"elicitation_dialog: {notification_message}", file=sys.stderr)
62
-
63
- sys.exit(0)
64
-
65
-
66
- if __name__ == "__main__":
67
- main()
@@ -1,267 +0,0 @@
1
- #!/usr/bin/env python3
2
-
3
- import json
4
- import os
5
- import platform
6
- import subprocess
7
- from typing import Optional
8
-
9
- NTFY_TOPIC = os.environ.get("NTFY_TOPIC", "")
10
- BWS_FETCH_TIMEOUT_SECONDS = 10
11
- BWS_EXECUTABLE_NAME = "bws"
12
- BWS_SECRET_GET_OUTPUT_FORMAT = "json"
13
- BWS_SECRET_JSON_VALUE_FIELD = "value"
14
- DISCORD_WEBHOOK_CONTENT_TYPE_HEADER = "Content-Type: application/json"
15
- DISCORD_WEBHOOK_USERNAME = "Claude Code"
16
- NTFY_BASE_URL = f"https://ntfy.sh/{NTFY_TOPIC}" if NTFY_TOPIC else ""
17
- WINDOWS_CHIMES_PATH = os.path.join(os.environ.get("SYSTEMROOT", r"C:\Windows"), "Media", "Windows Battery Critical.wav")
18
- LINUX_NOTIFICATION_SOUND = os.environ.get("NOTIFICATION_SOUND", "/usr/share/sounds/freedesktop/stereo/message.oga")
19
- LINUX_NOTIFICATION_TIMEOUT_MS = "3000"
20
- TOAST_DISPLAY_DURATION_MILLISECONDS = 6000
21
- DEFAULT_LINUX_TOAST_TITLE = "Claude Code"
22
- DEFAULT_LINUX_TOAST_MESSAGE = "Waiting for your input"
23
-
24
- TOAST_SCRIPT_TEMPLATE = r'''
25
- Add-Type -AssemblyName System.Windows.Forms
26
- Add-Type -AssemblyName System.Drawing
27
- Add-Type @"
28
- using System;
29
- using System.Runtime.InteropServices;
30
- public class Win32 {{
31
- [DllImport("user32.dll")]
32
- public static extern bool SetProcessDPIAware();
33
- [DllImport("user32.dll")]
34
- public static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);
35
- [DllImport("user32.dll")]
36
- public static extern int GetWindowLong(IntPtr hWnd, int nIndex);
37
- [DllImport("user32.dll")]
38
- public static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
39
- [DllImport("user32.dll")]
40
- public static extern bool SetLayeredWindowAttributes(IntPtr hwnd, uint crKey, byte bAlpha, uint dwFlags);
41
- public static readonly IntPtr HWND_TOPMOST = new IntPtr(-1);
42
- public const uint SWP_NOACTIVATE = 0x0010;
43
- public const uint SWP_SHOWWINDOW = 0x0040;
44
- public const int GWL_EXSTYLE = -20;
45
- public const int WS_EX_LAYERED = 0x80000;
46
- public const int WS_EX_TRANSPARENT = 0x20;
47
- public const uint LWA_ALPHA = 0x2;
48
- }}
49
- "@
50
-
51
- [Win32]::SetProcessDPIAware() | Out-Null
52
-
53
- $form = New-Object System.Windows.Forms.Form
54
- $form.FormBorderStyle = [System.Windows.Forms.FormBorderStyle]::None
55
- $form.Size = New-Object System.Drawing.Size(520, 110)
56
- $form.ShowInTaskbar = $false
57
- $form.BackColor = [System.Drawing.Color]::FromArgb(66, 135, 245)
58
- $form.StartPosition = [System.Windows.Forms.FormStartPosition]::Manual
59
-
60
- $screen = [System.Windows.Forms.Screen]::PrimaryScreen.WorkingArea
61
- $x = [int]($screen.Left + ($screen.Width - 520) / 2)
62
- $y = [int]($screen.Bottom - 110 - 50)
63
- $form.Location = New-Object System.Drawing.Point($x, $y)
64
-
65
- $inner = New-Object System.Windows.Forms.Panel
66
- $inner.Size = New-Object System.Drawing.Size(514, 104)
67
- $inner.Location = New-Object System.Drawing.Point(3, 3)
68
- $inner.BackColor = [System.Drawing.Color]::FromArgb(45, 45, 45)
69
- $form.Controls.Add($inner)
70
-
71
- $titleLabel = New-Object System.Windows.Forms.Label
72
- $titleLabel.Text = "{title}"
73
- $titleLabel.Font = New-Object System.Drawing.Font("Segoe UI", 12, [System.Drawing.FontStyle]::Bold)
74
- $titleLabel.ForeColor = [System.Drawing.Color]::FromArgb(120, 180, 255)
75
- $titleLabel.AutoSize = $false
76
- $titleLabel.Size = New-Object System.Drawing.Size(514, 30)
77
- $titleLabel.Location = New-Object System.Drawing.Point(0, 8)
78
- $titleLabel.TextAlign = [System.Drawing.ContentAlignment]::MiddleCenter
79
- $inner.Controls.Add($titleLabel)
80
-
81
- $messageLabel = New-Object System.Windows.Forms.Label
82
- $messageLabel.Text = "{message}"
83
- $messageLabel.Font = New-Object System.Drawing.Font("Segoe UI", 11)
84
- $messageLabel.ForeColor = [System.Drawing.Color]::White
85
- $messageLabel.AutoSize = $false
86
- $messageLabel.Size = New-Object System.Drawing.Size(500, 58)
87
- $messageLabel.Location = New-Object System.Drawing.Point(7, 40)
88
- $messageLabel.TextAlign = [System.Drawing.ContentAlignment]::TopCenter
89
- $inner.Controls.Add($messageLabel)
90
-
91
- $timer = New-Object System.Windows.Forms.Timer
92
- $timer.Interval = {toast_duration}
93
- $timer.Add_Tick({{ $form.Close() }})
94
- $timer.Start()
95
-
96
- $exStyle = [Win32]::GetWindowLong($form.Handle, [Win32]::GWL_EXSTYLE)
97
- [Win32]::SetWindowLong($form.Handle, [Win32]::GWL_EXSTYLE, $exStyle -bor [Win32]::WS_EX_LAYERED -bor [Win32]::WS_EX_TRANSPARENT)
98
- [Win32]::SetLayeredWindowAttributes($form.Handle, 0, 230, [Win32]::LWA_ALPHA)
99
- [Win32]::SetWindowPos($form.Handle, [Win32]::HWND_TOPMOST, $x, $y, 520, 110, [Win32]::SWP_NOACTIVATE -bor [Win32]::SWP_SHOWWINDOW)
100
- $form.Show()
101
- [System.Windows.Forms.Application]::Run($form)
102
- '''
103
-
104
-
105
- def is_wsl() -> bool:
106
- if platform.system() != "Linux":
107
- return False
108
- try:
109
- with open("/proc/version") as proc_version_file:
110
- return "microsoft" in proc_version_file.read().lower()
111
- except FileNotFoundError:
112
- return False
113
-
114
-
115
- def build_toast_script(title: str, message: str) -> str:
116
- safe_title = title.replace('"', '`"').replace("'", "`'")
117
- safe_message = message.replace('"', '`"').replace("'", "`'")
118
- return TOAST_SCRIPT_TEMPLATE.format(
119
- title=safe_title,
120
- message=safe_message,
121
- toast_duration=TOAST_DISPLAY_DURATION_MILLISECONDS,
122
- )
123
-
124
-
125
- def notify_wsl(title: str, message: str) -> None:
126
- script = build_toast_script(title, message)
127
- try:
128
- subprocess.Popen(
129
- ["powershell.exe", "-ExecutionPolicy", "Bypass", "-Command", script],
130
- stdout=subprocess.DEVNULL,
131
- stderr=subprocess.DEVNULL,
132
- start_new_session=True
133
- )
134
- except FileNotFoundError:
135
- pass
136
-
137
-
138
- def notify_windows(title: str, message: str) -> None:
139
- script = build_toast_script(title, message)
140
- subprocess.Popen(
141
- ["powershell", "-ExecutionPolicy", "Bypass", "-Command", script],
142
- stdout=subprocess.DEVNULL,
143
- stderr=subprocess.DEVNULL,
144
- creationflags=subprocess.CREATE_NO_WINDOW if hasattr(subprocess, "CREATE_NO_WINDOW") else 0
145
- )
146
-
147
-
148
- def notify_ntfy(title: str, message: str, priority: str = "high") -> None:
149
- if not NTFY_TOPIC:
150
- return
151
- try:
152
- subprocess.Popen(
153
- [
154
- "curl", "-s",
155
- "-H", f"Priority: {priority}",
156
- "-H", "Tags: bell",
157
- "-H", f"Title: {title}",
158
- "-d", message,
159
- NTFY_BASE_URL,
160
- ],
161
- stdout=subprocess.DEVNULL,
162
- stderr=subprocess.DEVNULL
163
- )
164
- except FileNotFoundError:
165
- pass
166
-
167
-
168
- def fetch_bws_secret(secret_id: str) -> Optional[str]:
169
- if not secret_id:
170
- return None
171
- try:
172
- completed_bws_process = subprocess.run(
173
- [BWS_EXECUTABLE_NAME, "secret", "get", secret_id, "--output", BWS_SECRET_GET_OUTPUT_FORMAT],
174
- capture_output=True,
175
- text=True,
176
- timeout=BWS_FETCH_TIMEOUT_SECONDS,
177
- check=True,
178
- )
179
- except (FileNotFoundError, subprocess.TimeoutExpired, subprocess.CalledProcessError):
180
- return None
181
- try:
182
- parsed_payload = json.loads(completed_bws_process.stdout)
183
- except json.JSONDecodeError:
184
- return None
185
- secret_value = parsed_payload.get(BWS_SECRET_JSON_VALUE_FIELD)
186
- if isinstance(secret_value, str):
187
- return secret_value
188
- return None
189
-
190
-
191
- def notify_discord(title: str, message: str, webhook_secret_id: str) -> None:
192
- if not webhook_secret_id:
193
- return
194
- webhook_url = fetch_bws_secret(webhook_secret_id)
195
- if not webhook_url:
196
- return
197
- discord_payload = json.dumps({
198
- "username": DISCORD_WEBHOOK_USERNAME,
199
- "content": f"**{title}**\n{message}",
200
- })
201
- try:
202
- subprocess.Popen(
203
- [
204
- "curl", "-s",
205
- "-H", DISCORD_WEBHOOK_CONTENT_TYPE_HEADER,
206
- "-d", discord_payload,
207
- webhook_url,
208
- ],
209
- stdout=subprocess.DEVNULL,
210
- stderr=subprocess.DEVNULL,
211
- )
212
- except FileNotFoundError:
213
- pass
214
-
215
-
216
- def notify_linux() -> None:
217
- try:
218
- subprocess.Popen(
219
- [
220
- "notify-send", "-t", LINUX_NOTIFICATION_TIMEOUT_MS,
221
- "-u", "normal", "-i", "dialog-warning",
222
- DEFAULT_LINUX_TOAST_TITLE, DEFAULT_LINUX_TOAST_MESSAGE,
223
- ],
224
- stdout=subprocess.DEVNULL,
225
- stderr=subprocess.DEVNULL
226
- )
227
- except FileNotFoundError:
228
- return
229
-
230
-
231
- def sound_windows() -> None:
232
- subprocess.Popen(
233
- ["powershell", "-WindowStyle", "Hidden", "-Command", f"(New-Object Media.SoundPlayer '{WINDOWS_CHIMES_PATH}').PlaySync()"],
234
- stdout=subprocess.DEVNULL,
235
- stderr=subprocess.DEVNULL,
236
- creationflags=subprocess.CREATE_NO_WINDOW if hasattr(subprocess, "CREATE_NO_WINDOW") else 0
237
- )
238
-
239
-
240
- def sound_wsl() -> None:
241
- try:
242
- subprocess.Popen(
243
- ["powershell.exe", "-WindowStyle", "Hidden", "-Command", f"(New-Object Media.SoundPlayer '{WINDOWS_CHIMES_PATH}').PlaySync()"],
244
- stdout=subprocess.DEVNULL,
245
- stderr=subprocess.DEVNULL
246
- )
247
- except FileNotFoundError:
248
- pass
249
-
250
-
251
- def sound_linux() -> None:
252
- if os.path.exists(LINUX_NOTIFICATION_SOUND):
253
- for each_player in ["paplay", "aplay", "play"]:
254
- try:
255
- subprocess.Popen(
256
- [each_player, LINUX_NOTIFICATION_SOUND],
257
- stdout=subprocess.DEVNULL,
258
- stderr=subprocess.DEVNULL
259
- )
260
- return
261
- except FileNotFoundError:
262
- continue
263
- print("\a", end="", flush=True)
264
-
265
-
266
- def get_project_name() -> str:
267
- return os.path.basename(os.getcwd())