Skip to content

Commit dfcf43d

Browse files
Filter list outputs based on task state
1 parent 9389763 commit dfcf43d

File tree

9 files changed

+697
-86
lines changed

9 files changed

+697
-86
lines changed

README.md

Lines changed: 36 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -282,24 +282,26 @@ This command displays information about all projects in the system or a specific
282282

283283
```javascript
284284
// Example of how an LLM would use the create_project tool
285-
const createProjectResult = await toolManager.callFunction('create_project', {
286-
initialPrompt: "Create a website for a small business",
287-
projectPlan: "We'll create a responsive website with Home, About, Services, and Contact pages",
288-
tasks: [
289-
{
290-
title: "Set up project structure",
291-
description: "Create repository and initialize with basic HTML/CSS/JS files"
292-
},
293-
{
294-
title: "Design homepage",
295-
description: "Create responsive homepage with navigation and hero section"
296-
},
297-
{
298-
title: "Implement about page",
299-
description: "Create about page with company history and team section"
300-
}
285+
{
286+
'create_project': {
287+
'initialPrompt': "Create a website for a small business",
288+
'projectPlan': "We'll create a responsive website with Home, About, Services, and Contact pages",
289+
'tasks': [
290+
{
291+
'title': "Set up project structure",
292+
'description': "Create repository and initialize with basic HTML/CSS/JS files"
293+
},
294+
{
295+
'title': "Design homepage",
296+
'description': "Create responsive homepage with navigation and hero section"
297+
},
298+
{
299+
'title': "Implement about page",
300+
'description': "Create about page with company history and team section"
301+
}
301302
]
302-
});
303+
}
304+
}
303305

304306
// Response will include:
305307
// {
@@ -319,9 +321,11 @@ const createProjectResult = await toolManager.callFunction('create_project', {
319321

320322
```javascript
321323
// Example of how an LLM would use the get_next_task tool
322-
const nextTaskResult = await toolManager.callFunction('get_next_task', {
323-
projectId: "proj-1234"
324-
});
324+
{
325+
'get_next_task': {
326+
'projectId': "proj-1234"
327+
}
328+
}
325329

