claude-code-tools 1.29.1__tar.gz → 1.29.2__tar.gz

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 (145) hide show
  1. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/PKG-INFO +1 -1
  2. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/__init__.py +1 -1
  3. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/agent_tunnel/config.py +8 -0
  4. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/agent_tunnel/mattermost_bot.py +100 -11
  5. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/docs/agent-tunnel-spec.md +8 -0
  6. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/pyproject.toml +2 -2
  7. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/.gitignore +0 -0
  8. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/LICENSE +0 -0
  9. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/README.md +0 -0
  10. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/action_rpc.py +0 -0
  11. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/agent_tunnel/__init__.py +0 -0
  12. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/agent_tunnel/backends.py +0 -0
  13. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/agent_tunnel/cli.py +0 -0
  14. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/agent_tunnel/convert.py +0 -0
  15. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/agent_tunnel/discord_bot.py +0 -0
  16. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/agent_tunnel/http_frontend.py +0 -0
  17. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/agent_tunnel/locking.py +0 -0
  18. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/agent_tunnel/paths.py +0 -0
  19. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/agent_tunnel/readonly.py +0 -0
  20. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/agent_tunnel/registry.py +0 -0
  21. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/agent_tunnel/relay.py +0 -0
  22. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/agent_tunnel/serve.py +0 -0
  23. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/agent_tunnel/session.py +0 -0
  24. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/agent_tunnel/store.py +0 -0
  25. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/agent_tunnel/tmux.py +0 -0
  26. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/agent_tunnel/trust.py +0 -0
  27. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/agent_tunnel/turn_log.py +0 -0
  28. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/aichat.py +0 -0
  29. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/amux/__init__.py +0 -0
  30. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/amux/__main__.py +0 -0
  31. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/amux/activity.py +0 -0
  32. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/amux/cache.py +0 -0
  33. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/amux/cli.py +0 -0
  34. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/amux/detect.py +0 -0
  35. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/amux/filters.py +0 -0
  36. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/amux/model.py +0 -0
  37. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/amux/render.py +0 -0
  38. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/amux/scan.py +0 -0
  39. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/claude_continue.py +0 -0
  40. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/cli_passthrough.py +0 -0
  41. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/codex_app_server_rpc.py +0 -0
  42. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/codex_continue.py +0 -0
  43. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/codex_server.py +0 -0
  44. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/codex_server_callback.py +0 -0
  45. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/codex_server_cli.py +0 -0
  46. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/codex_server_fingerprint.py +0 -0
  47. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/codex_server_generation.py +0 -0
  48. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/codex_server_legacy.py +0 -0
  49. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/codex_server_models.py +0 -0
  50. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/codex_server_process.py +0 -0
  51. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/codex_server_reservation.py +0 -0
  52. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/codex_server_resume.py +0 -0
  53. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/codex_server_retry.py +0 -0
  54. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/codex_server_reuse.py +0 -0
  55. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/codex_server_state.py +0 -0
  56. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/codex_server_supervisor.py +0 -0
  57. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/codex_server_wait.py +0 -0
  58. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/codex_server_worker.py +0 -0
  59. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/config.py +0 -0
  60. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/csv2gsheet.py +0 -0
  61. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/delete_session.py +0 -0
  62. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/dotenv_vault.py +0 -0
  63. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/env_safe.py +0 -0
  64. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/export_all.py +0 -0
  65. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/export_claude_session.py +0 -0
  66. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/export_codex_session.py +0 -0
  67. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/export_session.py +0 -0
  68. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/find_claude_session.py +0 -0
  69. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/find_codex_session.py +0 -0
  70. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/find_original_session.py +0 -0
  71. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/find_session.py +0 -0
  72. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/find_trimmed_sessions.py +0 -0
  73. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/fix_session.py +0 -0
  74. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/gdoc2docx.py +0 -0
  75. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/gdoc2md.py +0 -0
  76. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/github_wake.py +0 -0
  77. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/github_watch_daemon.py +0 -0
  78. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/github_watch_store.py +0 -0
  79. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/gsheet2csv.py +0 -0
  80. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/helper_sessions.py +0 -0
  81. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/issue_reply_delivery.py +0 -0
  82. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/legacy_session_wrappers.py +0 -0
  83. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/md2gdoc.py +0 -0
  84. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/move_account.py +0 -0
  85. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/msg/__init__.py +0 -0
  86. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/msg/cli.py +0 -0
  87. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/msg/hooks.py +0 -0
  88. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/msg/migrations.py +0 -0
  89. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/msg/models.py +0 -0
  90. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/msg/prompt_detect.py +0 -0
  91. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/msg/store.py +0 -0
  92. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/msg/watcher.py +0 -0
  93. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/node_menu_ui.py +0 -0
  94. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/port_claude_noise.py +0 -0
  95. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/port_claude_to_codex.py +0 -0
  96. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/port_codex_flatten.py +0 -0
  97. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/port_codex_to_claude.py +0 -0
  98. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/port_render.py +0 -0
  99. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/port_service.py +0 -0
  100. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/resolve_session.py +0 -0
  101. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/resolve_session_names.py +0 -0
  102. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/resolve_session_render.py +0 -0
  103. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/search_index.py +0 -0
  104. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/session_cli_resolution.py +0 -0
  105. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/session_lineage.py +0 -0
  106. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/session_menu.py +0 -0
  107. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/session_menu_cli.py +0 -0
  108. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/session_resolution.py +0 -0
  109. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/session_selection.py +0 -0
  110. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/session_utils.py +0 -0
  111. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/smart_trim.py +0 -0
  112. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/smart_trim_core.py +0 -0
  113. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/tmux_cli_controller.py +0 -0
  114. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/tmux_execution_helpers.py +0 -0
  115. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/tmux_remote_controller.py +0 -0
  116. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/trim_in_place.py +0 -0
  117. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/trim_session.py +0 -0
  118. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/trim_session_claude.py +0 -0
  119. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/trim_session_codex.py +0 -0
  120. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/workflow_cli.py +0 -0
  121. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/workflow_cli_contract.py +0 -0
  122. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/workflow_cli_formatting.py +0 -0
  123. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/workflow_cli_identity_policy.py +0 -0
  124. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/workflow_cli_manifest.py +0 -0
  125. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/workflow_cli_projection.py +0 -0
  126. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/workflow_cli_rendering.py +0 -0
  127. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/workflow_cli_snapshots.py +0 -0
  128. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/workflow_cli_store_backends.py +0 -0
  129. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/workflow_processes.py +0 -0
  130. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/workflow_runs.py +0 -0
  131. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/workflow_store_io.py +0 -0
  132. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/claude_code_tools/workflow_validation.py +0 -0
  133. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/docs/claude-code-tmux-tutorials.md +0 -0
  134. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/docs/dot-zshrc.md +0 -0
  135. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/docs/find-claude-session.md +0 -0
  136. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/docs/intercom-spec.md +0 -0
  137. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/docs/lmsh.md +0 -0
  138. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/docs/local-llm-setup.md +0 -0
  139. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/docs/tmux-cli-instructions.md +0 -0
  140. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/docs/vault-documentation.md +0 -0
  141. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/hatch_build.py +0 -0
  142. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/node_ui/action_config.js +0 -0
  143. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/node_ui/menu.js +0 -0
  144. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/node_ui/package-lock.json +0 -0
  145. {claude_code_tools-1.29.1 → claude_code_tools-1.29.2}/node_ui/package.json +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: claude-code-tools
