claude-task-master 0.1.3__py3-none-any.whl → 0.1.5__py3-none-any.whl

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 (31) hide show
  1. claude_task_master/__init__.py +1 -1
  2. claude_task_master/api/models.py +309 -0
  3. claude_task_master/api/routes.py +229 -0
  4. claude_task_master/api/routes_repo.py +317 -0
  5. claude_task_master/bin/claudetm +1 -1
  6. claude_task_master/cli.py +3 -1
  7. claude_task_master/cli_commands/mailbox.py +295 -0
  8. claude_task_master/cli_commands/workflow.py +37 -0
  9. claude_task_master/core/__init__.py +5 -0
  10. claude_task_master/core/agent_phases.py +1 -1
  11. claude_task_master/core/orchestrator.py +432 -9
  12. claude_task_master/core/parallel.py +4 -4
  13. claude_task_master/core/plan_updater.py +199 -0
  14. claude_task_master/core/pr_context.py +179 -64
  15. claude_task_master/core/prompts.py +4 -0
  16. claude_task_master/core/prompts_plan_update.py +148 -0
  17. claude_task_master/core/state.py +5 -1
  18. claude_task_master/core/workflow_stages.py +229 -22
  19. claude_task_master/github/client_pr.py +86 -20
  20. claude_task_master/mailbox/__init__.py +23 -0
  21. claude_task_master/mailbox/merger.py +163 -0
  22. claude_task_master/mailbox/models.py +95 -0
  23. claude_task_master/mailbox/storage.py +209 -0
  24. claude_task_master/mcp/server.py +183 -0
  25. claude_task_master/mcp/tools.py +921 -0
  26. claude_task_master/webhooks/events.py +356 -2
  27. {claude_task_master-0.1.3.dist-info → claude_task_master-0.1.5.dist-info}/METADATA +223 -4
  28. {claude_task_master-0.1.3.dist-info → claude_task_master-0.1.5.dist-info}/RECORD +31 -23
  29. {claude_task_master-0.1.3.dist-info → claude_task_master-0.1.5.dist-info}/WHEEL +1 -1
  30. {claude_task_master-0.1.3.dist-info → claude_task_master-0.1.5.dist-info}/entry_points.txt +0 -0
  31. {claude_task_master-0.1.3.dist-info → claude_task_master-0.1.5.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,317 @@
1
+ """Repository setup REST API routes for Claude Task Master.
2
+
3
+ This module provides REST API endpoints for repository setup operations:
4
+ - POST /repo/clone: Clone a git repository to the workspace
5
+ - POST /repo/setup: Set up a cloned repository for development
6
+ - POST /repo/plan: Create a plan for a repository (read-only, no work)
7
+
8
+ These endpoints support the AI developer workflow where repositories are
9
+ cloned, set up for development, and then work is planned/executed.
10
+
11
+ Usage:
12
+ from claude_task_master.api.routes_repo import create_repo_router
13
+
14
+ router = create_repo_router()
15
+ app.include_router(router, prefix="/repo")
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ import logging
21
+ from typing import TYPE_CHECKING
22
+
23
+ from claude_task_master.api.models import (
24
+ CloneRepoRequest,
25
+ CloneRepoResponse,
26
+ ErrorResponse,
27
+ PlanRepoRequest,
28
+ PlanRepoResponse,
29
+ SetupRepoRequest,
30
+ SetupRepoResponse,
31
+ )
32
+ from claude_task_master.mcp.tools import (
33
+ clone_repo,
34
+ plan_repo,
35
+ setup_repo,
36
+ )
37
+
38
+ if TYPE_CHECKING:
39
+ from fastapi import APIRouter
40
+ from fastapi.responses import JSONResponse
41
+
42
+ # Import FastAPI - using try/except for graceful degradation
43
+ try:
44
+ from fastapi import APIRouter
45
+ from fastapi.responses import JSONResponse
46
+
47
+ FASTAPI_AVAILABLE = True
48
+ except ImportError:
49
+ FASTAPI_AVAILABLE = False
50
+
51
+ logger = logging.getLogger(__name__)
52
+
53
+
54
+ def create_repo_router() -> APIRouter:
55
+ """Create router for repository setup endpoints.
56
+
57
+ These endpoints support the AI developer workflow for cloning,
58
+ setting up, and planning work on repositories.
59
+
60
+ Returns:
61
+ APIRouter configured with repo setup endpoints.
62
+
63
+ Raises:
64
+ ImportError: If FastAPI is not installed.
65
+ """
66
+ if not FASTAPI_AVAILABLE:
67
+ raise ImportError(
68
+ "FastAPI not installed. Install with: pip install claude-task-master[api]"
69
+ )
70
+
71
+ router = APIRouter(tags=["Repository Setup"])
72
+
73
+ @router.post(
74
+ "/clone",
75
+ response_model=CloneRepoResponse,
76
+ responses={
77
+ 400: {"model": ErrorResponse, "description": "Invalid request or clone failed"},
78
+ 500: {"model": ErrorResponse, "description": "Internal server error"},
79
+ },
80
+ summary="Clone Repository",
81
+ description=(
82
+ "Clone a git repository to the workspace. "
83
+ "Default target is ~/workspace/claude-task-master/{repo-name}."
84
+ ),
85
+ )
86
+ async def post_clone_repo(
87
+ clone_request: CloneRepoRequest,
88
+ ) -> CloneRepoResponse | JSONResponse:
89
+ """Clone a git repository.
90
+
91
+ Clones the specified repository to the workspace directory.
92
+ If no target directory is specified, clones to
93
+ ~/workspace/claude-task-master/{repo-name}.
94
+
95
+ Args:
96
+ clone_request: Clone request with URL, optional target_dir, and branch.
97
+
98
+ Returns:
99
+ CloneRepoResponse with clone result including target directory path.
100
+
101
+ Raises:
102
+ 400: If the URL is invalid or clone fails.
103
+ 500: If an unexpected error occurs.
104
+ """
105
+ try:
106
+ # Use the MCP tool implementation for consistency
107
+ result = clone_repo(
108
+ url=clone_request.url,
109
+ target_dir=clone_request.target_dir,
110
+ branch=clone_request.branch,
111
+ )
112
+
113
+ if not result.get("success", False):
114
+ return JSONResponse(
115
+ status_code=400,
116
+ content=ErrorResponse(
117
+ error="clone_failed",
118
+ message=result.get("message", "Clone failed"),
119
+ detail=result.get("error"),
120
+ suggestion="Check the repository URL and your network connection",
121
+ ).model_dump(),
122
+ )
123
+
124
+ return CloneRepoResponse(
125
+ success=True,
126
+ message=result.get("message", "Repository cloned successfully"),
127
+ repo_url=result.get("repo_url"),
128
+ target_dir=result.get("target_dir"),
129
+ branch=result.get("branch"),
130
+ )
131
+
132
+ except Exception as e:
133
+ logger.exception("Error cloning repository")
134
+ return JSONResponse(
135
+ status_code=500,
136
+ content=ErrorResponse(
137
+ error="internal_error",
138
+ message="Failed to clone repository",
139
+ detail=str(e),
140
+ ).model_dump(),
141
+ )
142
+
143
+ @router.post(
144
+ "/setup",
145
+ response_model=SetupRepoResponse,
146
+ responses={
147
+ 400: {"model": ErrorResponse, "description": "Invalid request or setup failed"},
148
+ 404: {"model": ErrorResponse, "description": "Directory not found"},
149
+ 500: {"model": ErrorResponse, "description": "Internal server error"},
150
+ },
151
+ summary="Setup Repository",
152
+ description=(
153
+ "Set up a cloned repository for development. "
154
+ "Detects project type and installs dependencies, creates venv, runs setup scripts."
155
+ ),
156
+ )
157
+ async def post_setup_repo(
158
+ setup_request: SetupRepoRequest,
159
+ ) -> SetupRepoResponse | JSONResponse:
160
+ """Set up a cloned repository for development.
161
+
162
+ Detects the project type and performs appropriate setup:
163
+ - Creates virtual environment (for Python projects)
164
+ - Installs dependencies (pip, npm, pnpm, yarn, bun)
165
+ - Runs setup scripts (setup-hooks.sh, setup.sh, etc.)
166
+
167
+ Args:
168
+ setup_request: Setup request with work_dir path.
169
+
170
+ Returns:
171
+ SetupRepoResponse with setup result including steps completed.
172
+
173
+ Raises:
174
+ 400: If setup fails.
175
+ 404: If the work directory doesn't exist.
176
+ 500: If an unexpected error occurs.
177
+ """
178
+ try:
179
+ # Use the MCP tool implementation for consistency
180
+ result = setup_repo(work_dir=setup_request.work_dir)
181
+
182
+ if not result.get("success", False):
183
+ # Determine appropriate error code
184
+ error_msg = result.get("error", "")
185
+ if "not found" in error_msg.lower() or "does not exist" in error_msg.lower():
186
+ return JSONResponse(
187
+ status_code=404,
188
+ content=ErrorResponse(
189
+ error="not_found",
190
+ message=result.get("message", "Directory not found"),
191
+ detail=result.get("error"),
192
+ suggestion="Ensure the work directory exists and is accessible",
193
+ ).model_dump(),
194
+ )
195
+
196
+ return JSONResponse(
197
+ status_code=400,
198
+ content=ErrorResponse(
199
+ error="setup_failed",
200
+ message=result.get("message", "Setup failed"),
201
+ detail=result.get("error"),
202
+ suggestion="Check the project structure and dependencies",
203
+ ).model_dump(),
204
+ )
205
+
206
+ return SetupRepoResponse(
207
+ success=True,
208
+ message=result.get("message", "Repository setup completed"),
209
+ work_dir=result.get("work_dir"),
210
+ steps_completed=result.get("steps_completed", []),
211
+ venv_path=result.get("venv_path"),
212
+ dependencies_installed=result.get("dependencies_installed", False),
213
+ setup_scripts_run=result.get("setup_scripts_run", []),
214
+ )
215
+
216
+ except Exception as e:
217
+ logger.exception("Error setting up repository")
218
+ return JSONResponse(
219
+ status_code=500,
220
+ content=ErrorResponse(
221
+ error="internal_error",
222
+ message="Failed to set up repository",
223
+ detail=str(e),
224
+ ).model_dump(),
225
+ )
226
+
227
+ @router.post(
228
+ "/plan",
229
+ response_model=PlanRepoResponse,
230
+ responses={
231
+ 400: {"model": ErrorResponse, "description": "Invalid request or planning failed"},
232
+ 404: {"model": ErrorResponse, "description": "Directory not found"},
233
+ 500: {"model": ErrorResponse, "description": "Internal server error"},
234
+ },
235
+ summary="Plan Repository Work",
236
+ description=(
237
+ "Create a plan for a repository without executing any work. "
238
+ "Uses read-only tools to analyze the codebase and generate a task list."
239
+ ),
240
+ )
241
+ async def post_plan_repo(
242
+ plan_request: PlanRepoRequest,
243
+ ) -> PlanRepoResponse | JSONResponse:
244
+ """Create a plan for a repository.
245
+
246
+ Uses read-only tools (Read, Glob, Grep) to analyze the codebase
247
+ and output a structured plan with tasks and success criteria.
248
+ No changes are made to the repository.
249
+
250
+ Use this after cloning and setting up a repo to plan work before
251
+ execution, or to get a plan for a new goal in an existing repository.
252
+
253
+ Args:
254
+ plan_request: Plan request with work_dir, goal, and optional model.
255
+
256
+ Returns:
257
+ PlanRepoResponse with plan, criteria, and run_id.
258
+
259
+ Raises:
260
+ 400: If planning fails or goal is invalid.
261
+ 404: If the work directory doesn't exist.
262
+ 500: If an unexpected error occurs.
263
+ """
264
+ try:
265
+ # Use the MCP tool implementation for consistency
266
+ result = plan_repo(
267
+ work_dir=plan_request.work_dir,
268
+ goal=plan_request.goal,
269
+ model=plan_request.model,
270
+ )
271
+
272
+ if not result.get("success", False):
273
+ # Determine appropriate error code
274
+ error_msg = result.get("error", "")
275
+ if "not found" in error_msg.lower() or "does not exist" in error_msg.lower():
276
+ return JSONResponse(
277
+ status_code=404,
278
+ content=ErrorResponse(
279
+ error="not_found",
280
+ message=result.get("message", "Directory not found"),
281
+ detail=result.get("error"),
282
+ suggestion="Ensure the work directory exists and is accessible",
283
+ ).model_dump(),
284
+ )
285
+
286
+ return JSONResponse(
287
+ status_code=400,
288
+ content=ErrorResponse(
289
+ error="planning_failed",
290
+ message=result.get("message", "Planning failed"),
291
+ detail=result.get("error"),
292
+ suggestion="Check the goal description and repository structure",
293
+ ).model_dump(),
294
+ )
295
+
296
+ return PlanRepoResponse(
297
+ success=True,
298
+ message=result.get("message", "Plan created successfully"),
299
+ work_dir=result.get("work_dir"),
300
+ goal=result.get("goal"),
301
+ plan=result.get("plan"),
302
+ criteria=result.get("criteria"),
303
+ run_id=result.get("run_id"),
304
+ )
305
+
306
+ except Exception as e:
307
+ logger.exception("Error planning repository work")
308
+ return JSONResponse(
309
+ status_code=500,
310
+ content=ErrorResponse(
311
+ error="internal_error",
312
+ message="Failed to create plan",
313
+ detail=str(e),
314
+ ).model_dump(),
315
+ )
316
+
317
+ return router
@@ -33,7 +33,7 @@ set -euo pipefail
33
33
 
34
34
  # Script version - synchronized with Python package version
35
35
  # This should be kept in sync using scripts/sync_version.py
36
- SCRIPT_VERSION="0.1.2"
36
+ SCRIPT_VERSION="0.1.5"
37
37
 
38
38
  # Configuration file location
39
39
  CONFIG_DIR=".claude-task-master"
claude_task_master/cli.py CHANGED
@@ -11,6 +11,7 @@ from .cli_commands.control import register_control_commands
11
11
  from .cli_commands.fix_pr import register_fix_pr_command
12
12
  from .cli_commands.github import register_github_commands
13
13
  from .cli_commands.info import register_info_commands
14
+ from .cli_commands.mailbox import register_mailbox_commands
14
15
  from .cli_commands.workflow import register_workflow_commands
15
16
  from .core.state import StateManager
16
17
  from .utils.debug_claude_md import debug_claude_md_detection
@@ -63,12 +64,13 @@ def main(
63
64
 
64
65
 
65
66
  # Register commands from submodules
66
- register_workflow_commands(app) # start, resume
67
+ register_workflow_commands(app) # start, resume (resume accepts optional message for plan updates)
67
68
  register_info_commands(app) # status, plan, logs, context, progress
68
69
  register_github_commands(app) # ci-status, ci-logs, pr-comments, pr-status
69
70
  register_config_commands(app) # config init, config show, config path
70
71
  register_control_commands(app) # pause, stop, config-update
71
72
  register_fix_pr_command(app) # fix-pr
73
+ register_mailbox_commands(app) # mailbox, mailbox send, mailbox clear
72
74
 
73
75
 
74
76
  @app.command()
@@ -0,0 +1,295 @@
1
+ """Mailbox CLI commands for Claude Task Master.
2
+
3
+ This module provides CLI commands for interacting with the mailbox:
4
+ - mailbox (status) - show mailbox status
5
+ - mailbox send - send a message to the mailbox
6
+ - mailbox clear - clear all messages
7
+ """
8
+
9
+ from typing import Annotated
10
+
11
+ import typer
12
+ from rich.console import Console
13
+ from rich.table import Table
14
+
15
+ from ..core.state import StateManager
16
+ from ..mailbox.models import Priority
17
+ from ..mailbox.storage import MailboxStorage
18
+
19
+ console = Console()
20
+
21
+
22
+ def get_mailbox_storage() -> MailboxStorage:
23
+ """Get mailbox storage instance using state manager's directory.
24
+
25
+ Returns:
26
+ MailboxStorage instance configured for the current project.
27
+ """
28
+ state_manager = StateManager()
29
+ return MailboxStorage(state_dir=state_manager.state_dir)
30
+
31
+
32
+ def mailbox_status() -> None:
33
+ """Show mailbox status.
34
+
35
+ Displays the number of pending messages and their previews.
36
+
37
+ Examples:
38
+ claudetm mailbox
39
+ """
40
+ state_manager = StateManager()
41
+
42
+ if not state_manager.exists():
43
+ console.print("[yellow]No active task found.[/yellow]")
44
+ console.print("[dim]Start a task first with 'claudetm start'.[/dim]")
45
+ raise typer.Exit(1)
46
+
47
+ try:
48
+ mailbox = get_mailbox_storage()
49
+ status = mailbox.get_status()
50
+
51
+ console.print("\n[bold blue]Mailbox Status[/bold blue]\n")
52
+
53
+ # Show summary
54
+ count = status["count"]
55
+ if count == 0:
56
+ console.print("[dim]No pending messages.[/dim]")
57
+ else:
58
+ console.print(f"[cyan]Pending messages:[/cyan] {count}")
59
+
60
+ # Show last checked
61
+ if status["last_checked"]:
62
+ console.print(f"[cyan]Last checked:[/cyan] {status['last_checked']}")
63
+
64
+ # Show total received
65
+ console.print(f"[cyan]Total received:[/cyan] {status['total_messages_received']}")
66
+
67
+ # Show message previews if any
68
+ if status["previews"]:
69
+ console.print()
70
+ table = Table(title="Messages")
71
+ table.add_column("Priority", style="cyan", width=8)
72
+ table.add_column("Sender", style="green", width=15)
73
+ table.add_column("Content", style="white")
74
+ table.add_column("Time", style="dim", width=20)
75
+
76
+ priority_names = {0: "LOW", 1: "NORMAL", 2: "HIGH", 3: "URGENT"}
77
+ priority_styles = {0: "dim", 1: "white", 2: "yellow", 3: "red bold"}
78
+
79
+ for preview in status["previews"]:
80
+ prio = preview["priority"]
81
+ prio_name = priority_names.get(prio, str(prio))
82
+ prio_style = priority_styles.get(prio, "white")
83
+
84
+ table.add_row(
85
+ f"[{prio_style}]{prio_name}[/{prio_style}]",
86
+ preview["sender"],
87
+ preview["content_preview"],
88
+ preview["timestamp"][:19].replace("T", " "), # Truncate and format datetime
89
+ )
90
+
91
+ console.print(table)
92
+
93
+ raise typer.Exit(0)
94
+
95
+ except typer.Exit:
96
+ raise
97
+ except Exception as e:
98
+ console.print(f"[red]Error: {e}[/red]")
99
+ raise typer.Exit(1) from None
100
+
101
+
102
+ def mailbox_send(
103
+ message: Annotated[str, typer.Argument(help="Message content to send")],
104
+ sender: Annotated[
105
+ str,
106
+ typer.Option("--sender", "-s", help="Sender identifier"),
107
+ ] = "cli",
108
+ priority: Annotated[
109
+ int,
110
+ typer.Option("--priority", "-p", help="Priority level (0=low, 1=normal, 2=high, 3=urgent)"),
111
+ ] = 1,
112
+ ) -> None:
113
+ """Send a message to the mailbox.
114
+
115
+ Adds a new message that will be processed after the current task completes.
116
+ The orchestrator checks the mailbox after each task and updates the plan
117
+ if messages are present.
118
+
119
+ Examples:
120
+ claudetm mailbox send "Please also update the README"
121
+ claudetm mailbox send "Fix the auth bug first" --priority 3
122
+ claudetm mailbox send "Low priority cleanup" -p 0 -s "supervisor"
123
+ """
124
+ state_manager = StateManager()
125
+
126
+ if not state_manager.exists():
127
+ console.print("[yellow]No active task found.[/yellow]")
128
+ console.print("[dim]Start a task first with 'claudetm start'.[/dim]")
129
+ raise typer.Exit(1)
130
+
131
+ # Validate priority
132
+ if priority < 0 or priority > 3:
133
+ console.print("[red]Error: Priority must be between 0 and 3.[/red]")
134
+ console.print("[dim]0=low, 1=normal, 2=high, 3=urgent[/dim]")
135
+ raise typer.Exit(1)
136
+
137
+ try:
138
+ mailbox = get_mailbox_storage()
139
+ message_id = mailbox.add_message(
140
+ content=message,
141
+ sender=sender,
142
+ priority=Priority(priority),
143
+ )
144
+
145
+ priority_names = {0: "LOW", 1: "NORMAL", 2: "HIGH", 3: "URGENT"}
146
+ console.print("[green]Message sent to mailbox.[/green]")
147
+ console.print(f"[dim]ID: {message_id}[/dim]")
148
+ console.print(f"[dim]Priority: {priority_names.get(priority, str(priority))}[/dim]")
149
+ console.print(f"[dim]Sender: {sender}[/dim]")
150
+ console.print()
151
+ console.print(
152
+ "[dim]The orchestrator will process this message after the current task.[/dim]"
153
+ )
154
+
155
+ raise typer.Exit(0)
156
+
157
+ except typer.Exit:
158
+ raise
159
+ except Exception as e:
160
+ console.print(f"[red]Error: {e}[/red]")
161
+ raise typer.Exit(1) from None
162
+
163
+
164
+ def mailbox_clear(
165
+ force: Annotated[
166
+ bool,
167
+ typer.Option("--force", "-f", help="Skip confirmation"),
168
+ ] = False,
169
+ ) -> None:
170
+ """Clear all messages from the mailbox.
171
+
172
+ Removes all pending messages. This is useful to cancel pending plan updates
173
+ or start fresh.
174
+
175
+ Examples:
176
+ claudetm mailbox clear
177
+ claudetm mailbox clear -f
178
+ """
179
+ state_manager = StateManager()
180
+
181
+ if not state_manager.exists():
182
+ console.print("[yellow]No active task found.[/yellow]")
183
+ console.print("[dim]Start a task first with 'claudetm start'.[/dim]")
184
+ raise typer.Exit(1)
185
+
186
+ try:
187
+ mailbox = get_mailbox_storage()
188
+ count = mailbox.count()
189
+
190
+ if count == 0:
191
+ console.print("[dim]Mailbox is already empty.[/dim]")
192
+ raise typer.Exit(0)
193
+
194
+ # Confirm unless forced
195
+ if not force:
196
+ confirm = typer.confirm(f"Clear {count} message(s) from mailbox?")
197
+ if not confirm:
198
+ console.print("[yellow]Cancelled.[/yellow]")
199
+ raise typer.Exit(0)
200
+
201
+ cleared = mailbox.clear()
202
+ console.print(f"[green]Cleared {cleared} message(s) from mailbox.[/green]")
203
+
204
+ raise typer.Exit(0)
205
+
206
+ except typer.Exit:
207
+ raise
208
+ except Exception as e:
209
+ console.print(f"[red]Error: {e}[/red]")
210
+ raise typer.Exit(1) from None
211
+
212
+
213
+ # Create the mailbox subcommand group
214
+ mailbox_app = typer.Typer(
215
+ name="mailbox",
216
+ help="""Manage the mailbox for inter-instance communication.
217
+
218
+ The mailbox allows external systems (MCP, REST API, other claudetm instances)
219
+ to send messages that will be processed by the orchestrator after each task.
220
+
221
+ Commands:
222
+ (no subcommand) Show mailbox status
223
+ send Send a message to the mailbox
224
+ clear Clear all messages
225
+
226
+ Examples:
227
+ claudetm mailbox # Show status
228
+ claudetm mailbox send "Update the README" # Send a message
229
+ claudetm mailbox send "Urgent fix" -p 3 # High priority message
230
+ claudetm mailbox clear # Clear messages
231
+ """,
232
+ add_completion=False,
233
+ invoke_without_command=True,
234
+ )
235
+
236
+
237
+ @mailbox_app.callback(invoke_without_command=True)
238
+ def mailbox_callback(ctx: typer.Context) -> None:
239
+ """Show mailbox status when called without subcommand."""
240
+ if ctx.invoked_subcommand is None:
241
+ mailbox_status()
242
+
243
+
244
+ @mailbox_app.command("send")
245
+ def mailbox_send_command(
246
+ message: Annotated[str, typer.Argument(help="Message content to send")],
247
+ sender: Annotated[
248
+ str,
249
+ typer.Option("--sender", "-s", help="Sender identifier"),
250
+ ] = "cli",
251
+ priority: Annotated[
252
+ int,
253
+ typer.Option("--priority", "-p", help="Priority level (0=low, 1=normal, 2=high, 3=urgent)"),
254
+ ] = 1,
255
+ ) -> None:
256
+ """Send a message to the mailbox.
257
+
258
+ Adds a new message that will be processed after the current task completes.
259
+ The orchestrator checks the mailbox after each task and updates the plan
260
+ if messages are present.
261
+
262
+ Examples:
263
+ claudetm mailbox send "Please also update the README"
264
+ claudetm mailbox send "Fix the auth bug first" --priority 3
265
+ claudetm mailbox send "Low priority cleanup" -p 0 -s "supervisor"
266
+ """
267
+ mailbox_send(message, sender, priority)
268
+
269
+
270
+ @mailbox_app.command("clear")
271
+ def mailbox_clear_command(
272
+ force: Annotated[
273
+ bool,
274
+ typer.Option("--force", "-f", help="Skip confirmation"),
275
+ ] = False,
276
+ ) -> None:
277
+ """Clear all messages from the mailbox.
278
+
279
+ Removes all pending messages. This is useful to cancel pending plan updates
280
+ or start fresh.
281
+
282
+ Examples:
283
+ claudetm mailbox clear
284
+ claudetm mailbox clear -f
285
+ """
286
+ mailbox_clear(force)
287
+
288
+
289
+ def register_mailbox_commands(app: typer.Typer) -> None:
290
+ """Register mailbox commands with the main Typer app.
291
+
292
+ Args:
293
+ app: The main Typer application.
294
+ """
295
+ app.add_typer(mailbox_app, name="mailbox")