326330
// Response will include:
327331
// {
@@ -341,11 +345,13 @@ const nextTaskResult = await toolManager.callFunction('get_next_task', {
341345

342346
```javascript
343347
// Example of how an LLM would use the mark_task_done tool
344-
const markDoneResult = await toolManager.callFunction('mark_task_done', {
345-
projectId: "proj-1234",
346-
taskId: "task-1",
347-
completedDetails: "Created repository at github.com/example/business-site and initialized with HTML5 boilerplate, CSS reset, and basic JS structure." // Required when marking as done
348-
});
348+
{
349+
'mark_task_done': {
350+
'projectId': "proj-1234",
351+
'taskId': "task-1",
352+
'completedDetails': "Created repository at github.com/example/business-site and initialized with HTML5 boilerplate, CSS reset, and basic JS structure." // Required when marking as done
353+
}
354+
}
349355

350356
// Response will include:
351357
// {
@@ -376,9 +382,11 @@ After approval, the LLM can check the task status using `read_task` or get the n
376382
```javascript
377383
// Example of how an LLM would use the finalize_project tool
378384
// (Called after all tasks are done and approved)
379-
const finalizeResult = await toolManager.callFunction('finalize_project', {
380-
projectId: "proj-1234"
381-
});
385+
{
386+
'finalize_project': {
387+
'projectId': "proj-1234"
388+
}
389+
}
382390

383391
// Response will include:
384392
// {

index.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { z } from "zod";
1212

1313
import { TaskManagerServer } from "./src/server/TaskManagerServer.js";
1414
import { ALL_TOOLS } from "./src/types/tools.js";
15+
import { TaskState } from "./src/types/index.js";
1516

1617
const DEFAULT_PATH = path.join(os.homedir(), "Documents", "tasks.json");
1718
const TASK_FILE_PATH = process.env.TASK_MANAGER_FILE_PATH || DEFAULT_PATH;
@@ -49,7 +50,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
4950
switch (name) {
5051
// Project tools
5152
case "list_projects": {
52-
const result = await taskManagerServer.listProjects();
53+
const state = args.state as TaskState | undefined;
54+
const result = await taskManagerServer.listProjects(state);
5355
return {
5456
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
5557
};
@@ -130,12 +132,11 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
130132

131133
// Task tools
132134
case "list_tasks": {
133-
// No explicit list tasks method, so return a message
135+
const projectId = args.projectId ? String(args.projectId) : undefined;
136+
const state = args.state as TaskState | undefined;
137+
const result = await taskManagerServer.listTasks(projectId, state);
134138
return {
135-
content: [{ type: "text", text: JSON.stringify({
136-
status: "error",
137-
message: "list_tasks functionality to be implemented in future version"
138-
}, null, 2) }],
139+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
139140
};
140141
}
141142

src/cli.ts

Lines changed: 91 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -240,15 +240,23 @@ program
240240
.command("list")
241241
.description("List all projects and their tasks")
242242
.option('-p, --project <projectId>', 'Show details for a specific project')
243+
.option('-s, --state <state>', "Filter by task/project state (open, pending_approval, completed, all)")
243244
.action(async (options) => {
244245
try {
246+
// Validate state option if provided
247+
if (options.state && !['open', 'pending_approval', 'completed', 'all'].includes(options.state)) {
248+
console.error(chalk.red(`Invalid state value: ${options.state}`));
249+
console.log(chalk.yellow('Valid states are: open, pending_approval, completed, all'));
250+
process.exit(1);
251+
}
252+
245253
const data = await readData();
246254

247255
if (data.projects.length === 0) {
248256
console.log(chalk.yellow('No projects found.'));
249257
return;
250258
}
251-
259+
252260
if (options.project) {
253261
// Show details for a specific project
254262
const project = data.projects.find(p => p.projectId === options.project);
@@ -260,46 +268,101 @@ program
260268
});
261269
process.exit(1);
262270
}
263-
264-
console.log(chalk.cyan(`\n📋 Project: ${chalk.bold(project.projectId)}`));
271+
272+
// Filter tasks by state if specified
273+
let tasks = project.tasks;
274+
if (options.state && options.state !== "all") {
275+
tasks = project.tasks.filter(task => {
276+
switch (options.state) {
277+
case "open":
278+
return task.status !== "done";
279+
case "pending_approval":
280+
return task.status === "done" && !task.approved;
281+
case "completed":
282+
return task.status === "done" && task.approved;
283+
default:
284+
return true;
285+
}
286+
});
287+
}
288+
289+
console.log(chalk.cyan(`\n📋 Project ${chalk.bold(options.project)} details:`));
265290
console.log(` - ${chalk.bold('Initial Prompt:')} ${project.initialPrompt}`);
266-
console.log(` - ${chalk.bold('Status:')} ${project.completed ? chalk.green('Completed ✓') : chalk.yellow('In Progress ⟳')}`);
291+
if (project.projectPlan && project.projectPlan !== project.initialPrompt) {
292+
console.log(` - ${chalk.bold('Project Plan:')} ${project.projectPlan}`);
293+
}
294+
console.log(` - ${chalk.bold('Status:')} ${project.completed ? chalk.green('Completed ✓') : chalk.yellow('In Progress')}`);
295+
296+
// Show progress info
297+
const totalTasks = project.tasks.length;
298+
const completedTasks = project.tasks.filter(t => t.status === "done").length;
299+
const approvedTasks = project.tasks.filter(t => t.approved).length;
267300

268-
console.log(chalk.cyan(`\n📋 Tasks:`));
269-
if (project.tasks.length === 0) {
270-
console.log(chalk.yellow(' No tasks found for this project.'));
271-
} else {
272-
project.tasks.forEach(task => {
273-
console.log(` - ${chalk.bold(task.id)}: ${task.title}`);
274-
console.log(` ${chalk.dim('Description:')} ${task.description}`);
275-
console.log(` ${chalk.dim('Status:')} ${task.status === 'done' ? chalk.green('Done ✓') : task.status === 'in progress' ? chalk.yellow('In Progress ⟳') : chalk.blue('Not Started ○')}`);
276-
console.log(` ${chalk.dim('Approved:')} ${task.approved ? chalk.green('Yes ✓') : chalk.red('No ✗')}`);
277-
if (task.status === 'done') {
278-
console.log(` ${chalk.dim('Completed Details:')} ${task.completedDetails || chalk.gray("None")}`);
301+
console.log(chalk.cyan(`\n📊 Progress: ${chalk.bold(`${approvedTasks}/${completedTasks}/${totalTasks}`)} (approved/completed/total)`));
302+
303+
// Create a progress bar
304+
const bar = '▓'.repeat(approvedTasks) + '▒'.repeat(completedTasks - approvedTasks) + '░'.repeat(totalTasks - completedTasks);
305+
console.log(` ${bar}`);
306+
307+
if (tasks.length > 0) {
308+
console.log(chalk.cyan('\n📝 Tasks:'));
309+
tasks.forEach(t => {
310+
const status = t.status === 'done' ? chalk.green('Done ✓') : t.status === 'in progress' ? chalk.yellow('In Progress ⟳') : chalk.blue('Not Started ○');
311+
const approved = t.approved ? chalk.green('Yes ✓') : chalk.red('No ✗');
312+
console.log(` - ${chalk.bold(t.id)}: ${t.title}`);
313+
console.log(` Status: ${status}, Approved: ${approved}`);
314+
console.log(` Description: ${t.description}`);
315+
if (t.completedDetails) {
316+
console.log(` Completed Details: ${t.completedDetails}`);
279317
}
280-
console.log();
281318
});
319+
} else {
320+
console.log(chalk.yellow('\nNo tasks match the specified state filter.'));
282321
}
283322
} else {
284323
// List all projects
285-
console.log(chalk.cyan(`\n📋 All Projects:`));
286-
data.projects.forEach(project => {
287-
const totalTasks = project.tasks.length;
288-
const completedTasks = project.tasks.filter(t => t.status === "done").length;
289-
const approvedTasks = project.tasks.filter(t => t.approved).length;
324+
let projectsToList = data.projects;
325+
326+
if (options.state && options.state !== "all") {
327+
projectsToList = data.projects.filter(project => {
328+
switch (options.state) {
329+
case "open":
330+
return !project.completed && project.tasks.some(task => task.status !== "done");
331+
case "pending_approval":
332+
return project.tasks.some(task => task.status === "done" && !task.approved);
333+
case "completed":
334+
return project.completed && project.tasks.every(task => task.status === "done" && task.approved);
335+
default:
336+
return true;
337+
}
338+
});
339+
}
340+
341+
if (projectsToList.length === 0) {
342+
console.log(chalk.yellow('No projects match the specified state filter.'));
343+
return;
344+
}
345+
346+
console.log(chalk.cyan('\n📋 Projects List:'));
347+
projectsToList.forEach(p => {
348+
const totalTasks = p.tasks.length;
349+
const completedTasks = p.tasks.filter(t => t.status === "done").length;
350+
const approvedTasks = p.tasks.filter(t => t.approved).length;
351+
const status = p.completed ? chalk.green('Completed ✓') : chalk.yellow('In Progress');
352+
353+
console.log(`\n${chalk.bold(p.projectId)}: ${status}`);
354+
console.log(` Initial Prompt: ${p.initialPrompt.substring(0, 100)}${p.initialPrompt.length > 100 ? '...' : ''}`);
355+
console.log(` Progress: ${chalk.bold(`${approvedTasks}/${completedTasks}/${totalTasks}`)} (approved/completed/total)`);
290356

291-
console.log(` - ${chalk.bold(project.projectId)}: ${project.initialPrompt.substring(0, 50)}${project.initialPrompt.length > 50 ? '...' : ''}`);
292-
console.log(` ${chalk.dim('Status:')} ${project.completed ? chalk.green('Completed ✓') : chalk.yellow('In Progress ⟳')}`);
293-
console.log(` ${chalk.dim('Progress:')} ${chalk.bold(`${approvedTasks}/${completedTasks}/${totalTasks}`)} (approved/completed/total)`);
294-
console.log();
357+
// Create a progress bar
358+
const bar = '▓'.repeat(approvedTasks) + '▒'.repeat(completedTasks - approvedTasks) + '░'.repeat(totalTasks - completedTasks);
359+
console.log(` ${bar}`);
295360
});
296-
297-
console.log(chalk.blue(`Use '${chalk.bold('task-manager-cli list -p <projectId>')}' for detailed information about a specific project.`));
298361
}
299362
} catch (error) {
300363
console.error(chalk.red(`An error occurred: ${error instanceof Error ? error.message : String(error)}`));
301364
process.exit(1);
302365
}
303366
});
304367

305-
program.parse();
368+
program.parse(process.argv);

src/server/TaskManagerServer.ts

Lines changed: 65 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import * as fs from "node:fs/promises";
22
import * as path from "node:path";
33
import * as os from "node:os";
4-
import { Task, Project, TaskManagerFile } from "../types/index.js";
4+
import { Task, Project, TaskManagerFile, TaskState } from "../types/index.js";
55

66
// Get platform-appropriate app data directory
77
const getAppDataDir = () => {
@@ -316,13 +316,30 @@ export class TaskManagerServer {
316316
return { status: "task_not_found", message: "No such task found" };
317317
}
318318

319-
public async listProjects() {
319+
public async listProjects(state?: TaskState) {
320320
await this.loadTasks();
321+
let filteredProjects = this.data.projects;
322+
323+
if (state && state !== "all") {
324+
filteredProjects = this.data.projects.filter(proj => {
325+
switch (state) {
326+
case "open":
327+
return !proj.completed && proj.tasks.some(task => task.status !== "done");
328+
case "pending_approval":
329+
return proj.tasks.some(task => task.status === "done" && !task.approved);
330+
case "completed":
331+
return proj.completed && proj.tasks.every(task => task.status === "done" && task.approved);
332+
default:
333+
return true; // Should not happen due to type safety
334+
}
335+
});
336+
}
337+
321338
const projectsList = this.formatProjectsList();
322339
return {
323340
status: "projects_listed",
324341
message: `Current projects in the system:\n${projectsList}`,
325-
projects: this.data.projects.map((proj) => ({
342+
projects: filteredProjects.map((proj) => ({
326343
projectId: proj.projectId,
327344
initialPrompt: proj.initialPrompt,
328345
totalTasks: proj.tasks.length,
@@ -332,6 +349,51 @@ export class TaskManagerServer {
332349
};
333350
}
334351

352+
public async listTasks(projectId?: string, state?: TaskState) {
353+
await this.loadTasks();
354+
let tasks: Task[] = [];
355+
356+
if (projectId) {
357+
const project = this.data.projects.find(p => p.projectId === projectId);
358+
if (!project) {
359+
return { status: 'error', message: 'Project not found' };
360+
}
361+
tasks = project.tasks;
362+
} else {
363+
// Flatten all tasks from all projects if no projectId is given
364+
tasks = this.data.projects.flatMap(project => project.tasks);
365+
}
366+
367+
// Apply state filtering
368+
let filteredTasks = tasks;
369+
if (state && state !== "all") {
370+
filteredTasks = tasks.filter(task => {
371+
switch (state) {
372+
case "open":
373+
return task.status === "not started" || task.status === "in progress";
374+
case "pending_approval":
375+
return task.status === "done" && !task.approved;
376+
case "completed":
377+
return task.status === "done" && task.approved;
378+
default:
379+
return true; // Should not happen due to type safety
380+
}
381+
});
382+
}
383+
384+
return {
385+
status: "tasks_listed",
386+
message: `Tasks in the system${projectId ? ` for project ${projectId}` : ""}:\n${filteredTasks.length} tasks found.`,
387+
tasks: filteredTasks.map(task => ({
388+
id: task.id,
389+
title: task.title,
390+
description: task.description,
391+
status: task.status,
392+
approved: task.approved
393+
}))
394+
};
395+
}
396+
335397
public async addTasksToProject(
336398
projectId: string,
337399
tasks: { title: string; description: string }[]

0 commit comments

Comments
 (0)