claude-task-master 0.1.1__py3-none-any.whl → 0.1.3__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 (42) hide show
  1. claude_task_master/__init__.py +1 -1
  2. claude_task_master/api/__init__.py +98 -0
  3. claude_task_master/api/models.py +553 -0
  4. claude_task_master/api/routes.py +1135 -0
  5. claude_task_master/api/routes_config.py +160 -0
  6. claude_task_master/api/routes_control.py +278 -0
  7. claude_task_master/api/routes_webhooks.py +980 -0
  8. claude_task_master/api/server.py +551 -0
  9. claude_task_master/auth/__init__.py +89 -0
  10. claude_task_master/auth/middleware.py +448 -0
  11. claude_task_master/auth/password.py +332 -0
  12. claude_task_master/bin/claudetm +1 -1
  13. claude_task_master/cli.py +4 -0
  14. claude_task_master/cli_commands/__init__.py +2 -0
  15. claude_task_master/cli_commands/ci_helpers.py +114 -0
  16. claude_task_master/cli_commands/control.py +191 -0
  17. claude_task_master/cli_commands/fix_pr.py +260 -0
  18. claude_task_master/cli_commands/fix_session.py +174 -0
  19. claude_task_master/cli_commands/workflow.py +51 -3
  20. claude_task_master/core/__init__.py +13 -0
  21. claude_task_master/core/agent_message.py +27 -5
  22. claude_task_master/core/control.py +466 -0
  23. claude_task_master/core/orchestrator.py +316 -4
  24. claude_task_master/core/pr_context.py +7 -2
  25. claude_task_master/core/prompts_working.py +32 -12
  26. claude_task_master/core/state.py +84 -2
  27. claude_task_master/core/state_exceptions.py +9 -6
  28. claude_task_master/core/workflow_stages.py +160 -21
  29. claude_task_master/github/client_pr.py +43 -1
  30. claude_task_master/mcp/auth.py +153 -0
  31. claude_task_master/mcp/server.py +268 -10
  32. claude_task_master/mcp/tools.py +281 -0
  33. claude_task_master/server.py +489 -0
  34. claude_task_master/webhooks/__init__.py +73 -0
  35. claude_task_master/webhooks/client.py +703 -0
  36. claude_task_master/webhooks/config.py +565 -0
  37. claude_task_master/webhooks/events.py +639 -0
  38. {claude_task_master-0.1.1.dist-info → claude_task_master-0.1.3.dist-info}/METADATA +144 -6
  39. {claude_task_master-0.1.1.dist-info → claude_task_master-0.1.3.dist-info}/RECORD +42 -21
  40. {claude_task_master-0.1.1.dist-info → claude_task_master-0.1.3.dist-info}/entry_points.txt +2 -0
  41. {claude_task_master-0.1.1.dist-info → claude_task_master-0.1.3.dist-info}/WHEEL +0 -0
  42. {claude_task_master-0.1.1.dist-info → claude_task_master-0.1.3.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,160 @@
1
+ """Configuration update endpoint for Claude Task Master REST API.
2
+
3
+ This module provides the PATCH /config endpoint for runtime configuration updates.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ import logging
9
+ from pathlib import Path
10
+ from typing import TYPE_CHECKING
11
+
12
+ from claude_task_master.api.models import (
13
+ ConfigUpdateRequest,
14
+ ControlResponse,
15
+ ErrorResponse,
16
+ )
17
+ from claude_task_master.core.state import StateManager
18
+
19
+ if TYPE_CHECKING:
20
+ from fastapi import APIRouter, Request
21
+ from fastapi.responses import JSONResponse
22
+
23
+ # Import FastAPI - using try/except for graceful degradation
24
+ try:
25
+ from fastapi import APIRouter, Request
26
+ from fastapi.responses import JSONResponse
27
+
28
+ FASTAPI_AVAILABLE = True
29
+ except ImportError:
30
+ FASTAPI_AVAILABLE = False
31
+
32
+ logger = logging.getLogger(__name__)
33
+
34
+
35
+ def _get_state_manager(request: Request) -> StateManager:
36
+ """Get state manager from request, using working directory from app state.
37
+
38
+ Args:
39
+ request: The FastAPI request object.
40
+
41
+ Returns:
42
+ StateManager instance configured for the app's working directory.
43
+ """
44
+ working_dir: Path = getattr(request.app.state, "working_dir", Path.cwd())
45
+ state_dir = working_dir / ".claude-task-master"
46
+ return StateManager(state_dir=state_dir)
47
+
48
+
49
+ def create_config_router() -> APIRouter:
50
+ """Create router for configuration endpoint.
51
+
52
+ Returns:
53
+ APIRouter configured with config endpoint.
54
+
55
+ Raises:
56
+ ImportError: If FastAPI is not installed.
57
+ """
58
+ if not FASTAPI_AVAILABLE:
59
+ raise ImportError(
60
+ "FastAPI not installed. Install with: pip install claude-task-master[api]"
61
+ )
62
+
63
+ router = APIRouter(tags=["Config"])
64
+
65
+ @router.patch(
66
+ "/config",
67
+ response_model=ControlResponse,
68
+ responses={
69
+ 400: {"model": ErrorResponse, "description": "Invalid request"},
70
+ 404: {"model": ErrorResponse, "description": "No active task found"},
71
+ 500: {"model": ErrorResponse, "description": "Internal server error"},
72
+ },
73
+ summary="Update Configuration",
74
+ description="Update task configuration options at runtime.",
75
+ )
76
+ async def patch_config(
77
+ request: Request, update: ConfigUpdateRequest
78
+ ) -> ControlResponse | JSONResponse:
79
+ """Update task configuration.
80
+
81
+ Updates the task options with the provided values. Only the fields
82
+ specified in the request are updated; other options remain unchanged.
83
+
84
+ Args:
85
+ request: The FastAPI request object.
86
+ update: Configuration update request.
87
+
88
+ Returns:
89
+ ControlResponse with update details.
90
+
91
+ Raises:
92
+ 400: If no updates provided.
93
+ 404: If no active task exists.
94
+ 500: If an error occurs updating configuration.
95
+ """
96
+ state_manager = _get_state_manager(request)
97
+
98
+ if not state_manager.exists():
99
+ return JSONResponse(
100
+ status_code=404,
101
+ content=ErrorResponse(
102
+ error="not_found",
103
+ message="No active task found",
104
+ suggestion="Start a new task with 'claudetm start <goal>'",
105
+ ).model_dump(),
106
+ )
107
+
108
+ try:
109
+ state = state_manager.load_state()
110
+
111
+ # Check if there are any updates
112
+ if not update.has_updates():
113
+ return JSONResponse(
114
+ status_code=400,
115
+ content=ErrorResponse(
116
+ error="invalid_request",
117
+ message="No configuration updates provided",
118
+ suggestion="Include at least one field to update in the request body",
119
+ ).model_dump(),
120
+ )
121
+
122
+ # Build update dict
123
+ updates_dict = update.to_update_dict()
124
+ previous_status = state.status
125
+
126
+ # Apply updates to options
127
+ options_dict = state.options.model_dump()
128
+ options_dict.update(updates_dict)
129
+
130
+ # Update state options
131
+ from claude_task_master.core.state import TaskOptions
132
+
133
+ state.options = TaskOptions(**options_dict)
134
+ state_manager.save_state(state)
135
+
136
+ # Build response
137
+ updated_fields = list(updates_dict.keys())
138
+ fields_str = ", ".join(updated_fields)
139
+
140
+ return ControlResponse(
141
+ success=True,
142
+ operation="update_config",
143
+ message=f"Configuration updated: {fields_str}",
144
+ previous_status=previous_status,
145
+ new_status=state.status,
146
+ details={"updated": updates_dict},
147
+ )
148
+
149
+ except Exception as e:
150
+ logger.exception("Error updating configuration")
151
+ return JSONResponse(
152
+ status_code=500,
153
+ content=ErrorResponse(
154
+ error="internal_error",
155
+ message="Failed to update configuration",
156
+ detail=str(e),
157
+ ).model_dump(),
158
+ )
159
+
160
+ return router
@@ -0,0 +1,278 @@
1
+ """Control endpoints for Claude Task Master REST API.
2
+
3
+ This module provides the POST /control/stop and POST /control/resume endpoints
4
+ for runtime task control.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import logging
10
+ from pathlib import Path
11
+ from typing import TYPE_CHECKING
12
+
13
+ from claude_task_master.api.models import (
14
+ ErrorResponse,
15
+ ResumeRequest,
16
+ StopRequest,
17
+ TaskStatus,
18
+ )
19
+ from claude_task_master.core.state import StateManager
20
+
21
+ if TYPE_CHECKING:
22
+ from fastapi import APIRouter, Request
23
+ from fastapi.responses import JSONResponse
24
+
25
+ # Import FastAPI - using try/except for graceful degradation
26
+ try:
27
+ from fastapi import APIRouter, Body, Request
28
+ from fastapi.responses import JSONResponse
29
+
30
+ FASTAPI_AVAILABLE = True
31
+ except ImportError:
32
+ FASTAPI_AVAILABLE = False
33
+
34
+ logger = logging.getLogger(__name__)
35
+
36
+ # Module-level singletons for default request bodies
37
+ _default_stop_request = StopRequest()
38
+ _default_resume_request = ResumeRequest()
39
+
40
+
41
+ def _get_state_manager(request: Request) -> StateManager:
42
+ """Get state manager from request, using working directory from app state.
43
+
44
+ Args:
45
+ request: The FastAPI request object.
46
+
47
+ Returns:
48
+ StateManager instance configured for the app's working directory.
49
+ """
50
+ working_dir: Path = getattr(request.app.state, "working_dir", Path.cwd())
51
+ state_dir = working_dir / ".claude-task-master"
52
+ return StateManager(state_dir=state_dir)
53
+
54
+
55
+ def create_control_router() -> APIRouter:
56
+ """Create router for control endpoints.
57
+
58
+ Returns:
59
+ APIRouter configured with control endpoints.
60
+
61
+ Raises:
62
+ ImportError: If FastAPI is not installed.
63
+ """
64
+ if not FASTAPI_AVAILABLE:
65
+ raise ImportError(
66
+ "FastAPI not installed. Install with: pip install claude-task-master[api]"
67
+ )
68
+
69
+ router = APIRouter(tags=["Control"])
70
+
71
+ @router.post(
72
+ "/control/stop",
73
+ response_model=dict, # Using dict instead of ControlResponse for flexibility
74
+ responses={
75
+ 400: {"model": ErrorResponse, "description": "Invalid request"},
76
+ 404: {"model": ErrorResponse, "description": "No active task found"},
77
+ 500: {"model": ErrorResponse, "description": "Internal server error"},
78
+ },
79
+ summary="Stop Task",
80
+ description="Stop the currently running task.",
81
+ )
82
+ async def post_control_stop(
83
+ request: Request,
84
+ stop_req: StopRequest = Body(default=_default_stop_request), # noqa: B008
85
+ ) -> dict | JSONResponse:
86
+ """Stop task execution.
87
+
88
+ Stops the current task by updating its status to "stopped".
89
+ Optionally cleans up state files.
90
+
91
+ Args:
92
+ request: The FastAPI request object.
93
+ stop_req: Stop request with optional reason and cleanup flag.
94
+
95
+ Returns:
96
+ Response with stop confirmation.
97
+
98
+ Raises:
99
+ 400: If task cannot be stopped.
100
+ 404: If no active task exists.
101
+ 500: If an error occurs stopping the task.
102
+ """
103
+ state_manager = _get_state_manager(request)
104
+
105
+ if not state_manager.exists():
106
+ return JSONResponse(
107
+ status_code=404,
108
+ content=ErrorResponse(
109
+ error="not_found",
110
+ message="No active task found",
111
+ suggestion="Start a new task with 'claudetm start <goal>'",
112
+ ).model_dump(),
113
+ )
114
+
115
+ try:
116
+ state = state_manager.load_state()
117
+ previous_status = state.status
118
+
119
+ # Check if task can be stopped
120
+ if state.status in (TaskStatus.SUCCESS, TaskStatus.FAILED):
121
+ return JSONResponse(
122
+ status_code=400,
123
+ content=ErrorResponse(
124
+ error="invalid_operation",
125
+ message=f"Cannot stop task from {state.status} status",
126
+ suggestion="Task is already in a terminal state",
127
+ ).model_dump(),
128
+ )
129
+
130
+ # Update status to stopped
131
+ state.status = TaskStatus.STOPPED.value
132
+ state_manager.save_state(state)
133
+
134
+ # Handle cleanup if requested
135
+ if stop_req.cleanup:
136
+ # Keep logs but remove other state files
137
+ logs_dir = state_manager.state_dir / "logs"
138
+ if logs_dir.exists():
139
+ # Keep logs intact
140
+ pass
141
+
142
+ # Remove state files except logs
143
+ for file_path in state_manager.state_dir.iterdir():
144
+ if file_path.is_file() and file_path.name != "logs":
145
+ file_path.unlink()
146
+
147
+ reason_msg = f": {stop_req.reason}" if stop_req.reason else ""
148
+ return {
149
+ "success": True,
150
+ "operation": "stop",
151
+ "message": f"Task stopped successfully{reason_msg}",
152
+ "previous_status": previous_status,
153
+ "new_status": TaskStatus.STOPPED,
154
+ "details": {
155
+ "reason": stop_req.reason,
156
+ "cleanup": stop_req.cleanup,
157
+ },
158
+ }
159
+
160
+ except Exception as e:
161
+ logger.exception("Error stopping task")
162
+ return JSONResponse(
163
+ status_code=500,
164
+ content=ErrorResponse(
165
+ error="internal_error",
166
+ message="Failed to stop task",
167
+ detail=str(e),
168
+ ).model_dump(),
169
+ )
170
+
171
+ @router.post(
172
+ "/control/resume",
173
+ response_model=dict, # Using dict instead of ControlResponse for flexibility
174
+ responses={
175
+ 400: {"model": ErrorResponse, "description": "Invalid request"},
176
+ 404: {"model": ErrorResponse, "description": "No active task found"},
177
+ 500: {"model": ErrorResponse, "description": "Internal server error"},
178
+ },
179
+ summary="Resume Task",
180
+ description="Resume a paused or stopped task.",
181
+ )
182
+ async def post_control_resume(
183
+ request: Request,
184
+ resume_req: ResumeRequest = Body(default=_default_resume_request), # noqa: B008
185
+ ) -> dict | JSONResponse:
186
+ """Resume task execution.
187
+
188
+ Resumes a task that was paused or stopped by updating its status
189
+ back to "working".
190
+
191
+ Args:
192
+ request: The FastAPI request object.
193
+ resume_req: Resume request with optional reason.
194
+
195
+ Returns:
196
+ Response with resume confirmation.
197
+
198
+ Raises:
199
+ 400: If task cannot be resumed.
200
+ 404: If no active task exists.
201
+ 500: If an error occurs resuming the task.
202
+ """
203
+ state_manager = _get_state_manager(request)
204
+
205
+ if not state_manager.exists():
206
+ return JSONResponse(
207
+ status_code=404,
208
+ content=ErrorResponse(
209
+ error="not_found",
210
+ message="No active task found",
211
+ suggestion="Start a new task with 'claudetm start <goal>'",
212
+ ).model_dump(),
213
+ )
214
+
215
+ try:
216
+ state = state_manager.load_state()
217
+ previous_status = state.status
218
+
219
+ # Check if task can be resumed
220
+ if state.status == TaskStatus.WORKING.value:
221
+ # Idempotent - already working, just return success
222
+ reason_msg = f": {resume_req.reason}" if resume_req.reason else ""
223
+ return {
224
+ "success": True,
225
+ "operation": "resume",
226
+ "message": f"Task already running{reason_msg}",
227
+ "previous_status": previous_status,
228
+ "new_status": TaskStatus.WORKING,
229
+ "details": {"reason": resume_req.reason},
230
+ }
231
+
232
+ if state.status in (TaskStatus.SUCCESS, TaskStatus.FAILED):
233
+ return JSONResponse(
234
+ status_code=400,
235
+ content=ErrorResponse(
236
+ error="invalid_operation",
237
+ message=f"Cannot resume task from {state.status} status",
238
+ suggestion="Start a new task to continue work",
239
+ ).model_dump(),
240
+ )
241
+
242
+ # Allow resume from: paused, blocked, stopped
243
+ if state.status not in (TaskStatus.PAUSED, TaskStatus.BLOCKED, TaskStatus.STOPPED):
244
+ return JSONResponse(
245
+ status_code=400,
246
+ content=ErrorResponse(
247
+ error="invalid_state",
248
+ message=f"Cannot resume task from {state.status} status",
249
+ suggestion="Task must be paused, blocked, or stopped to resume",
250
+ ).model_dump(),
251
+ )
252
+
253
+ # Update status to working
254
+ state.status = TaskStatus.WORKING.value
255
+ state_manager.save_state(state)
256
+
257
+ reason_msg = f": {resume_req.reason}" if resume_req.reason else ""
258
+ return {
259
+ "success": True,
260
+ "operation": "resume",
261
+ "message": f"Task resumed successfully{reason_msg}",
262
+ "previous_status": previous_status,
263
+ "new_status": TaskStatus.WORKING,
264
+ "details": {"reason": resume_req.reason},
265
+ }
266
+
267
+ except Exception as e:
268
+ logger.exception("Error resuming task")
269
+ return JSONResponse(
270
+ status_code=500,
271
+ content=ErrorResponse(
272
+ error="internal_error",
273
+ message="Failed to resume task",
274
+ detail=str(e),
275
+ ).model_dump(),
276
+ )
277
+
278
+ return router