Skip to content

Commit 3545de4

Browse files
Move file reading out of client and into TaskManager
1 parent f4d8222 commit 3545de4

File tree

8 files changed

+114
-31
lines changed

8 files changed

+114
-31
lines changed

README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ Usually you will set the tool configuration in Claude Desktop, Cursor, or anothe
3030
To use the CLI utility, you can use the following command:
3131

3232
```bash
33-
npx task-manager-cli --help
33+
npx taskqueue --help
3434
```
3535

3636
This will show the available commands and options.
@@ -134,7 +134,7 @@ A typical workflow for an LLM using this task manager would be:
134134
Task approval is controlled exclusively by the human user through the CLI command:
135135

136136
```bash
137-
npx task-manager-cli approve-task -- <projectId> <taskId>
137+
npx taskqueue approve-task -- <projectId> <taskId>
138138
```
139139

140140
Options:
@@ -147,13 +147,13 @@ Note: Tasks must be marked as "done" with completed details before they can be a
147147
The CLI provides a command to list all projects and tasks:
148148

149149
```bash
150-
npx task-manager-cli list-tasks
150+
npx taskqueue list-tasks
151151
```
152152

153153
To view details of a specific project:
154154

155155
```bash
156-
npx task-manager-cli list-tasks -- -p <projectId>
156+
npx taskqueue list-tasks -- -p <projectId>
157157
```
158158

159159
This command displays information about all projects in the system or a specific project, including:

