claude-task-master 0.1.4__py3-none-any.whl → 0.1.6__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/config.py +3 -3
- 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 +176 -62
- claude_task_master/core/prompts.py +4 -0
- claude_task_master/core/prompts_plan_update.py +148 -0
- claude_task_master/core/prompts_planning.py +6 -2
- claude_task_master/core/state.py +5 -1
- claude_task_master/core/task_runner.py +73 -34
- 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.4.dist-info → claude_task_master-0.1.6.dist-info}/METADATA +223 -4
- {claude_task_master-0.1.4.dist-info → claude_task_master-0.1.6.dist-info}/RECORD +34 -26
- {claude_task_master-0.1.4.dist-info → claude_task_master-0.1.6.dist-info}/WHEEL +1 -1
- {claude_task_master-0.1.4.dist-info → claude_task_master-0.1.6.dist-info}/entry_points.txt +0 -0
- {claude_task_master-0.1.4.dist-info → claude_task_master-0.1.6.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
"""Message merger for combining multiple mailbox messages.
|
|
2
|
+
|
|
3
|
+
This module provides the MessageMerger class that consolidates multiple
|
|
4
|
+
messages into a single coherent change request for plan updates.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from datetime import datetime
|
|
10
|
+
from typing import TYPE_CHECKING
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from .models import MailboxMessage
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class MessageMerger:
|
|
17
|
+
"""Merges multiple mailbox messages into a single change request.
|
|
18
|
+
|
|
19
|
+
The merger consolidates messages by:
|
|
20
|
+
1. Sorting by priority (highest first)
|
|
21
|
+
2. Grouping by sender (optional)
|
|
22
|
+
3. Formatting into a structured change request
|
|
23
|
+
|
|
24
|
+
This is deterministic (no AI) to ensure consistent behavior.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def merge(self, messages: list[MailboxMessage]) -> str:
|
|
28
|
+
"""Merge multiple messages into a single change request.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
messages: List of MailboxMessage objects to merge.
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
A formatted string suitable for plan update prompts.
|
|
35
|
+
|
|
36
|
+
Raises:
|
|
37
|
+
ValueError: If messages list is empty.
|
|
38
|
+
"""
|
|
39
|
+
if not messages:
|
|
40
|
+
raise ValueError("Cannot merge empty message list")
|
|
41
|
+
|
|
42
|
+
if len(messages) == 1:
|
|
43
|
+
return self._format_single_message(messages[0])
|
|
44
|
+
|
|
45
|
+
return self._format_multiple_messages(messages)
|
|
46
|
+
|
|
47
|
+
def _format_single_message(self, message: MailboxMessage) -> str:
|
|
48
|
+
"""Format a single message as a change request.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
message: The message to format.
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
Formatted change request string.
|
|
55
|
+
"""
|
|
56
|
+
parts = [message.content]
|
|
57
|
+
|
|
58
|
+
# Add sender attribution if not anonymous
|
|
59
|
+
if message.sender != "anonymous":
|
|
60
|
+
parts.append(f"\n\n---\n*From: {message.sender}*")
|
|
61
|
+
|
|
62
|
+
return "".join(parts)
|
|
63
|
+
|
|
64
|
+
def _format_multiple_messages(self, messages: list[MailboxMessage]) -> str:
|
|
65
|
+
"""Format multiple messages as a consolidated change request.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
messages: List of messages to format (already sorted by priority).
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
Formatted change request with all messages.
|
|
72
|
+
"""
|
|
73
|
+
header = self._build_header(messages)
|
|
74
|
+
body = self._build_body(messages)
|
|
75
|
+
footer = self._build_footer(len(messages))
|
|
76
|
+
|
|
77
|
+
return f"{header}\n\n{body}\n\n{footer}"
|
|
78
|
+
|
|
79
|
+
def _build_header(self, messages: list[MailboxMessage]) -> str:
|
|
80
|
+
"""Build the header section for merged messages.
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
messages: List of messages.
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
Header string with count and timestamp.
|
|
87
|
+
"""
|
|
88
|
+
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
|
89
|
+
return (
|
|
90
|
+
f"**Consolidated Change Requests ({len(messages)} messages)**\n"
|
|
91
|
+
f"*Processed at: {timestamp}*"
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
def _build_body(self, messages: list[MailboxMessage]) -> str:
|
|
95
|
+
"""Build the body section with all message contents.
|
|
96
|
+
|
|
97
|
+
Args:
|
|
98
|
+
messages: List of messages to include.
|
|
99
|
+
|
|
100
|
+
Returns:
|
|
101
|
+
Formatted body with numbered messages.
|
|
102
|
+
"""
|
|
103
|
+
parts = []
|
|
104
|
+
|
|
105
|
+
for i, msg in enumerate(messages, 1):
|
|
106
|
+
priority_label = self._get_priority_label(msg.priority)
|
|
107
|
+
sender_info = f" (from {msg.sender})" if msg.sender != "anonymous" else ""
|
|
108
|
+
|
|
109
|
+
parts.append(f"### Request {i}{priority_label}{sender_info}\n\n{msg.content}")
|
|
110
|
+
|
|
111
|
+
return "\n\n---\n\n".join(parts)
|
|
112
|
+
|
|
113
|
+
def _get_priority_label(self, priority: int) -> str:
|
|
114
|
+
"""Get a human-readable priority label.
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
priority: Priority value (0-3).
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
Priority label string or empty string for normal priority.
|
|
121
|
+
"""
|
|
122
|
+
labels = {
|
|
123
|
+
0: " [LOW]",
|
|
124
|
+
1: "", # Normal - no label
|
|
125
|
+
2: " [HIGH]",
|
|
126
|
+
3: " [URGENT]",
|
|
127
|
+
}
|
|
128
|
+
return labels.get(priority, "")
|
|
129
|
+
|
|
130
|
+
def _build_footer(self, count: int) -> str:
|
|
131
|
+
"""Build the footer with instructions.
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
count: Number of messages.
|
|
135
|
+
|
|
136
|
+
Returns:
|
|
137
|
+
Footer string with instructions.
|
|
138
|
+
"""
|
|
139
|
+
return (
|
|
140
|
+
f"**Please address ALL {count} change requests above in the plan update.**\n"
|
|
141
|
+
f"Prioritize URGENT requests first, then HIGH, then others.\n"
|
|
142
|
+
f"If requests conflict, prefer higher-priority requests."
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
def merge_to_single_content(self, messages: list[MailboxMessage]) -> str:
|
|
146
|
+
"""Merge messages to just the content strings combined.
|
|
147
|
+
|
|
148
|
+
A simpler alternative to merge() that just concatenates content.
|
|
149
|
+
|
|
150
|
+
Args:
|
|
151
|
+
messages: List of messages.
|
|
152
|
+
|
|
153
|
+
Returns:
|
|
154
|
+
Combined content string.
|
|
155
|
+
"""
|
|
156
|
+
if not messages:
|
|
157
|
+
raise ValueError("Cannot merge empty message list")
|
|
158
|
+
|
|
159
|
+
if len(messages) == 1:
|
|
160
|
+
return messages[0].content
|
|
161
|
+
|
|
162
|
+
contents = [msg.content for msg in messages]
|
|
163
|
+
return "\n\n---\n\n".join(contents)
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"""Pydantic models for the mailbox system.
|
|
2
|
+
|
|
3
|
+
This module defines the data models used by the mailbox:
|
|
4
|
+
- MailboxMessage: A single message with metadata
|
|
5
|
+
- MailboxState: The overall mailbox state for persistence
|
|
6
|
+
- MessagePreview: A shortened version for status display
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from datetime import datetime
|
|
10
|
+
from enum import IntEnum
|
|
11
|
+
from typing import Any
|
|
12
|
+
from uuid import uuid4
|
|
13
|
+
|
|
14
|
+
from pydantic import BaseModel, Field
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class Priority(IntEnum):
|
|
18
|
+
"""Message priority levels.
|
|
19
|
+
|
|
20
|
+
Higher values indicate higher priority.
|
|
21
|
+
Messages with higher priority are processed first.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
LOW = 0
|
|
25
|
+
NORMAL = 1
|
|
26
|
+
HIGH = 2
|
|
27
|
+
URGENT = 3
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class MailboxMessage(BaseModel):
|
|
31
|
+
"""A message in the mailbox.
|
|
32
|
+
|
|
33
|
+
Attributes:
|
|
34
|
+
id: Unique message identifier (auto-generated UUID).
|
|
35
|
+
sender: Identifier of the message sender.
|
|
36
|
+
content: The message content/body.
|
|
37
|
+
priority: Message priority level (higher = more important).
|
|
38
|
+
timestamp: When the message was created.
|
|
39
|
+
metadata: Optional additional metadata.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
id: str = Field(default_factory=lambda: str(uuid4()))
|
|
43
|
+
sender: str = "anonymous"
|
|
44
|
+
content: str
|
|
45
|
+
priority: Priority = Priority.NORMAL
|
|
46
|
+
timestamp: datetime = Field(default_factory=datetime.now)
|
|
47
|
+
metadata: dict[str, Any] = Field(default_factory=dict)
|
|
48
|
+
|
|
49
|
+
def to_preview(self, max_length: int = 100) -> "MessagePreview":
|
|
50
|
+
"""Convert to a preview for status display.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
max_length: Maximum length of the content preview.
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
MessagePreview with truncated content.
|
|
57
|
+
"""
|
|
58
|
+
preview_content = self.content
|
|
59
|
+
if len(preview_content) > max_length:
|
|
60
|
+
preview_content = preview_content[: max_length - 3] + "..."
|
|
61
|
+
|
|
62
|
+
return MessagePreview(
|
|
63
|
+
id=self.id,
|
|
64
|
+
sender=self.sender,
|
|
65
|
+
content_preview=preview_content,
|
|
66
|
+
priority=self.priority,
|
|
67
|
+
timestamp=self.timestamp,
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class MessagePreview(BaseModel):
|
|
72
|
+
"""A shortened preview of a message.
|
|
73
|
+
|
|
74
|
+
Used in status responses to avoid sending full message content.
|
|
75
|
+
"""
|
|
76
|
+
|
|
77
|
+
id: str
|
|
78
|
+
sender: str
|
|
79
|
+
content_preview: str
|
|
80
|
+
priority: Priority
|
|
81
|
+
timestamp: datetime
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class MailboxState(BaseModel):
|
|
85
|
+
"""Persistent state of the mailbox.
|
|
86
|
+
|
|
87
|
+
Attributes:
|
|
88
|
+
messages: List of messages currently in the mailbox.
|
|
89
|
+
last_checked: When the mailbox was last checked/processed.
|
|
90
|
+
total_messages_received: Total count of messages ever received.
|
|
91
|
+
"""
|
|
92
|
+
|
|
93
|
+
messages: list[MailboxMessage] = Field(default_factory=list)
|
|
94
|
+
last_checked: datetime | None = None
|
|
95
|
+
total_messages_received: int = 0
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
"""Mailbox storage for persistent message storage.
|
|
2
|
+
|
|
3
|
+
This module provides the MailboxStorage class that handles:
|
|
4
|
+
- Adding messages to the mailbox
|
|
5
|
+
- Retrieving messages (with optional clearing)
|
|
6
|
+
- Atomic file operations for safe concurrent access
|
|
7
|
+
- Persistence to .claude-task-master/mailbox.json
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import json
|
|
13
|
+
import shutil
|
|
14
|
+
import tempfile
|
|
15
|
+
from datetime import datetime
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
from typing import TYPE_CHECKING
|
|
18
|
+
|
|
19
|
+
from pydantic import ValidationError
|
|
20
|
+
|
|
21
|
+
from .models import MailboxMessage, MailboxState, Priority
|
|
22
|
+
|
|
23
|
+
if TYPE_CHECKING:
|
|
24
|
+
from typing import Any
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class MailboxStorageError(Exception):
|
|
28
|
+
"""Base exception for mailbox storage errors."""
|
|
29
|
+
|
|
30
|
+
pass
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class MailboxStorage:
|
|
34
|
+
"""Manages mailbox message storage.
|
|
35
|
+
|
|
36
|
+
Persists messages to a JSON file with atomic writes for safety.
|
|
37
|
+
Supports concurrent access through file-based locking.
|
|
38
|
+
|
|
39
|
+
Attributes:
|
|
40
|
+
storage_path: Path to the mailbox.json file.
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
def __init__(self, state_dir: Path | None = None):
|
|
44
|
+
"""Initialize mailbox storage.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
state_dir: Directory for state files. Defaults to .claude-task-master.
|
|
48
|
+
"""
|
|
49
|
+
self.state_dir = state_dir or Path(".claude-task-master")
|
|
50
|
+
self.storage_path = self.state_dir / "mailbox.json"
|
|
51
|
+
|
|
52
|
+
def _ensure_dir(self) -> None:
|
|
53
|
+
"""Ensure the state directory exists."""
|
|
54
|
+
self.state_dir.mkdir(parents=True, exist_ok=True)
|
|
55
|
+
|
|
56
|
+
def _load_state(self) -> MailboxState:
|
|
57
|
+
"""Load the current mailbox state from disk.
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
MailboxState with current messages, or empty state if file doesn't exist.
|
|
61
|
+
"""
|
|
62
|
+
if not self.storage_path.exists():
|
|
63
|
+
return MailboxState()
|
|
64
|
+
|
|
65
|
+
try:
|
|
66
|
+
with open(self.storage_path) as f:
|
|
67
|
+
data = json.load(f)
|
|
68
|
+
return MailboxState(**data)
|
|
69
|
+
except (json.JSONDecodeError, ValidationError):
|
|
70
|
+
# Corrupted file - return empty state
|
|
71
|
+
return MailboxState()
|
|
72
|
+
|
|
73
|
+
def _save_state(self, state: MailboxState) -> None:
|
|
74
|
+
"""Save mailbox state atomically.
|
|
75
|
+
|
|
76
|
+
Uses temp file + rename for atomic writes.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
state: The MailboxState to save.
|
|
80
|
+
"""
|
|
81
|
+
self._ensure_dir()
|
|
82
|
+
|
|
83
|
+
# Write to temp file, then rename for atomicity
|
|
84
|
+
fd, temp_path = tempfile.mkstemp(dir=self.state_dir, prefix=".mailbox_", suffix=".json")
|
|
85
|
+
try:
|
|
86
|
+
with open(fd, "w") as f:
|
|
87
|
+
# Use model_dump with mode='json' for proper datetime serialization
|
|
88
|
+
json.dump(state.model_dump(mode="json"), f, indent=2)
|
|
89
|
+
shutil.move(temp_path, self.storage_path)
|
|
90
|
+
except Exception:
|
|
91
|
+
# Clean up temp file on error
|
|
92
|
+
try:
|
|
93
|
+
Path(temp_path).unlink()
|
|
94
|
+
except Exception:
|
|
95
|
+
pass
|
|
96
|
+
raise
|
|
97
|
+
|
|
98
|
+
def add_message(
|
|
99
|
+
self,
|
|
100
|
+
content: str,
|
|
101
|
+
sender: str = "anonymous",
|
|
102
|
+
priority: int | Priority = Priority.NORMAL,
|
|
103
|
+
metadata: dict[str, Any] | None = None,
|
|
104
|
+
) -> str:
|
|
105
|
+
"""Add a message to the mailbox.
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
content: The message content.
|
|
109
|
+
sender: Identifier of the sender (default: "anonymous").
|
|
110
|
+
priority: Message priority (0=low, 1=normal, 2=high, 3=urgent).
|
|
111
|
+
metadata: Optional additional metadata dict.
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
The ID of the created message.
|
|
115
|
+
"""
|
|
116
|
+
# Convert int to Priority enum if needed
|
|
117
|
+
if isinstance(priority, int):
|
|
118
|
+
priority = Priority(priority)
|
|
119
|
+
|
|
120
|
+
message = MailboxMessage(
|
|
121
|
+
content=content,
|
|
122
|
+
sender=sender,
|
|
123
|
+
priority=priority,
|
|
124
|
+
metadata=metadata or {},
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
state = self._load_state()
|
|
128
|
+
state.messages.append(message)
|
|
129
|
+
state.total_messages_received += 1
|
|
130
|
+
self._save_state(state)
|
|
131
|
+
|
|
132
|
+
return message.id
|
|
133
|
+
|
|
134
|
+
def get_messages(self) -> list[MailboxMessage]:
|
|
135
|
+
"""Get all messages without removing them.
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
List of messages sorted by priority (highest first), then timestamp.
|
|
139
|
+
"""
|
|
140
|
+
state = self._load_state()
|
|
141
|
+
# Sort by priority (descending) then timestamp (ascending)
|
|
142
|
+
return sorted(state.messages, key=lambda m: (-m.priority, m.timestamp))
|
|
143
|
+
|
|
144
|
+
def get_and_clear(self) -> list[MailboxMessage]:
|
|
145
|
+
"""Get all messages and remove them atomically.
|
|
146
|
+
|
|
147
|
+
This is the primary method for processing messages - it ensures
|
|
148
|
+
no message is lost or processed twice.
|
|
149
|
+
|
|
150
|
+
Returns:
|
|
151
|
+
List of messages sorted by priority (highest first), then timestamp.
|
|
152
|
+
"""
|
|
153
|
+
state = self._load_state()
|
|
154
|
+
messages = sorted(state.messages, key=lambda m: (-m.priority, m.timestamp))
|
|
155
|
+
|
|
156
|
+
# Clear messages and update last_checked
|
|
157
|
+
state.messages = []
|
|
158
|
+
state.last_checked = datetime.now()
|
|
159
|
+
self._save_state(state)
|
|
160
|
+
|
|
161
|
+
return messages
|
|
162
|
+
|
|
163
|
+
def clear(self) -> int:
|
|
164
|
+
"""Clear all messages from the mailbox.
|
|
165
|
+
|
|
166
|
+
Returns:
|
|
167
|
+
The number of messages that were removed.
|
|
168
|
+
"""
|
|
169
|
+
state = self._load_state()
|
|
170
|
+
count = len(state.messages)
|
|
171
|
+
|
|
172
|
+
state.messages = []
|
|
173
|
+
state.last_checked = datetime.now()
|
|
174
|
+
self._save_state(state)
|
|
175
|
+
|
|
176
|
+
return count
|
|
177
|
+
|
|
178
|
+
def count(self) -> int:
|
|
179
|
+
"""Get the number of messages in the mailbox.
|
|
180
|
+
|
|
181
|
+
Returns:
|
|
182
|
+
Number of pending messages.
|
|
183
|
+
"""
|
|
184
|
+
state = self._load_state()
|
|
185
|
+
return len(state.messages)
|
|
186
|
+
|
|
187
|
+
def get_status(self) -> dict[str, Any]:
|
|
188
|
+
"""Get mailbox status information.
|
|
189
|
+
|
|
190
|
+
Returns:
|
|
191
|
+
Dict with count, previews, last_checked, and total_received.
|
|
192
|
+
"""
|
|
193
|
+
state = self._load_state()
|
|
194
|
+
messages = sorted(state.messages, key=lambda m: (-m.priority, m.timestamp))
|
|
195
|
+
|
|
196
|
+
return {
|
|
197
|
+
"count": len(messages),
|
|
198
|
+
"previews": [m.to_preview().model_dump(mode="json") for m in messages],
|
|
199
|
+
"last_checked": (state.last_checked.isoformat() if state.last_checked else None),
|
|
200
|
+
"total_messages_received": state.total_messages_received,
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
def exists(self) -> bool:
|
|
204
|
+
"""Check if the mailbox storage file exists.
|
|
205
|
+
|
|
206
|
+
Returns:
|
|
207
|
+
True if mailbox.json exists.
|
|
208
|
+
"""
|
|
209
|
+
return self.storage_path.exists()
|
claude_task_master/mcp/server.py
CHANGED
|
@@ -63,6 +63,12 @@ PauseTaskResult = tools.PauseTaskResult
|
|
|
63
63
|
StopTaskResult = tools.StopTaskResult
|
|
64
64
|
ResumeTaskResult = tools.ResumeTaskResult
|
|
65
65
|
UpdateConfigResult = tools.UpdateConfigResult
|
|
66
|
+
SendMessageResult = tools.SendMessageResult
|
|
67
|
+
MailboxStatusResult = tools.MailboxStatusResult
|
|
68
|
+
ClearMailboxResult = tools.ClearMailboxResult
|
|
69
|
+
CloneRepoResult = tools.CloneRepoResult
|
|
70
|
+
SetupRepoResult = tools.SetupRepoResult
|
|
71
|
+
PlanRepoResult = tools.PlanRepoResult
|
|
66
72
|
|
|
67
73
|
|
|
68
74
|
# =============================================================================
|
|
@@ -348,6 +354,183 @@ def create_server(
|
|
|
348
354
|
state_dir=state_dir,
|
|
349
355
|
)
|
|
350
356
|
|
|
357
|
+
# =============================================================================
|
|
358
|
+
# Mailbox Tool Wrappers
|
|
359
|
+
# =============================================================================
|
|
360
|
+
|
|
361
|
+
@mcp.tool()
|
|
362
|
+
def send_message(
|
|
363
|
+
content: str,
|
|
364
|
+
sender: str = "anonymous",
|
|
365
|
+
priority: int = 1,
|
|
366
|
+
state_dir: str | None = None,
|
|
367
|
+
) -> dict[str, Any]:
|
|
368
|
+
"""Send a message to the claudetm mailbox.
|
|
369
|
+
|
|
370
|
+
Messages are processed after the current task completes. Multiple messages
|
|
371
|
+
are merged into a single change request that updates the plan before
|
|
372
|
+
continuing work.
|
|
373
|
+
|
|
374
|
+
Use this to send instructions, feedback, or change requests to a running
|
|
375
|
+
claudetm instance from external systems or other AI agents.
|
|
376
|
+
|
|
377
|
+
Args:
|
|
378
|
+
content: The message content describing the change request.
|
|
379
|
+
sender: Identifier of the sender (default: "anonymous").
|
|
380
|
+
priority: Message priority - 0=low, 1=normal, 2=high, 3=urgent.
|
|
381
|
+
state_dir: Optional custom state directory path.
|
|
382
|
+
|
|
383
|
+
Returns:
|
|
384
|
+
Dictionary containing the message_id on success, or error info.
|
|
385
|
+
|
|
386
|
+
Example:
|
|
387
|
+
send_message("Please also add unit tests for the new feature")
|
|
388
|
+
send_message("URGENT: Fix the security bug first", priority=3)
|
|
389
|
+
"""
|
|
390
|
+
return tools.send_message(work_dir, content, sender, priority, state_dir)
|
|
391
|
+
|
|
392
|
+
@mcp.tool()
|
|
393
|
+
def check_mailbox(state_dir: str | None = None) -> dict[str, Any]:
|
|
394
|
+
"""Check the status of the claudetm mailbox.
|
|
395
|
+
|
|
396
|
+
Returns the number of pending messages and previews of each.
|
|
397
|
+
Use this to see what messages are waiting to be processed.
|
|
398
|
+
|
|
399
|
+
Args:
|
|
400
|
+
state_dir: Optional custom state directory path.
|
|
401
|
+
|
|
402
|
+
Returns:
|
|
403
|
+
Dictionary containing mailbox status with message previews.
|
|
404
|
+
"""
|
|
405
|
+
return tools.check_mailbox(work_dir, state_dir)
|
|
406
|
+
|
|
407
|
+
@mcp.tool()
|
|
408
|
+
def clear_mailbox(state_dir: str | None = None) -> dict[str, Any]:
|
|
409
|
+
"""Clear all messages from the claudetm mailbox.
|
|
410
|
+
|
|
411
|
+
Use this to discard all pending messages without processing them.
|
|
412
|
+
|
|
413
|
+
Args:
|
|
414
|
+
state_dir: Optional custom state directory path.
|
|
415
|
+
|
|
416
|
+
Returns:
|
|
417
|
+
Dictionary indicating success and number of messages cleared.
|
|
418
|
+
"""
|
|
419
|
+
return tools.clear_mailbox(work_dir, state_dir)
|
|
420
|
+
|
|
421
|
+
# =============================================================================
|
|
422
|
+
# Repo Setup Tool Wrappers
|
|
423
|
+
# =============================================================================
|
|
424
|
+
|
|
425
|
+
@mcp.tool()
|
|
426
|
+
def clone_repo(
|
|
427
|
+
url: str,
|
|
428
|
+
target_dir: str | None = None,
|
|
429
|
+
branch: str | None = None,
|
|
430
|
+
) -> dict[str, Any]:
|
|
431
|
+
"""Clone a git repository to the workspace.
|
|
432
|
+
|
|
433
|
+
Clones the repository to ~/workspace/claude-task-master/{project-name}
|
|
434
|
+
by default, or to a custom target directory if specified. This is the
|
|
435
|
+
first step in setting up a new development environment for AI developers.
|
|
436
|
+
|
|
437
|
+
Args:
|
|
438
|
+
url: Git repository URL (HTTPS or SSH format).
|
|
439
|
+
Examples: https://github.com/user/repo.git or git@github.com:user/repo.git
|
|
440
|
+
target_dir: Optional custom target directory path. If not provided,
|
|
441
|
+
defaults to ~/workspace/claude-task-master/{repo-name}.
|
|
442
|
+
branch: Optional branch to checkout after cloning.
|
|
443
|
+
|
|
444
|
+
Returns:
|
|
445
|
+
Dictionary containing:
|
|
446
|
+
- success: Whether clone was successful
|
|
447
|
+
- message: Human-readable result message
|
|
448
|
+
- repo_url: The cloned repository URL
|
|
449
|
+
- target_dir: The directory where repo was cloned
|
|
450
|
+
- branch: The branch checked out (if specified)
|
|
451
|
+
- error: Error details on failure
|
|
452
|
+
|
|
453
|
+
Example:
|
|
454
|
+
clone_repo("https://github.com/anthropics/claude-code")
|
|
455
|
+
clone_repo("git@github.com:user/project.git", branch="develop")
|
|
456
|
+
clone_repo("https://github.com/user/project", target_dir="/custom/path")
|
|
457
|
+
"""
|
|
458
|
+
return tools.clone_repo(url, target_dir, branch)
|
|
459
|
+
|
|
460
|
+
@mcp.tool()
|
|
461
|
+
def setup_repo(
|
|
462
|
+
repo_dir: str,
|
|
463
|
+
) -> dict[str, Any]:
|
|
464
|
+
"""Set up a cloned repository for development.
|
|
465
|
+
|
|
466
|
+
Detects the project type and performs appropriate setup:
|
|
467
|
+
- Creates virtual environment (for Python projects)
|
|
468
|
+
- Installs dependencies (pip, npm, pnpm, yarn, bun)
|
|
469
|
+
- Runs setup scripts (setup-hooks.sh, setup.sh, etc.)
|
|
470
|
+
|
|
471
|
+
Supports Python projects (pyproject.toml, setup.py, requirements.txt)
|
|
472
|
+
and Node.js projects (package.json). Uses uv for Python dependency
|
|
473
|
+
management when available, falling back to standard venv + pip.
|
|
474
|
+
|
|
475
|
+
Args:
|
|
476
|
+
repo_dir: Path to the cloned repository directory to set up.
|
|
477
|
+
|
|
478
|
+
Returns:
|
|
479
|
+
Dictionary containing:
|
|
480
|
+
- success: Whether setup completed successfully
|
|
481
|
+
- message: Human-readable result message
|
|
482
|
+
- work_dir: The directory that was set up
|
|
483
|
+
- steps_completed: List of completed setup steps
|
|
484
|
+
- venv_path: Path to virtual environment (Python projects)
|
|
485
|
+
- dependencies_installed: Whether dependencies were installed
|
|
486
|
+
- setup_scripts_run: List of setup scripts that were executed
|
|
487
|
+
- error: Error details on failure
|
|
488
|
+
|
|
489
|
+
Example:
|
|
490
|
+
setup_repo("/home/user/workspace/claude-task-master/my-project")
|
|
491
|
+
setup_repo("~/workspace/claude-task-master/python-app")
|
|
492
|
+
"""
|
|
493
|
+
return tools.setup_repo(repo_dir)
|
|
494
|
+
|
|
495
|
+
@mcp.tool()
|
|
496
|
+
def plan_repo(
|
|
497
|
+
repo_dir: str,
|
|
498
|
+
goal: str,
|
|
499
|
+
model: str = "opus",
|
|
500
|
+
) -> dict[str, Any]:
|
|
501
|
+
"""Create a plan for a repository without executing any work.
|
|
502
|
+
|
|
503
|
+
This is a plan-only mode that reads the codebase using read-only tools
|
|
504
|
+
(Read, Glob, Grep) and outputs a structured plan with tasks and success
|
|
505
|
+
criteria. No changes are made to the repository.
|
|
506
|
+
|
|
507
|
+
Use this after `clone_repo` and `setup_repo` to plan work before execution,
|
|
508
|
+
or to get a plan for a new goal in an existing repository.
|
|
509
|
+
|
|
510
|
+
Args:
|
|
511
|
+
repo_dir: Path to the repository directory to plan for.
|
|
512
|
+
goal: The goal/task description to plan for. Be specific about
|
|
513
|
+
what you want to accomplish.
|
|
514
|
+
model: Model to use for planning (default: "opus" for best quality).
|
|
515
|
+
Options: "opus", "sonnet", "haiku".
|
|
516
|
+
|
|
517
|
+
Returns:
|
|
518
|
+
Dictionary containing:
|
|
519
|
+
- success: Whether planning completed successfully
|
|
520
|
+
- message: Human-readable result message
|
|
521
|
+
- work_dir: The directory that was planned for
|
|
522
|
+
- goal: The goal that was planned for
|
|
523
|
+
- plan: The generated task plan (markdown with checkboxes)
|
|
524
|
+
- criteria: Success criteria for verifying completion
|
|
525
|
+
- run_id: Unique identifier for this planning session
|
|
526
|
+
- error: Error details on failure
|
|
527
|
+
|
|
528
|
+
Example:
|
|
529
|
+
plan_repo("/home/user/workspace/project", "Add user authentication")
|
|
530
|
+
plan_repo("~/workspace/my-app", "Fix the login bug", model="sonnet")
|
|
531
|
+
"""
|
|
532
|
+
return tools.plan_repo(repo_dir, goal, model)
|
|
533
|
+
|
|
351
534
|
# =============================================================================
|
|
352
535
|
# Resource Wrappers
|
|
353
536
|
# =============================================================================
|