@dalmasonto/taskflow-mcp 1.0.11 → 1.0.15
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.
- package/dist/agent-registry.d.ts +5 -1
- package/dist/agent-registry.js +29 -25
- package/dist/config.d.ts +19 -0
- package/dist/config.js +108 -0
- package/dist/db.js +23 -2
- package/dist/index.js +70 -1
- package/dist/resources.d.ts +2 -0
- package/dist/resources.js +76 -0
- package/dist/sse.js +122 -18
- package/dist/tmux-bridge.js +14 -5
- package/dist/tools/activity.d.ts +10 -0
- package/dist/tools/activity.js +95 -3
- package/dist/tools/agent-inbox.js +8 -8
- package/dist/tools/agent.js +2 -2
- package/dist/tools/analytics.js +86 -3
- package/dist/tools/checkpoint.d.ts +27 -0
- package/dist/tools/checkpoint.js +105 -0
- package/dist/tools/notifications.js +4 -4
- package/dist/tools/projects.js +6 -6
- package/dist/tools/settings.js +24 -3
- package/dist/tools/tasks.d.ts +2 -0
- package/dist/tools/tasks.js +14 -8
- package/dist/tools/terminal.d.ts +2 -0
- package/dist/tools/terminal.js +73 -0
- package/dist/tools/timer.js +4 -4
- package/dist/types.d.ts +5 -1
- package/dist/types.js +2 -0
- package/package.json +1 -1
package/dist/tools/tasks.js
CHANGED
|
@@ -202,6 +202,10 @@ export async function updateTask(params) {
|
|
|
202
202
|
updates.due_date = params.due_date;
|
|
203
203
|
if (params.estimated_time !== undefined)
|
|
204
204
|
updates.estimated_time = params.estimated_time;
|
|
205
|
+
if (params.allowed_tools !== undefined)
|
|
206
|
+
updates.allowed_tools = JSON.stringify(params.allowed_tools);
|
|
207
|
+
if (params.denied_tools !== undefined)
|
|
208
|
+
updates.denied_tools = JSON.stringify(params.denied_tools);
|
|
205
209
|
if (Object.keys(updates).length === 0) {
|
|
206
210
|
return successResponse(oldParsed);
|
|
207
211
|
}
|
|
@@ -356,14 +360,14 @@ export function registerTaskTools(server) {
|
|
|
356
360
|
links: z.array(LinkSchema).optional(),
|
|
357
361
|
due_date: z.string().optional(),
|
|
358
362
|
estimated_time: z.number().optional(),
|
|
359
|
-
}, async (params) => createTask(params));
|
|
363
|
+
}, { readOnlyHint: false }, async (params) => createTask(params));
|
|
360
364
|
server.tool('list_tasks', 'List tasks with optional filters. Use at conversation start to see what is in progress or blocked. Filter by status, project, priority, or tag.', {
|
|
361
365
|
status: TaskStatus.optional(),
|
|
362
366
|
project_id: z.number().optional(),
|
|
363
367
|
priority: TaskPriority.optional(),
|
|
364
368
|
tag: z.string().optional(),
|
|
365
|
-
}, async (params) => listTasks(params));
|
|
366
|
-
server.tool('get_task', 'Get a task by ID with time tracking info. Read the description carefully — it often contains implementation details and acceptance criteria.', { id: z.number() }, async (params) => getTask(params));
|
|
369
|
+
}, { readOnlyHint: true }, async (params) => listTasks(params));
|
|
370
|
+
server.tool('get_task', 'Get a task by ID with time tracking info. Read the description carefully — it often contains implementation details and acceptance criteria.', { id: z.number() }, { readOnlyHint: true }, async (params) => getTask(params));
|
|
367
371
|
server.tool('update_task', 'Update task fields. Use this to add details, update descriptions with progress notes, or adjust priority as you learn more.', {
|
|
368
372
|
id: z.number(),
|
|
369
373
|
title: z.string().optional(),
|
|
@@ -376,12 +380,14 @@ export function registerTaskTools(server) {
|
|
|
376
380
|
links: z.array(LinkSchema).optional(),
|
|
377
381
|
due_date: z.string().optional(),
|
|
378
382
|
estimated_time: z.number().optional(),
|
|
379
|
-
|
|
383
|
+
allowed_tools: z.array(z.string()).optional().describe('Allowlist of MCP tool names for this task. If set, only these tools can be used while timer is active.'),
|
|
384
|
+
denied_tools: z.array(z.string()).optional().describe('Denylist of MCP tool names for this task. These tools are blocked while timer is active.'),
|
|
385
|
+
}, { readOnlyHint: false }, async (params) => updateTask(params));
|
|
380
386
|
server.tool('update_task_status', 'Update task status with transition validation. Use when a task becomes blocked, is partially done, or needs to be reopened.', {
|
|
381
387
|
id: z.number(),
|
|
382
388
|
status: TaskStatus,
|
|
383
|
-
}, async (params) => updateTaskStatus(params));
|
|
384
|
-
server.tool('delete_task', 'Delete a task by ID. Use sparingly — prefer updating status to "done" instead of deleting.', { id: z.number() }, async (params) => deleteTask(params));
|
|
389
|
+
}, { readOnlyHint: false }, async (params) => updateTaskStatus(params));
|
|
390
|
+
server.tool('delete_task', 'Delete a task by ID. Use sparingly — prefer updating status to "done" instead of deleting.', { id: z.number() }, { destructiveHint: true }, async (params) => deleteTask(params));
|
|
385
391
|
server.tool('bulk_create_tasks', 'Create multiple tasks in a single transaction. Useful when breaking down a feature into subtasks.', {
|
|
386
392
|
tasks: z.array(z.object({
|
|
387
393
|
title: z.string(),
|
|
@@ -392,6 +398,6 @@ export function registerTaskTools(server) {
|
|
|
392
398
|
dependencies: z.array(z.number()).optional(),
|
|
393
399
|
tags: z.array(z.string()).optional(),
|
|
394
400
|
})),
|
|
395
|
-
}, async (params) => bulkCreateTasks(params));
|
|
396
|
-
server.tool('search_tasks', 'Search tasks by title or description. Use this to find tasks related to your current work before creating duplicates.', { query: z.string() }, async (params) => searchTasks(params));
|
|
401
|
+
}, { readOnlyHint: false }, async (params) => bulkCreateTasks(params));
|
|
402
|
+
server.tool('search_tasks', 'Search tasks by title or description. Use this to find tasks related to your current work before creating duplicates.', { query: z.string() }, { readOnlyHint: true }, async (params) => searchTasks(params));
|
|
397
403
|
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { execFileSync } from 'child_process';
|
|
3
|
+
import { getDb } from '../db.js';
|
|
4
|
+
import { logActivity, errorResponse, successResponse } from '../helpers.js';
|
|
5
|
+
function getAgentPane(agentName) {
|
|
6
|
+
const db = getDb();
|
|
7
|
+
const agent = db.prepare('SELECT * FROM agent_registry WHERE name = ?').get(agentName);
|
|
8
|
+
if (!agent)
|
|
9
|
+
return { error: errorResponse(`Agent "${agentName}" not found`, 'NOT_FOUND') };
|
|
10
|
+
if (!agent.tmux_pane)
|
|
11
|
+
return { error: errorResponse(`Agent "${agentName}" has no tmux pane`, 'VALIDATION_ERROR') };
|
|
12
|
+
return { agent };
|
|
13
|
+
}
|
|
14
|
+
export function registerTerminalTools(server) {
|
|
15
|
+
server.tool('capture_terminal', 'Capture the current terminal content of an agent\'s tmux pane. Returns the visible text on screen. Useful for seeing what prompts or output an agent is displaying.', {
|
|
16
|
+
agent_name: z.string().describe('Name of the agent whose terminal to capture'),
|
|
17
|
+
}, { readOnlyHint: true }, async (params) => {
|
|
18
|
+
const result = getAgentPane(params.agent_name);
|
|
19
|
+
if (result.error)
|
|
20
|
+
return result.error;
|
|
21
|
+
try {
|
|
22
|
+
const output = execFileSync('tmux', ['capture-pane', '-p', '-t', result.agent.tmux_pane], { timeout: 5000 }).toString();
|
|
23
|
+
return successResponse({
|
|
24
|
+
agent: params.agent_name,
|
|
25
|
+
pane: result.agent.tmux_pane,
|
|
26
|
+
content: output,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
catch (err) {
|
|
30
|
+
return errorResponse(`Failed to capture pane: ${err.message}`, 'VALIDATION_ERROR');
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
server.tool('send_keys', 'Send raw keystrokes to an agent\'s tmux pane. Use this to respond to interactive prompts (yes/no, numbered choices, permission approvals) displayed in an agent\'s terminal. By default appends Enter after the keys.', {
|
|
34
|
+
agent_name: z.string().describe('Name of the agent whose terminal to send keys to'),
|
|
35
|
+
keys: z.string().describe('The keys/text to send (e.g. "yes", "1", "y") or tmux key names (e.g. "Escape", "Up", "Down", "BTab")'),
|
|
36
|
+
enter: z.boolean().optional().describe('Whether to press Enter after the keys (default: true)'),
|
|
37
|
+
literal: z.boolean().optional().describe('Send as literal text with -l flag (default: true). Set false for tmux key names like Escape, Up, Down, Left, Right, BTab'),
|
|
38
|
+
}, { destructiveHint: true }, async (params) => {
|
|
39
|
+
const result = getAgentPane(params.agent_name);
|
|
40
|
+
if (result.error)
|
|
41
|
+
return result.error;
|
|
42
|
+
const sendEnter = params.enter !== false;
|
|
43
|
+
const isLiteral = params.literal !== false;
|
|
44
|
+
try {
|
|
45
|
+
const pane = result.agent.tmux_pane;
|
|
46
|
+
if (isLiteral) {
|
|
47
|
+
// Send as literal text (like typing on a keyboard)
|
|
48
|
+
execFileSync('tmux', ['send-keys', '-t', pane, '-l', params.keys], { stdio: 'ignore', timeout: 5000 });
|
|
49
|
+
if (sendEnter) {
|
|
50
|
+
execFileSync('tmux', ['send-keys', '-t', pane, 'Enter'], { stdio: 'ignore', timeout: 5000 });
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
// Send as tmux key names (Escape, Up, Down, etc.)
|
|
55
|
+
const args = ['send-keys', '-t', pane, params.keys];
|
|
56
|
+
if (sendEnter)
|
|
57
|
+
args.push('Enter');
|
|
58
|
+
execFileSync('tmux', args, { stdio: 'ignore', timeout: 5000 });
|
|
59
|
+
}
|
|
60
|
+
logActivity('terminal_send_keys', `Sent keys to ${params.agent_name}: ${params.keys.slice(0, 50)}`, { entityType: 'agent' });
|
|
61
|
+
return successResponse({
|
|
62
|
+
agent: params.agent_name,
|
|
63
|
+
pane: result.agent.tmux_pane,
|
|
64
|
+
keys: params.keys,
|
|
65
|
+
enter: sendEnter,
|
|
66
|
+
sent: true,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
catch (err) {
|
|
70
|
+
return errorResponse(`Failed to send keys: ${err.message}`, 'VALIDATION_ERROR');
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
}
|
package/dist/tools/timer.js
CHANGED
|
@@ -140,15 +140,15 @@ export async function listSessions(params) {
|
|
|
140
140
|
}
|
|
141
141
|
// ─── MCP registration ─────────────────────────────────────────────────
|
|
142
142
|
export function registerTimerTools(server) {
|
|
143
|
-
server.tool('start_timer', 'Start a timer session for a task. Call this before beginning work on any task to track focused time. Automatically transitions the task to "in_progress".', { task_id: z.number() }, async (params) => startTimer(params));
|
|
144
|
-
server.tool('pause_timer', 'Pause the active timer session for a task. Call when switching context or waiting for user input. The task transitions to "paused".', { task_id: z.number() }, async (params) => pauseTimer(params));
|
|
143
|
+
server.tool('start_timer', 'Start a timer session for a task. Call this before beginning work on any task to track focused time. Automatically transitions the task to "in_progress".', { task_id: z.number() }, { readOnlyHint: false }, async (params) => startTimer(params));
|
|
144
|
+
server.tool('pause_timer', 'Pause the active timer session for a task. Call when switching context or waiting for user input. The task transitions to "paused".', { task_id: z.number() }, { readOnlyHint: false }, async (params) => pauseTimer(params));
|
|
145
145
|
server.tool('stop_timer', 'Stop the active timer and set a final status. Call when finishing work — use "done" if complete, "partial_done" if more remains, "blocked" if stuck.', {
|
|
146
146
|
task_id: z.number(),
|
|
147
147
|
final_status: z.enum(['done', 'partial_done', 'blocked']).optional(),
|
|
148
|
-
}, async (params) => stopTimer(params));
|
|
148
|
+
}, { readOnlyHint: false }, async (params) => stopTimer(params));
|
|
149
149
|
server.tool('list_sessions', 'List timer sessions with optional filters. Use to review time spent on a task or across a date range.', {
|
|
150
150
|
task_id: z.number().optional(),
|
|
151
151
|
start_date: z.string().optional(),
|
|
152
152
|
end_date: z.string().optional(),
|
|
153
|
-
}, async (params) => listSessions(params));
|
|
153
|
+
}, { readOnlyHint: true }, async (params) => listSessions(params));
|
|
154
154
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -22,9 +22,9 @@ export declare const ProjectType: z.ZodEnum<{
|
|
|
22
22
|
export type ProjectType = z.infer<typeof ProjectType>;
|
|
23
23
|
export declare const NotificationType: z.ZodEnum<{
|
|
24
24
|
info: "info";
|
|
25
|
+
error: "error";
|
|
25
26
|
success: "success";
|
|
26
27
|
warning: "warning";
|
|
27
|
-
error: "error";
|
|
28
28
|
}>;
|
|
29
29
|
export type NotificationType = z.infer<typeof NotificationType>;
|
|
30
30
|
export declare const AgentMessageStatus: z.ZodEnum<{
|
|
@@ -66,6 +66,10 @@ export declare const ActivityAction: z.ZodEnum<{
|
|
|
66
66
|
agent_question_answered: "agent_question_answered";
|
|
67
67
|
agent_connected: "agent_connected";
|
|
68
68
|
agent_disconnected: "agent_disconnected";
|
|
69
|
+
terminal_send_keys: "terminal_send_keys";
|
|
70
|
+
terminal_captured: "terminal_captured";
|
|
71
|
+
compaction_summary: "compaction_summary";
|
|
72
|
+
activity_compacted: "activity_compacted";
|
|
69
73
|
}>;
|
|
70
74
|
export type ActivityAction = z.infer<typeof ActivityAction>;
|
|
71
75
|
export declare const VALID_TRANSITIONS: Record<TaskStatus, TaskStatus[]>;
|
package/dist/types.js
CHANGED
|
@@ -16,6 +16,8 @@ export const ActivityAction = z.enum([
|
|
|
16
16
|
'link_added', 'tag_added', 'tag_removed', 'debug_log',
|
|
17
17
|
'agent_question', 'agent_question_answered',
|
|
18
18
|
'agent_connected', 'agent_disconnected',
|
|
19
|
+
'terminal_send_keys', 'terminal_captured',
|
|
20
|
+
'compaction_summary', 'activity_compacted',
|
|
19
21
|
]);
|
|
20
22
|
export const VALID_TRANSITIONS = {
|
|
21
23
|
not_started: ['in_progress', 'blocked'],
|