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.
- claude_task_master/__init__.py +1 -1
- claude_task_master/api/models.py +309 -0
- claude_task_master/api/routes.py +229 -0
- claude_task_master/api/routes_repo.py +317 -0
- claude_task_master/bin/claudetm +1 -1
- claude_task_master/cli.py +3 -1
- claude_task_master/cli_commands/mailbox.py +295 -0
- claude_task_master/cli_commands/workflow.py +37 -0
- claude_task_master/core/__init__.py +5 -0
- claude_task_master/core/agent_phases.py +1 -1
- claude_task_master/core/orchestrator.py +432 -9
- claude_task_master/core/parallel.py +4 -4
- claude_task_master/core/plan_updater.py +199 -0
- claude_task_master/core/pr_context.py +179 -64
- claude_task_master/core/prompts.py +4 -0
- claude_task_master/core/prompts_plan_update.py +148 -0
- claude_task_master/core/state.py +5 -1
- claude_task_master/core/workflow_stages.py +229 -22
- claude_task_master/github/client_pr.py +86 -20
- claude_task_master/mailbox/__init__.py +23 -0
- claude_task_master/mailbox/merger.py +163 -0
- claude_task_master/mailbox/models.py +95 -0
- claude_task_master/mailbox/storage.py +209 -0
- claude_task_master/mcp/server.py +183 -0
- claude_task_master/mcp/tools.py +921 -0
- claude_task_master/webhooks/events.py +356 -2
- {claude_task_master-0.1.3.dist-info → claude_task_master-0.1.5.dist-info}/METADATA +223 -4
- {claude_task_master-0.1.3.dist-info → claude_task_master-0.1.5.dist-info}/RECORD +31 -23
- {claude_task_master-0.1.3.dist-info → claude_task_master-0.1.5.dist-info}/WHEEL +1 -1
- {claude_task_master-0.1.3.dist-info → claude_task_master-0.1.5.dist-info}/entry_points.txt +0 -0
- {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
|
claude_task_master/bin/claudetm
CHANGED
|
@@ -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.
|
|
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")
|