claude-dev-env 1.38.1 → 1.40.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 (282) 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 +199 -0
  7. package/_shared/pr-loop/scripts/config/reviews_disabled_constants.py +8 -0
  8. package/_shared/pr-loop/scripts/post_audit_thread.py +1242 -0
  9. package/_shared/pr-loop/scripts/preflight.py +129 -2
  10. package/_shared/pr-loop/scripts/reviews_disabled.py +59 -0
  11. package/_shared/pr-loop/scripts/tests/test_code_rules_gate.py +0 -19
  12. package/_shared/pr-loop/scripts/tests/test_post_audit_thread.py +1116 -0
  13. package/_shared/pr-loop/scripts/tests/test_post_audit_thread_constants.py +127 -0
  14. package/_shared/pr-loop/scripts/tests/test_preflight.py +41 -0
  15. package/_shared/pr-loop/scripts/tests/test_reviews_disabled.py +36 -0
  16. package/_shared/pr-loop/state-schema.md +1 -1
  17. package/agents/clean-coder.md +2 -2
  18. package/agents/pr-description-writer.md +150 -52
  19. package/bin/install.mjs +6 -7
  20. package/bin/install.test.mjs +8 -0
  21. package/commands/doc-gist.md +16 -0
  22. package/commands/plan.md +0 -2
  23. package/commands/review-plan.md +1 -1
  24. package/docs/CODE_RULES.md +122 -2
  25. package/docs/PR_DESCRIPTION_GUIDE.md +127 -64
  26. package/hooks/blocking/bot_mention_comment_blocker.py +75 -0
  27. package/hooks/blocking/code_rules_enforcer.py +1143 -129
  28. package/hooks/blocking/convergence_gate_blocker.py +130 -0
  29. package/hooks/blocking/destructive_command_blocker.py +74 -0
  30. package/hooks/blocking/gh_body_arg_blocker.py +30 -0
  31. package/hooks/blocking/md_to_html_blocker.py +119 -0
  32. package/hooks/blocking/pr_description_enforcer.py +57 -22
  33. package/hooks/blocking/test_bot_mention_comment_blocker.py +131 -0
  34. package/hooks/blocking/test_code_rules_enforcer.py +21 -0
  35. package/hooks/blocking/test_code_rules_enforcer_any_exempt_files.py +70 -0
  36. package/hooks/blocking/test_code_rules_enforcer_any_imports_and_cast.py +92 -0
  37. package/hooks/blocking/test_code_rules_enforcer_banned_import_alias.py +143 -0
  38. package/hooks/blocking/test_code_rules_enforcer_banned_prefixes.py +152 -0
  39. package/hooks/blocking/test_code_rules_enforcer_bare_except.py +120 -0
  40. package/hooks/blocking/test_code_rules_enforcer_boundary_types.py +175 -0
  41. package/hooks/blocking/test_code_rules_enforcer_cap_meta.py +0 -1
  42. package/hooks/blocking/test_code_rules_enforcer_collection_prefix.py +50 -0
  43. package/hooks/blocking/test_code_rules_enforcer_docstring_format.py +255 -0
  44. package/hooks/blocking/test_code_rules_enforcer_inline_tuple_string_magic.py +130 -0
  45. package/hooks/blocking/test_code_rules_enforcer_stub_implementations.py +141 -0
  46. package/hooks/blocking/test_code_rules_enforcer_test_branching.py +143 -0
  47. package/hooks/blocking/test_code_rules_enforcer_thin_wrapper_files.py +169 -0
  48. package/hooks/blocking/test_code_rules_enforcer_todo_markers.py +99 -0
  49. package/hooks/blocking/test_code_rules_enforcer_typed_dict_pairs.py +141 -0
  50. package/hooks/blocking/test_convergence_gate_blocker.py +63 -0
  51. package/hooks/blocking/test_destructive_command_blocker.py +146 -0
  52. package/hooks/blocking/test_destructive_command_blocker_no_verify.py +102 -0
  53. package/hooks/blocking/test_gh_body_arg_blocker.py +45 -0
  54. package/hooks/blocking/test_md_to_html_blocker.py +317 -0
  55. package/hooks/blocking/test_pr_description_enforcer.py +69 -8
  56. package/hooks/config/any_type_config.py +7 -0
  57. package/hooks/config/banned_identifiers_constants.py +11 -0
  58. package/hooks/config/blocking_check_limits.py +38 -0
  59. package/hooks/config/bot_mention_comment_blocker_constants.py +20 -0
  60. package/hooks/config/code_rules_enforcer_constants.py +53 -0
  61. package/hooks/config/convergence_branch_constants.py +9 -0
  62. package/hooks/config/doc_gist_auto_publish_constants.py +18 -0
  63. package/hooks/config/html_companion_constants.py +20 -0
  64. package/hooks/config/inline_tuple_string_magic_constants.py +22 -0
  65. package/hooks/config/pr_description_enforcer_constants.py +14 -0
  66. package/hooks/config/test_banned_identifiers_constants.py +17 -0
  67. package/hooks/hooks.json +28 -20
  68. package/hooks/pyproject.toml +69 -0
  69. package/hooks/validators/mypy_integration.py +47 -1
  70. package/hooks/validators/run_all_validators.py +3 -3
  71. package/hooks/validators/test_mypy_integration.py +50 -1
  72. package/hooks/workflow/doc_gist_auto_publish.py +144 -0
  73. package/hooks/workflow/md_to_html_companion.py +365 -0
  74. package/hooks/workflow/test_doc_gist_auto_publish.py +117 -0
  75. package/hooks/workflow/test_md_to_html_companion.py +452 -0
  76. package/package.json +1 -1
  77. package/rules/gh-body-file.md +2 -0
  78. package/scripts/Install-SweepEmptyDirs.ps1 +111 -0
  79. package/scripts/check.ps1 +106 -0
  80. package/scripts/config/timing.py +11 -0
  81. package/scripts/sweep_empty_dirs.py +138 -0
  82. package/scripts/sync_to_cursor/rules.py +1 -1
  83. package/scripts/test_sweep_empty_dirs.py +183 -0
  84. package/skills/_shared/pr-loop/prompts/pr-consistency-audit.xml +323 -0
  85. package/skills/_shared/pr-loop/scripts/_cli_utils.py +22 -0
  86. package/skills/_shared/pr-loop/scripts/_path_resolver.py +165 -0
  87. package/skills/_shared/pr-loop/scripts/_xml_utils.py +20 -0
  88. package/skills/_shared/pr-loop/scripts/build_audit_prompt.py +182 -0
  89. package/skills/_shared/pr-loop/scripts/build_fix_prompt.py +185 -0
  90. package/skills/_shared/pr-loop/scripts/config/__init__.py +0 -0
  91. package/skills/_shared/pr-loop/scripts/config/path_resolver_constants.py +78 -0
  92. package/skills/_shared/pr-loop/scripts/init_loop_state.py +135 -0
  93. package/skills/_shared/pr-loop/scripts/teardown_worktrees.py +175 -0
  94. package/skills/_shared/pr-loop/scripts/write_audit_outcomes.py +182 -0
  95. package/skills/_shared/pr-loop/scripts/write_fix_outcomes.py +206 -0
  96. package/skills/bugteam/CONSTRAINTS.md +21 -22
  97. package/skills/bugteam/EXAMPLES.md +3 -3
  98. package/skills/bugteam/PROMPTS.md +227 -67
  99. package/skills/bugteam/SKILL.md +132 -455
  100. package/skills/bugteam/reference/README.md +1 -1
  101. package/skills/bugteam/reference/audit-and-teammates.md +112 -39
  102. package/skills/bugteam/reference/audit-contract.md +4 -22
  103. package/skills/bugteam/reference/copilot-gap-analysis.md +8 -5
  104. package/skills/bugteam/reference/design-rationale.md +2 -2
  105. package/skills/bugteam/reference/github-pr-reviews.md +50 -57
  106. package/skills/bugteam/reference/obstacles/audit-assign-ids.md +13 -0
  107. package/skills/bugteam/reference/obstacles/audit-capture-excerpts.md +13 -0
  108. package/skills/bugteam/reference/obstacles/audit-walk-categories.md +13 -0
  109. package/skills/bugteam/reference/obstacles/audit-write-xml.md +13 -0
  110. package/skills/bugteam/reference/obstacles/fix-append-summary.md +13 -0
  111. package/skills/bugteam/reference/obstacles/fix-apply-fixes.md +13 -0
  112. package/skills/bugteam/reference/obstacles/fix-git-add-commit.md +13 -0
  113. package/skills/bugteam/reference/obstacles/fix-git-push.md +13 -0
  114. package/skills/bugteam/reference/obstacles/fix-post-reply.md +13 -0
  115. package/skills/bugteam/reference/obstacles/fix-publish-summary.md +13 -0
  116. package/skills/bugteam/reference/obstacles/fix-py-compile.md +13 -0
  117. package/skills/bugteam/reference/obstacles/fix-read-files.md +13 -0
  118. package/skills/bugteam/reference/obstacles/fix-resolve-thread.md +13 -0
  119. package/skills/bugteam/reference/obstacles/fix-test-suite.md +13 -0
  120. package/skills/bugteam/reference/obstacles/fix-violation-count.md +13 -0
  121. package/skills/bugteam/reference/obstacles/fix-write-xml.md +13 -0
  122. package/skills/bugteam/reference/team-setup.md +111 -9
  123. package/skills/bugteam/reference/teardown-publish-permissions.md +39 -8
  124. package/skills/bugteam/scripts/README.md +60 -0
  125. package/skills/bugteam/scripts/_claude_permissions_common.py +358 -0
  126. package/skills/bugteam/scripts/bugteam_code_rules_gate.py +976 -0
  127. package/skills/bugteam/scripts/bugteam_fix_hookspath.py +375 -0
  128. package/skills/bugteam/scripts/bugteam_preflight.py +328 -0
  129. package/skills/bugteam/scripts/config/bugteam_code_rules_gate_constants.py +25 -0
  130. package/skills/bugteam/scripts/config/bugteam_fix_hookspath_constants.py +26 -0
  131. package/skills/bugteam/scripts/config/bugteam_preflight_constants.py +35 -0
  132. package/skills/bugteam/scripts/config/claude_permissions_common_constants.py +20 -0
  133. package/skills/bugteam/scripts/config/probe_code_rules_enforcer_check_constants.py +12 -0
  134. package/skills/bugteam/scripts/config/windows_safe_rmtree_constants.py +7 -0
  135. package/skills/bugteam/scripts/grant_project_claude_permissions.py +175 -0
  136. package/skills/bugteam/scripts/probe_code_rules_enforcer_check.py +107 -0
  137. package/skills/bugteam/scripts/revoke_project_claude_permissions.py +220 -0
  138. package/skills/bugteam/scripts/test__claude_permissions_common.py +112 -0
  139. package/skills/bugteam/scripts/test_bugteam_code_rules_gate.py +400 -0
  140. package/skills/bugteam/scripts/test_bugteam_fix_hookspath.py +384 -0
  141. package/skills/bugteam/scripts/test_bugteam_preflight.py +309 -0
  142. package/skills/bugteam/scripts/test_claude_permissions_common.py +195 -0
  143. package/skills/bugteam/scripts/test_grant_project_claude_permissions.py +55 -0
  144. package/skills/bugteam/scripts/test_probe_code_rules_enforcer_check.py +76 -0
  145. package/skills/bugteam/scripts/test_revoke_project_claude_permissions.py +55 -0
  146. package/skills/bugteam/scripts/test_windows_safe_rmtree.py +108 -0
  147. package/skills/bugteam/scripts/windows_safe_rmtree.py +100 -0
  148. package/skills/bugteam/test_skill_additions.py +1 -11
  149. package/skills/code/SKILL.md +176 -0
  150. package/skills/copilot-review/SKILL.md +16 -0
  151. package/skills/doc-gist/SKILL.md +99 -0
  152. package/skills/doc-gist/references/examples/01-exploration-code-approaches.html +453 -0
  153. package/skills/doc-gist/references/examples/02-exploration-visual-designs.html +515 -0
  154. package/skills/doc-gist/references/examples/03-code-review-pr.html +638 -0
  155. package/skills/doc-gist/references/examples/04-code-understanding.html +491 -0
  156. package/skills/doc-gist/references/examples/05-design-system.html +629 -0
  157. package/skills/doc-gist/references/examples/06-component-variants.html +605 -0
  158. package/skills/doc-gist/references/examples/07-prototype-animation.html +455 -0
  159. package/skills/doc-gist/references/examples/08-prototype-interaction.html +396 -0
  160. package/skills/doc-gist/references/examples/09-slide-deck.html +592 -0
  161. package/skills/doc-gist/references/examples/10-svg-illustrations.html +492 -0
  162. package/skills/doc-gist/references/examples/11-status-report.html +528 -0
  163. package/skills/doc-gist/references/examples/12-incident-report.html +596 -0
  164. package/skills/doc-gist/references/examples/13-flowchart-diagram.html +395 -0
  165. package/skills/doc-gist/references/examples/14-research-feature-explainer.html +381 -0
  166. package/skills/doc-gist/references/examples/15-research-concept-explainer.html +368 -0
  167. package/skills/doc-gist/references/examples/16-implementation-plan.html +702 -0
  168. package/skills/doc-gist/references/examples/17-pr-writeup.html +595 -0
  169. package/skills/doc-gist/references/examples/18-editor-triage-board.html +573 -0
  170. package/skills/doc-gist/references/examples/19-editor-feature-flags.html +663 -0
  171. package/skills/doc-gist/references/examples/20-editor-prompt-tuner.html +722 -0
  172. package/skills/doc-gist/references/examples/README.md +5 -0
  173. package/skills/doc-gist/scripts/config/__init__.py +0 -0
  174. package/skills/doc-gist/scripts/config/gist_upload_constants.py +16 -0
  175. package/skills/doc-gist/scripts/gist_upload.py +177 -0
  176. package/skills/doc-gist/scripts/test_gist_upload.py +51 -0
  177. package/skills/findbugs/SKILL.md +96 -2
  178. package/skills/monitor-open-prs/SKILL.md +14 -32
  179. package/skills/monitor-open-prs/test_skill_contract.py +0 -11
  180. package/skills/pr-consistency-audit/SKILL.md +112 -0
  181. package/skills/pr-consistency-audit/reference/detection-rules.md +96 -0
  182. package/skills/pr-consistency-audit/reference/illustrations.md +78 -0
  183. package/skills/pr-converge/SKILL.md +229 -23
  184. package/skills/pr-converge/config/__init__.py +0 -0
  185. package/skills/pr-converge/config/constants.py +63 -0
  186. package/skills/pr-converge/reference/convergence-gates.md +138 -44
  187. package/skills/pr-converge/reference/examples.md +43 -11
  188. package/skills/pr-converge/reference/fix-protocol.md +6 -5
  189. package/skills/pr-converge/reference/ground-rules.md +5 -3
  190. package/skills/pr-converge/reference/multi-pr-orchestration.md +44 -19
  191. package/skills/pr-converge/reference/obstacles/fix-post-replies.md +13 -0
  192. package/skills/pr-converge/reference/obstacles/fix-publish-summary.md +13 -0
  193. package/skills/pr-converge/reference/obstacles/fix-push.md +13 -0
  194. package/skills/pr-converge/reference/obstacles/fix-read-filelines.md +13 -0
  195. package/skills/pr-converge/reference/obstacles/fix-reset-state.md +13 -0
  196. package/skills/pr-converge/reference/obstacles/fix-resolve-threads.md +13 -0
  197. package/skills/pr-converge/reference/obstacles/fix-spawn-clean-coder.md +13 -0
  198. package/skills/pr-converge/reference/obstacles/fix-stage-commit.md +13 -0
  199. package/skills/pr-converge/reference/obstacles/fix-trigger-bugbot.md +13 -0
  200. package/skills/pr-converge/reference/obstacles/fix-write-test.md +13 -0
  201. package/skills/pr-converge/reference/per-tick.md +107 -31
  202. package/skills/pr-converge/reference/state-schema.md +22 -1
  203. package/skills/pr-converge/reference/stop-conditions.md +9 -7
  204. package/skills/pr-converge/scripts/README.md +34 -46
  205. package/skills/pr-converge/scripts/check_bugbot_ci.py +279 -0
  206. package/skills/pr-converge/scripts/check_convergence.py +497 -0
  207. package/skills/pr-converge/scripts/check_pending_reviews.py +154 -0
  208. package/skills/pr-converge/scripts/config/pr_converge_constants.py +118 -0
  209. package/skills/pr-converge/scripts/fetch_copilot_reviews.py +134 -0
  210. package/skills/pr-converge/scripts/post_fix_reply.py +168 -0
  211. package/skills/pr-converge/scripts/test_check_bugbot_ci.py +312 -0
  212. package/skills/pr-converge/workflows/schedule-wakeup-loop.md +5 -12
  213. package/skills/qbug/SKILL.md +157 -27
  214. package/skills/session-log/SKILL.md +216 -114
  215. package/skills/session-tidy/SKILL.md +1 -1
  216. package/skills/skill-builder/SKILL.md +138 -56
  217. package/skills/skill-builder/references/delegation-map.md +72 -113
  218. package/skills/skill-builder/references/progressive-disclosure.md +122 -0
  219. package/skills/skill-builder/references/self-audit-checklist.md +92 -0
  220. package/skills/skill-builder/references/skill-types.md +228 -0
  221. package/skills/skill-builder/references/thariq-x-post-skills.json +33 -0
  222. package/skills/skill-builder/templates/gap-analysis.md +15 -8
  223. package/skills/skill-builder/workflows/improve-skill.md +86 -57
  224. package/skills/skill-builder/workflows/new-skill.md +80 -168
  225. package/skills/skill-builder/workflows/polish-skill.md +78 -54
  226. package/skills/structure-prompt/SKILL.md +50 -0
  227. package/skills/structure-prompt/reference/adversarial-tuning.md +62 -0
  228. package/skills/structure-prompt/reference/block-classification.md +27 -0
  229. package/skills/structure-prompt/reference/canonical-case.md +48 -0
  230. package/skills/structure-prompt/reference/citation-depth.md +70 -0
  231. package/skills/structure-prompt/reference/cleanup.md +33 -0
  232. package/skills/structure-prompt/reference/constraints.md +33 -0
  233. package/skills/structure-prompt/reference/directives.md +37 -0
  234. package/skills/structure-prompt/reference/examples.md +72 -0
  235. package/skills/structure-prompt/reference/instantiation.md +51 -0
  236. package/skills/structure-prompt/reference/output-contract.md +72 -0
  237. package/skills/structure-prompt/reference/per-category.md +23 -0
  238. package/skills/structure-prompt/reference/persona.md +38 -0
  239. package/skills/structure-prompt/reference/research.md +33 -0
  240. package/skills/structure-prompt/reference/structure.md +28 -0
  241. package/agents/code-standards-agent.md +0 -93
  242. package/agents/groq-coder.md +0 -113
  243. package/agents/plan-executor.md +0 -226
  244. package/agents/project-docs-analyzer.md +0 -53
  245. package/agents/project-structure-organizer-agent.md +0 -72
  246. package/agents/skill-to-agent-converter.md +0 -370
  247. package/agents/skill-writer-agent.md +0 -470
  248. package/agents/user-docs-writer.md +0 -67
  249. package/agents/workflow-visual-documenter.md +0 -82
  250. package/commands/readability-review.md +0 -20
  251. package/hooks/mypy.ini +0 -2
  252. package/hooks/notification/attention_needed_notify.py +0 -71
  253. package/hooks/notification/claude_notification_handler.py +0 -67
  254. package/hooks/notification/notification_utils.py +0 -267
  255. package/hooks/notification/subagent_complete_notify.py +0 -381
  256. package/hooks/notification/test_attention_needed_notify.py +0 -47
  257. package/hooks/notification/test_claude_notification_handler.py +0 -54
  258. package/hooks/notification/test_notification_utils.py +0 -91
  259. package/hooks/notification/test_subagent_complete_notify.py +0 -79
  260. package/scripts/config/groq_bugteam_config.py +0 -230
  261. package/scripts/config/test_groq_bugteam_config.py +0 -83
  262. package/scripts/config/test_spec_implementer_prompt.py +0 -32
  263. package/scripts/groq_bugteam.README.md +0 -131
  264. package/scripts/groq_bugteam.py +0 -647
  265. package/scripts/groq_bugteam_dotenv.py +0 -40
  266. package/scripts/groq_bugteam_spec.py +0 -226
  267. package/scripts/test_groq_bugteam.py +0 -529
  268. package/scripts/test_groq_bugteam_apply_fix_from_spec.py +0 -426
  269. package/scripts/test_groq_bugteam_dotenv.py +0 -66
  270. package/scripts/test_groq_bugteam_spec.py +0 -338
  271. package/skills/bugteam/SKILL_EVALS.md +0 -309
  272. package/skills/dream/SKILL.md +0 -118
  273. package/skills/ingest/SKILL.md +0 -40
  274. package/skills/npm-creator/SKILL.md +0 -187
  275. package/skills/readability-review/SKILL.md +0 -127
  276. package/skills/resume-review/SKILL.md +0 -261
  277. package/skills/rule-audit/SKILL.md +0 -307
  278. package/skills/rule-creator/SKILL.md +0 -150
  279. package/skills/searching-obsidian-vault/SKILL.md +0 -131
  280. package/skills/skill-writer/REFERENCE.md +0 -284
  281. package/skills/skill-writer/SKILL.md +0 -222
  282. 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())