3
- Version: 1.29.1
3
+ Version: 1.29.2
4
4
  Summary: Collection of tools for working with Claude Code
5
5
  License-File: LICENSE
6
6
  Requires-Python: >=3.11
@@ -1,3 +1,3 @@
1
1
  """Claude Code Tools - Collection of utilities for Claude Code."""
2
2
 
3
- __version__ = "1.29.1"
3
+ __version__ = "1.29.2"
@@ -104,6 +104,10 @@ class MattermostConfig:
104
104
  allowed_user_ids: list[str] = field(default_factory=list)
105
105
  # false only for a self-signed test server.
106
106
  verify_tls: bool = True
107
+ # Handle answered when a teammate just @-mentions the bot without naming
108
+ # a handle ("@bot how does X work?"). Empty = use the only shared session
109
+ # when exactly one is live, else ask which.
110
+ default_handle: str = ""
107
111
 
108
112
 
109
113
  @dataclass
@@ -385,6 +389,10 @@ respond_to_dms = false
385
389
  # channel_ids = [] # 26-char channel ids
386
390
  # allowed_user_ids = [] # empty = anyone in the watched channels
387
391
  # verify_tls = true
392
+ # Handle answered when someone just @-mentions the bot without naming one
393
+ # ("@bot how does X work?"). Unset = the only shared session when exactly
394
+ # one is live, else the bot asks which.
395
+ # default_handle = ""
388
396
 
389
397
  [claude]
390
398
  binary = "claude"