package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"type": "module",
88
"bin": {
99
"taskqueue-mcp": "dist/index.js",
10-
"task-manager-cli": "dist/src/client/index.js"
10+
"taskqueue": "dist/src/client/index.js"
1111
},
1212
"files": [
1313
"dist/index.js",

src/client/cli.ts

Lines changed: 5 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import fs from "fs/promises";
1414
const program = new Command();
1515

1616
program
17-
.name("task-manager-cli")
17+
.name("taskqueue")
1818
.description("CLI for the Task Manager MCP Server")
1919
.version("1.2.0")
2020
.option(
@@ -151,7 +151,7 @@ program
151151

152152
if (completedTasks === totalTasks && approvedTasks === totalTasks) {
153153
console.log(chalk.green('\n🎉 All tasks are completed and approved!'));
154-
console.log(chalk.blue(`The project can now be finalized using: task-manager-cli finalize ${projectId}`));
154+
console.log(chalk.blue(`The project can now be finalized using: taskqueue finalize ${projectId}`));
155155
} else {
156156
if (totalTasks - completedTasks > 0) {
157157
console.log(chalk.yellow(`\n${totalTasks - completedTasks} tasks remaining to be completed.`));
@@ -281,7 +281,7 @@ program
281281

282282
console.log(chalk.green('\n🎉 Project successfully completed and approved!'));
283283
console.log(chalk.gray('You can view the project details anytime using:'));
284-
console.log(chalk.blue(` task-manager-cli list -p ${projectId}`));
284+
console.log(chalk.blue(` taskqueue list -p ${projectId}`));
285285

286286
} catch (error) {
287287
const normalized = normalizeError(error);
@@ -490,27 +490,12 @@ program
490490
try {
491491
console.log(chalk.blue(`Generating project plan from prompt...`));
492492

493-
// Read attachment files if provided
494-
const attachments: string[] = [];
495-
for (const file of options.attachment) {
496-
try {
497-
const content = await fs.readFile(file, 'utf-8');
498-
attachments.push(content);
499-
} catch (error: unknown) {
500-
if (error instanceof Error) {
501-
console.error(chalk.yellow(`Warning: Could not read attachment file ${chalk.bold(file)}: ${error.message}`));
502-
} else {
503-
console.error(chalk.yellow(`Warning: Could not read attachment file ${chalk.bold(file)}: Unknown error`));
504-
}
505-
}
506-
}
507-
508-
// Call the generateProjectPlan method
493+
// Pass attachment filenames directly to the server
509494
const response = await taskManager.generateProjectPlan({
510495
prompt: options.prompt,
511496
provider: options.provider,
512497
model: options.model,
513-
attachments,
498+
attachments: options.attachment // Pass the filenames directly
514499
});
515500

516501
if ('error' in response) {

src/server/FileSystemService.ts

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { readFile, writeFile, mkdir } from 'node:fs/promises';
2-
import { dirname, join } from "node:path";
2+
import { dirname, join, resolve } from "node:path";
33
import { homedir } from "node:os";
44
import { TaskManagerFile, ErrorCode } from "../types/index.js";
55
import { createError } from "../utils/errors.js";
@@ -176,4 +176,30 @@ export class FileSystemService {
176176
}
177177
});
178178
}
179+
180+
/**
181+
* Reads an attachment file from the current working directory
182+
* @param filename The name of the file to read (relative to cwd)
183+
* @returns The contents of the file as a string
184+
* @throws {StandardError} If the file cannot be read
185+
*/
186+
public async readAttachmentFile(filename: string): Promise<string> {
187+
try {
188+
const filePath = resolve(process.cwd(), filename);
189+
return await readFile(filePath, 'utf-8');
190+
} catch (error) {
191+
if (error instanceof Error && error.message.includes('ENOENT')) {
192+
throw createError(
193+
ErrorCode.FileReadError,
194+
`Attachment file not found: ${filename}`,
195+
{ originalError: error }
196+
);
197+
}
198+
throw createError(
199+
ErrorCode.FileReadError,
200+
`Failed to read attachment file: ${filename}`,
201+
{ originalError: error }
202+
);
203+
}
204+
}
179205
}

src/server/TaskManager.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -132,10 +132,22 @@ export class TaskManager {
132132
}): Promise<StandardResponse<ProjectCreationSuccessData>> {
133133
await this.ensureInitialized();
134134

135+
// Read all attachment files
136+
const attachmentContents: string[] = [];
137+
for (const filename of attachments) {
138+
try {
139+
const content = await this.fileSystemService.readAttachmentFile(filename);
140+
attachmentContents.push(content);
141+
} catch (error) {
142+
// Propagate file read errors
143+
throw error;
144+
}
145+
}
146+
135147
// Wrap prompt and attachments in XML tags
136148
let llmPrompt = `<prompt>${prompt}</prompt>`;
137-
for (const att of attachments) {
138-
llmPrompt += `\n<attachment>${att}</attachment>`;
149+
for (const content of attachmentContents) {
150+
llmPrompt += `\n<attachment>${content}</attachment>`;
139151
}
140152

141153
// Import and configure the appropriate provider

tests/integration/cli.integration.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ describe("CLI Integration Tests", () => {
1212
let tasksFilePath: string;
1313

1414
beforeEach(async () => {
15-
tempDir = path.join(os.tmpdir(), `task-manager-cli-test-${Date.now()}`);
15+
tempDir = path.join(os.tmpdir(), `taskqueue-test-${Date.now()}`);
1616
await fs.mkdir(tempDir, { recursive: true });
1717
tasksFilePath = path.join(tempDir, "test-tasks.json");
1818
process.env.TASK_MANAGER_FILE_PATH = tasksFilePath;
@@ -196,7 +196,7 @@ describe("CLI Integration Tests", () => {
196196
const { stderr } = await execAsync(
197197
`TASK_MANAGER_FILE_PATH=${tasksFilePath} tsx ${CLI_PATH} generate-plan --prompt "Create app" --attachment nonexistent.txt`
198198
).catch(error => error);
199-
199+
200200
// Just verify we get a warning about the attachment
201201
expect(stderr).toContain("Warning:");
202202
expect(stderr).toContain("nonexistent.txt");

tests/integration/e2e.integration.test.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ import * as path from 'node:path';
55
import * as os from 'node:os';
66
import * as fs from 'node:fs/promises';
77
import process from 'node:process';
8+
import dotenv from 'dotenv';
9+
10+
// Load environment variables from .env file
11+
dotenv.config();
812

913
interface ToolResponse {
1014
isError: boolean;
@@ -285,4 +289,60 @@ describe('MCP Client Integration', () => {
285289
expect(finalizeResult.isError).toBeFalsy();
286290
console.log('Project was successfully finalized after explicit task approval');
287291
});
292+
293+
// Skip by default as it requires OpenAI API key
294+
it.skip('should generate a project plan using OpenAI', async () => {
295+
console.log('Testing project plan generation...');
296+
297+
// Skip if no OpenAI API key is set
298+
const openaiApiKey = process.env.OPENAI_API_KEY;
299+
if (!openaiApiKey) {
300+
console.log('Skipping test: OPENAI_API_KEY not set');
301+
return;
302+
}
303+
304+
// Create a temporary requirements file
305+
const requirementsPath = path.join(tempDir, 'requirements.md');
306+
const requirements = `# TODO App Requirements
307+
308+
- Use React for the frontend
309+
- Include add, delete, and mark complete functionality
310+
- Store todos in local storage
311+
- Add basic styling`;
312+
313+
await fs.writeFile(requirementsPath, requirements, 'utf-8');
314+
console.log('Created temporary requirements file:', requirementsPath);
315+
316+
// Test prompt and context
317+
const testPrompt = "Create a step-by-step project plan to build a simple TODO app with React";
318+
319+
// Generate project plan
320+
const generateResult = await client.callTool({
321+
name: "generate_project_plan",
322+
arguments: {
323+
prompt: testPrompt,
324+
provider: "openai",
325+
model: "gpt-4-turbo",
326+
attachments: [requirementsPath]
327+
}
328+
}) as ToolResponse;
329+
330+
expect(generateResult.isError).toBeFalsy();
331+
const planData = JSON.parse((generateResult.content[0] as { text: string }).text);
332+
333+
// Verify the generated plan structure
334+
expect(planData).toHaveProperty('data');
335+
expect(planData.data).toHaveProperty('projectPlan');
336+
expect(planData.data).toHaveProperty('tasks');
337+
expect(Array.isArray(planData.data.tasks)).toBe(true);
338+
expect(planData.data.tasks.length).toBeGreaterThan(0);
339+
340+
// Verify task structure
341+
const firstTask = planData.data.tasks[0];
342+
expect(firstTask).toHaveProperty('title');
343+
expect(firstTask).toHaveProperty('description');
344+
345+
// The temporary file will be cleaned up by the afterAll hook that removes tempDir
346+
console.log('Successfully generated project plan with tasks');
347+
});
288348
});

0 commit comments

Comments
 (0)