@@ -141,11 +141,23 @@ def parse_posted(event: dict[str, Any]) -> Optional[MMPost]:
141
141
  )
142
142
 
143
143
 
144
+ def resolve_default_handle(cfg: TunnelConfig, live: Sequence[str]) -> str:
145
+ """Handle to answer for a bare ``@bot`` mention.
146
+
147
+ The configured ``default_handle`` wins. With none set, a single shared
148
+ session is unambiguous; with several, the bot asks which one.
149
+ """
150
+ if cfg.mattermost.default_handle:
151
+ return cfg.mattermost.default_handle
152
+ return live[0] if len(live) == 1 else ""
153
+
154
+
144
155
  @dataclass
145
156
  class Route:
146
157
  """What the bot should do with one post."""
147
158
 
148
- action: str # ignore | list | open | unknown_handle | followup | close
159
+ # ignore | list | open | unknown_handle | which_handle | followup | close
160
+ action: str
149
161
  thread_key: str = ""
150
162
  root_id: str = ""
151
163
  handle: str = ""
@@ -161,6 +173,7 @@ def route_post(
161
173
  allowed_user_ids: Sequence[str],
162
174
  is_bound: Callable[[str], bool],
163
175
  handle_live: Callable[[str], bool],
176
+ default_handle: str = "",
164
177
  ) -> Route:
165
178
  """Decide what to do with a post (pure; no I/O).
166
179
 
@@ -172,6 +185,8 @@ def route_post(
172
185
  allowed_user_ids: If non-empty, only these users are answered.
173
186
  is_bound: Whether a thread key is bound to a session.
174
187
  handle_live: Whether a handle is currently shared.
188
+ default_handle: Handle to use when the bot is @-mentioned without
189
+ one (see ``resolve_default_handle``); "" = ask which.
175
190
 
176
191
  Returns:
177
192
  The routing decision.
@@ -194,7 +209,18 @@ def route_post(
194
209
  if post.root_id:
195
210
  key = f"mm:{post.root_id}"
196
211
  if not is_bound(key):
197
- return ignore
212
+ # Not our thread yet. Teammates still address the bot inside one
213
+ # (e.g. answering its "which session?" question), so an explicit
214
+ # handle or @bot mention opens a binding on that root; anything
215
+ # else stays ignored.
216
+ return _open_route(
217
+ text,
218
+ post_id=post.root_id,
219
+ bot_username=bot_username,
220
+ handle_live=handle_live,
221
+ default_handle=default_handle,
222
+ complain_unknown=False,
223
+ )
198
224
  # A reply that opens with @someone-else (or @all/@channel/@here) is
199
225
  # teammates talking among themselves. A leading @bot is stripped.
200
226
  who = leading_mention(text)
@@ -208,21 +234,74 @@ def route_post(
208
234
  return Route("close", thread_key=key, root_id=post.root_id)
209
235
  return Route("followup", thread_key=key, root_id=post.root_id, text=text)
210
236
 
237
+ return _open_route(
238
+ text,
239
+ post_id=post.id,
240
+ bot_username=bot_username,
241
+ handle_live=handle_live,
242
+ default_handle=default_handle,
243
+ )
244
+
245
+
246
+ def _open_route(
247
+ text: str,
248
+ *,
249
+ post_id: str,
250
+ bot_username: str,
251
+ handle_live: Callable[[str], bool],
252
+ default_handle: str,
253
+ complain_unknown: bool = True,
254
+ ) -> Route:
255
+ """Route a message that may open a thread on ``post_id`` (pure).
256
+
257
+ Used for a root post and for a message inside a thread the bot has not
258
+ bound yet: both are "someone addressing the bot in the channel".
259
+ ``complain_unknown`` is off in the latter case, so ordinary chatter
260
+ there stays unanswered.
261
+ """
262
+ ignore = Route("ignore")
263
+ # An @bot mention is how a teammate naturally addresses the bot. Strip
264
+ # it; whatever follows is the question (a handle may still lead it).
265
+ mentioned = False
266
+ who = leading_mention(text)
267
+ if who is not None:
268
+ if who in BROADCAST_MENTIONS or who != bot_username.lower():
269
+ return ignore # teammates talking to each other
270
+ mentioned = True
271
+ text = _MENTION_RE.sub("", text.lstrip(), count=1).strip()
272
+
211
273
  if is_list_command(text):
212
- return Route("list", root_id=post.id)
213
- token, _, remainder = text.partition(" ")
214
- handle = token.strip().lower()
274
+ return Route("list", root_id=post_id)
275
+ # Split on ANY whitespace: a handle followed by a newline is still a
276
+ # handle, and must not fall through to the default.
277
+ parts = text.split(None, 1)
278
+ handle = parts[0].strip().lower() if parts else ""
279
+ remainder = parts[1].strip() if len(parts) > 1 else ""
215
280
  if handle_live(handle):
216
281
  return Route(
217
282
  "open",
218
- thread_key=f"mm:{post.id}",
219
- root_id=post.id,
283
+ thread_key=f"mm:{post_id}",
284
+ root_id=post_id,
220
285
  handle=handle,
221
- text=remainder.strip(),
286
+ text=remainder,
222
287
  )
223
- # Only complain if it clearly looks like a handle attempt.
224
- if HANDLE_RE.match(handle) and not remainder:
225
- return Route("unknown_handle", root_id=post.id, handle=handle)
288
+ if mentioned:
289
+ # Addressed to the bot but no handle named: answer the default one,
290
+ # but only while it is actually shared.
291
+ if not default_handle or not handle_live(default_handle):
292
+ return Route("which_handle", root_id=post_id)
293
+ return Route(
294
+ "open",
295
+ thread_key=f"mm:{post_id}",
296
+ root_id=post_id,
297
+ handle=default_handle,
298
+ text=text,
299
+ )
300
+ # Only complain if it clearly looks like a handle attempt — and never
301
+ # inside someone else's thread, where a one-word reply ("thanks") would
302
+ # otherwise draw a "no live session" answer.
303
+ if complain_unknown and HANDLE_RE.match(handle) and not remainder:
304
+ return Route("unknown_handle", root_id=post_id, handle=handle)
226
305
  return ignore
227
306
 
228
307
 
@@ -553,6 +632,9 @@ class _Router:
553
632
  allowed_user_ids=self.cfg.mattermost.allowed_user_ids,
554
633
  is_bound=lambda key: relay.store.get(key) is not None,
555
634
  handle_live=lambda h: self.registry.get(h) is not None,
635
+ default_handle=resolve_default_handle(
636
+ self.cfg, [r.handle for r in self.registry.active()]
637
+ ),
556
638
  )
557
639
  if route.action == "ignore":
558
640
  return
@@ -560,6 +642,13 @@ class _Router:
560
642
  sender = post.sender or "A teammate"
561
643
  if route.action == "list":
562
644
  await dest.send(relay.handles_text())
645
+ elif route.action == "which_handle":
646
+ live = self.registry.active()
647
+ example = live[0].handle if live else "handle"
648
+ await dest.send(
649
+ "Which session should I ask? Put its handle first, e.g. "
650
+ f"`{example} your question`.\n\n" + relay.handles_text()
651
+ )
563
652
  elif route.action == "unknown_handle":
564
653
  await dest.send(
565
654
  f"No live session for handle `{route.handle}`. "
@@ -236,6 +236,14 @@ Daemon + core — `claude_code_tools/agent_tunnel/`:
236
236
  Mattermost routes the same way, with Mattermost's thread model: a root post
237
237
  `\<handle\> [question]` in a watched channel (26-char `channel_ids`) opens a
238
238
  thread keyed `mm:<root post id>`, and the bot answers as replies under it.
239
+ A root post that simply **@-mentions the bot** works too — the mention is
240
+ stripped and what follows is the question, answered against
241
+ `[mattermost] default_handle` (unset: the only shared session when exactly
242
+ one is live, else the bot replies asking which handle to use). A handle may
243
+ still lead the question after the mention.
244
+ A message inside a thread the bot has NOT bound is routed the same way as a
245
+ root post (so a teammate can answer the bot's "which session?" question
246
+ in place); anything else there is left alone.
239
247
  Replies in a bound thread are follow-ups; a reply opening with
240
248
  `@someone-else`, `@all`, `@channel` or `@here` is side-chat and ignored, and
241
249
  a leading `@bot` is stripped. `!list`/`!handles` and `!done`/`!close`/`!end`
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "claude-code-tools"
3
- version = "1.29.1"
3
+ version = "1.29.2"
4
4
  description = "Collection of tools for working with Claude Code"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -113,7 +113,7 @@ markers = [
113
113
 
114
114
  [tool.commitizen]
115
115
  name = "cz_conventional_commits"
116
- version = "1.29.1"
116
+ version = "1.29.2"
117
117
  tag_format = "v$version"
118
118
  version_files = [
119
119
  "pyproject.toml:version",