diff --git a/README.md b/README.md index 7d3fa35..c751d63 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ Usually you will set the tool configuration in Claude Desktop, Cursor, or anothe To use the CLI utility, you can use the following command: ```bash -npx task-manager-cli --help +npx taskqueue --help ``` This will show the available commands and options. @@ -134,7 +134,7 @@ A typical workflow for an LLM using this task manager would be: Task approval is controlled exclusively by the human user through the CLI command: ```bash -npx task-manager-cli approve-task -- +npx taskqueue approve-task -- ``` Options: @@ -147,13 +147,13 @@ Note: Tasks must be marked as "done" with completed details before they can be a The CLI provides a command to list all projects and tasks: ```bash -npx task-manager-cli list-tasks +npx taskqueue list-tasks ``` To view details of a specific project: ```bash -npx task-manager-cli list-tasks -- -p +npx taskqueue list-tasks -- -p ``` This command displays information about all projects in the system or a specific project, including: diff --git a/index.ts b/index.ts index 507362d..22d2748 100644 --- a/index.ts +++ b/index.ts @@ -10,7 +10,7 @@ import { ListToolsRequestSchema, CallToolRequestSchema } from "@modelcontextprot const server = new Server( { name: "task-manager-server", - version: "1.2.0" + version: "1.3.0" }, { capabilities: { diff --git a/package-lock.json b/package-lock.json index 9ff3eb6..cfb01f9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,39 +1,40 @@ { "name": "taskqueue-mcp", - "version": "1.2.0", + "version": "1.3.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "taskqueue-mcp", - "version": "1.2.0", + "version": "1.3.0", "license": "MIT", "dependencies": { "@ai-sdk/deepseek": "^0.2.2", "@ai-sdk/google": "^1.2.3", - "@ai-sdk/openai": "^1.3.3", - "@modelcontextprotocol/sdk": "^1.7.0", - "ai": "^4.2.6", - "chalk": "^5.3.0", - "commander": "^11.0.0", - "glob": "^10.3.10", - "zod": "^3.22.4", - "zod-to-json-schema": "^3.23.5" + "@ai-sdk/openai": "^1.3.4", + "@modelcontextprotocol/sdk": "^1.8.0", + "ai": "^4.2.8", + "chalk": "^5.4.1", + "cli-table3": "^0.6.5", + "commander": "^13.1.0", + "glob": "^11.0.1", + "zod": "^3.24.2", + "zod-to-json-schema": "^3.24.5" }, "bin": { - "task-manager-cli": "dist/src/client/index.js", + "taskqueue": "dist/src/client/index.js", "taskqueue-mcp": "dist/index.js" }, "devDependencies": { "@jest/globals": "^29.7.0", - "@types/jest": "^29.5.12", + "@types/jest": "^29.5.14", "@types/json-schema": "^7.0.15", - "@types/node": "^20.11.0", + "@types/node": "^22.13.14", "dotenv": "^16.4.7", "jest": "^29.7.0", - "shx": "^0.3.4", - "ts-jest": "^29.1.2", - "typescript": "^5.3.3" + "shx": "^0.4.0", + "ts-jest": "^29.3.0", + "typescript": "^5.8.2" } }, "node_modules/@ai-sdk/deepseek": { @@ -70,9 +71,9 @@ } }, "node_modules/@ai-sdk/openai": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@ai-sdk/openai/-/openai-1.3.3.tgz", - "integrity": "sha512-CH57tonLB4DwkwqwnMmTCoIOR7cNW3bP5ciyloI7rBGJS/Bolemsoo+vn5YnwkyT9O1diWJyvYeTh7A4UfiYOw==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@ai-sdk/openai/-/openai-1.3.4.tgz", + "integrity": "sha512-BOw7dQpiTlpaqi1u/NU4Or2+jA6buzl6GOUuYyu/uFI7dxJs1zPkY8IjAp4DQhi+kQGH6GGbEPw0LkIbeK4BVA==", "license": "Apache-2.0", "dependencies": { "@ai-sdk/provider": "1.1.0", @@ -131,13 +132,13 @@ } }, "node_modules/@ai-sdk/react": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@ai-sdk/react/-/react-1.2.2.tgz", - "integrity": "sha512-rxyNTFjUd3IilVOJFuUJV5ytZBYAIyRi50kFS2gNmSEiG4NHMBBm31ddrxI/i86VpY8gzZVp1/igtljnWBihUA==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@ai-sdk/react/-/react-1.2.3.tgz", + "integrity": "sha512-EQ6nmmQBBAal1yg72GB/Q7QnmDXMfgYvCo9Gym2mESXUHTqwpXU0JFHtk5Kq3EEkk7CVMf1oBWlNFNvU5ckQBg==", "license": "Apache-2.0", "dependencies": { "@ai-sdk/provider-utils": "2.2.1", - "@ai-sdk/ui-utils": "1.2.1", + "@ai-sdk/ui-utils": "1.2.2", "swr": "^2.2.5", "throttleit": "2.1.0" }, @@ -155,9 +156,9 @@ } }, "node_modules/@ai-sdk/ui-utils": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@ai-sdk/ui-utils/-/ui-utils-1.2.1.tgz", - "integrity": "sha512-BzvMbYm7LHBlbWuLlcG1jQh4eu14MGpz7L+wrGO1+F4oQ+O0fAjgUSNwPWGlZpKmg4NrcVq/QLmxiVJrx2R4Ew==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@ai-sdk/ui-utils/-/ui-utils-1.2.2.tgz", + "integrity": "sha512-6rCx2jSEPuiF6fytfMNscSOinHQZp52aFCHyPVpPPkcWnOur1jPWhol+0TFCUruDl7dCfcSIfTexQUq2ioLwaA==", "license": "Apache-2.0", "dependencies": { "@ai-sdk/provider": "1.1.0", @@ -242,14 +243,14 @@ } }, "node_modules/@babel/generator": { - "version": "7.26.10", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.10.tgz", - "integrity": "sha512-rRHT8siFIXQrAYOYqZQVsAr8vJ+cBNqcVAY6m5V8/4QqzaPl+zDBe6cLEPRDuNOUf3ww8RfJVlOyQMoSI+5Ang==", + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.0.tgz", + "integrity": "sha512-VybsKvpiN1gU1sdMZIp7FcqphVVKEwcuj02x73uvcHE0PTihx1nlBcowYWhDwjpoAXRv43+gDzyggGnn1XZhVw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.26.10", - "@babel/types": "^7.26.10", + "@babel/parser": "^7.27.0", + "@babel/types": "^7.27.0", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^3.0.2" @@ -259,13 +260,13 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.26.5", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.26.5.tgz", - "integrity": "sha512-IXuyn5EkouFJscIDuFF5EsiSolseme1s0CZB+QxVugqJLYmKdxI1VfIBOst0SUu4rnk2Z7kqTwmoO1lp3HIfnA==", + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.0.tgz", + "integrity": "sha512-LVk7fbXml0H2xH34dFzKQ7TDZ2G4/rVTOrq9V+icbbadjbVxxeFeDsNHv2SrZeWoA+6ZiTyWYWtScEIW07EAcA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.26.5", + "@babel/compat-data": "^7.26.8", "@babel/helper-validator-option": "^7.25.9", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", @@ -275,16 +276,6 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^3.0.2" - } - }, "node_modules/@babel/helper-module-imports": { "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", @@ -358,27 +349,27 @@ } }, "node_modules/@babel/helpers": { - "version": "7.26.10", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.10.tgz", - "integrity": "sha512-UPYc3SauzZ3JGgj87GgZ89JVdC5dj0AoetR5Bw6wj4niittNyFh6+eOGonYvJ1ao6B8lEa3Q3klS7ADZ53bc5g==", + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.0.tgz", + "integrity": "sha512-U5eyP/CTFPuNE3qk+WZMxFkp/4zUzdceQlfzf7DdGdhp+Fezd7HD+i8Y24ZuTMKX3wQBld449jijbGq6OdGNQg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/template": "^7.26.9", - "@babel/types": "^7.26.10" + "@babel/template": "^7.27.0", + "@babel/types": "^7.27.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.26.10", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.10.tgz", - "integrity": "sha512-6aQR2zGE/QFi8JpDLjUZEPYOs7+mhKXm86VaKFiLP35JQwQb6bwUE+XbvkH0EptsYhbNBSUGaUBLKqxH1xSgsA==", + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.0.tgz", + "integrity": "sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.26.10" + "@babel/types": "^7.27.0" }, "bin": { "parser": "bin/babel-parser.js" @@ -627,32 +618,32 @@ } }, "node_modules/@babel/template": { - "version": "7.26.9", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.26.9.tgz", - "integrity": "sha512-qyRplbeIpNZhmzOysF/wFMuP9sctmh2cFzRAZOn1YapxBsE1i9bJIY586R/WBLfLcmcBlM8ROBiQURnnNy+zfA==", + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.0.tgz", + "integrity": "sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA==", "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.26.2", - "@babel/parser": "^7.26.9", - "@babel/types": "^7.26.9" + "@babel/parser": "^7.27.0", + "@babel/types": "^7.27.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.26.10", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.26.10.tgz", - "integrity": "sha512-k8NuDrxr0WrPH5Aupqb2LCVURP/S0vBEn5mK6iH+GIYob66U5EtoZvcdudR2jQ4cmTwhEwW1DLB+Yyas9zjF6A==", + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.0.tgz", + "integrity": "sha512-19lYZFzYVQkkHkl4Cy4WrAVcqBkgvV2YM2TU3xG6DIwO7O3ecbDPfW3yM3bjAGcqcQHi+CCtjMR3dIEHxsd6bA==", "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.26.2", - "@babel/generator": "^7.26.10", - "@babel/parser": "^7.26.10", - "@babel/template": "^7.26.9", - "@babel/types": "^7.26.10", + "@babel/generator": "^7.27.0", + "@babel/parser": "^7.27.0", + "@babel/template": "^7.27.0", + "@babel/types": "^7.27.0", "debug": "^4.3.1", "globals": "^11.1.0" }, @@ -661,9 +652,9 @@ } }, "node_modules/@babel/types": { - "version": "7.26.10", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.10.tgz", - "integrity": "sha512-emqcG3vHrpxUKTrxcblR36dcrcoRDvKmnL/dCL6ZsHaShW80qxCAcNhzQZrpeM765VzEos+xOi4s+r4IXzTwdQ==", + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.0.tgz", + "integrity": "sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==", "dev": true, "license": "MIT", "dependencies": { @@ -681,6 +672,16 @@ "dev": true, "license": "MIT" }, + "node_modules/@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.1.90" + } + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -698,6 +699,29 @@ "node": ">=12" } }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "license": "MIT" + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -1328,13 +1352,14 @@ } }, "node_modules/@modelcontextprotocol/sdk": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.7.0.tgz", - "integrity": "sha512-IYPe/FLpvF3IZrd/f5p5ffmWhMc3aEMuM2wGJASDqC2Ge7qatVCdbfPx3n/5xFeb19xN0j/911M2AaFuircsWA==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.8.0.tgz", + "integrity": "sha512-e06W7SwrontJDHwCawNO5SGxG+nU9AAx+jpHHZqGl/WrDBdWOpvirC+s58VpJTB5QemI4jTRcjWT4Pt3Q1NPQQ==", "license": "MIT", "dependencies": { "content-type": "^1.0.5", "cors": "^2.8.5", + "cross-spawn": "^7.0.3", "eventsource": "^3.0.2", "express": "^5.0.1", "express-rate-limit": "^7.5.0", @@ -1347,6 +1372,44 @@ "node": ">=18" } }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/@opentelemetry/api": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", @@ -1356,16 +1419,6 @@ "node": ">=8.0.0" } }, - "node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=14" - } - }, "node_modules/@sinclair/typebox": { "version": "0.27.8", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", @@ -1429,9 +1482,9 @@ } }, "node_modules/@types/babel__traverse": { - "version": "7.20.6", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.6.tgz", - "integrity": "sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==", + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.7.tgz", + "integrity": "sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==", "dev": true, "license": "MIT", "dependencies": { @@ -1500,13 +1553,13 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "20.17.24", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.24.tgz", - "integrity": "sha512-d7fGCyB96w9BnWQrOsJtpyiSaBcAYYr75bnK6ZRjDbql2cGLj/3GsL5OYmLPNq76l7Gf2q4Rv9J2o6h5CrD9sA==", + "version": "22.13.14", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.14.tgz", + "integrity": "sha512-Zs/Ollc1SJ8nKUAgc7ivOEdIBM8JAKgrqqUYi2J997JuKO7/tpQC+WCetQ1sypiKCQWHdvdg9wBNpUPEWZae7w==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": "~6.19.2" + "undici-types": "~6.20.0" } }, "node_modules/@types/stack-utils": { @@ -1547,15 +1600,15 @@ } }, "node_modules/ai": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/ai/-/ai-4.2.6.tgz", - "integrity": "sha512-vw0tGCUvnmOmzFm4rZI0o+sKx3Lcp7Fo5MrK3T+0ZVws/6+3CtB9ANmaC7DhJfdZFYm+wJuWMylsSEiJMjhJZQ==", + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/ai/-/ai-4.2.8.tgz", + "integrity": "sha512-0gwfPZAuuQ+uTfk/GssrfnNTYxliCFKojbSQoEhzpbpSVaPao9NoU3iuE8vwBjWuDKqILRGzYGFE4+vTak0Oxg==", "license": "Apache-2.0", "dependencies": { "@ai-sdk/provider": "1.1.0", "@ai-sdk/provider-utils": "2.2.1", - "@ai-sdk/react": "1.2.2", - "@ai-sdk/ui-utils": "1.2.1", + "@ai-sdk/react": "1.2.3", + "@ai-sdk/ui-utils": "1.2.2", "@opentelemetry/api": "1.9.0", "jsondiffpatch": "0.6.0" }, @@ -1601,12 +1654,13 @@ } }, "node_modules/ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, "license": "MIT", "engines": { - "node": ">=12" + "node": ">=10" }, "funding": { "url": "https://github.com/chalk/ansi-styles?sponsor=1" @@ -1799,16 +1853,16 @@ "license": "MIT" }, "node_modules/body-parser": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.1.0.tgz", - "integrity": "sha512-/hPxh61E+ll0Ujp24Ilm64cykicul1ypfwjVttduAiEdtnJFvLePSrIPk+HMImtNv5270wOGCb1Tns2rybMkoQ==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", + "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", "license": "MIT", "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", "debug": "^4.4.0", "http-errors": "^2.0.0", - "iconv-lite": "^0.5.2", + "iconv-lite": "^0.6.3", "on-finished": "^2.4.1", "qs": "^6.14.0", "raw-body": "^3.0.0", @@ -1818,18 +1872,29 @@ "node": ">=18" } }, - "node_modules/body-parser/node_modules/iconv-lite": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.5.2.tgz", - "integrity": "sha512-kERHXvpSaB4aU3eANwidg79K8FlrN77m8G9V+0vOR3HYaRifrlwMEpT7ZBJqLSEIHnEgJTHcWK82wwLwwKwtag==", + "node_modules/body-parser/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", "license": "MIT", "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" + "ms": "^2.1.3" }, "engines": { - "node": ">=0.10.0" + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, "node_modules/body-parser/node_modules/qs": { "version": "6.14.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", @@ -1989,9 +2054,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001705", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001705.tgz", - "integrity": "sha512-S0uyMMiYvA7CxNgomYBwwwPUnWzFD83f3B1ce5jHUfHTH//QL6hHsreI8RVC5606R4ssqravelYO5TU6t8sEyg==", + "version": "1.0.30001707", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001707.tgz", + "integrity": "sha512-3qtRjw/HQSMlDWf+X79N206fepf4SOOU6SQLMaq/0KkZLmSjPxAkBOQQ+FxbHKfHmYLZFfdWsO3KA90ceHPSnw==", "dev": true, "funding": [ { @@ -2054,6 +2119,21 @@ "dev": true, "license": "MIT" }, + "node_modules/cli-table3": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.5.tgz", + "integrity": "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==", + "license": "MIT", + "dependencies": { + "string-width": "^4.2.0" + }, + "engines": { + "node": "10.* || >= 12.*" + }, + "optionalDependencies": { + "@colors/colors": "1.5.0" + } + }, "node_modules/cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", @@ -2095,28 +2175,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/cliui/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/cliui/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/cliui/node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -2185,12 +2243,12 @@ "license": "MIT" }, "node_modules/commander": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", - "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-13.1.0.tgz", + "integrity": "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==", "license": "MIT", "engines": { - "node": ">=16" + "node": ">=18" } }, "node_modules/concat-map": { @@ -2329,12 +2387,12 @@ } }, "node_modules/debug": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", - "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", + "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", "license": "MIT", "dependencies": { - "ms": "^2.1.3" + "ms": "2.1.2" }, "engines": { "node": ">=6.0" @@ -2388,16 +2446,6 @@ "node": ">=6" } }, - "node_modules/destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", - "license": "MIT", - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, "node_modules/detect-newline": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", @@ -2480,9 +2528,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.120", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.120.tgz", - "integrity": "sha512-oTUp3gfX1gZI+xfD2djr2rzQdHCwHzPQrrK0CD7WpTdF0nPdQ/INcRVjWgLdCT4a9W3jFObR9DAfsuyFQnI8CQ==", + "version": "1.5.128", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.128.tgz", + "integrity": "sha512-bo1A4HH/NS522Ws0QNFIzyPcyUUNV/yyy70Ho1xqfGYzPUme2F/xr4tlEOuM6/A538U1vDA7a4XfCd1CKRegKQ==", "dev": true, "license": "ISC" }, @@ -2500,9 +2548,9 @@ } }, "node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "license": "MIT" }, "node_modules/encodeurl": { @@ -2514,6 +2562,16 @@ "node": ">= 0.8" } }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, "node_modules/error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -2604,21 +2662,21 @@ } }, "node_modules/eventsource": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.5.tgz", - "integrity": "sha512-LT/5J605bx5SNyE+ITBDiM3FxffBiq9un7Vx0EwMDM3vg8sWKx/tO2zC+LMqZ+smAM0F2hblaDZUVZF0te2pSw==", + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.6.tgz", + "integrity": "sha512-l19WpE2m9hSuyP06+FbuUUf1G+R0SFLrtQfbRb9PRr+oimOfxQhgGCbVaXg5IvZyyTThJsxh6L/srkMiCeBPDA==", "license": "MIT", "dependencies": { - "eventsource-parser": "^3.0.0" + "eventsource-parser": "^3.0.1" }, "engines": { "node": ">=18.0.0" } }, "node_modules/eventsource-parser": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.0.tgz", - "integrity": "sha512-T1C0XCUimhxVQzW4zFipdx0SficT651NnkR0ZSH3yQwh+mFMdLfgjABVi4YtMTtaL4s168593DaoaRLMqryavA==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.1.tgz", + "integrity": "sha512-VARTJ9CYeuQYb0pZEPbzi740OWFgpHe7AYJ2WFZVnUDUQp5Dk2yJUgF36YsZ81cOyxT0QxmXD2EQpapAouzWVA==", "license": "MIT", "engines": { "node": ">=18.0.0" @@ -2739,29 +2797,23 @@ "express": "^4.11 || 5 || ^5.0.0-beta.1" } }, - "node_modules/express/node_modules/debug": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", - "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, "license": "MIT", "dependencies": { - "ms": "2.1.2" + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" }, "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "node": ">=8.6.0" } }, - "node_modules/express/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "license": "MIT" - }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", @@ -2769,6 +2821,16 @@ "dev": true, "license": "MIT" }, + "node_modules/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, "node_modules/fb-watchman": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", @@ -2832,6 +2894,29 @@ "node": ">= 0.8" } }, + "node_modules/finalhandler/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, "node_modules/find-up": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", @@ -2992,25 +3077,41 @@ } }, "node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.1.tgz", + "integrity": "sha512-zrQDm8XPnYEKawJScsnM0QzobJxlT/kHOOlRTio8IH/GrmxRE5fjllkzdaHclIuNjUQTJYH2xHNIGfdpJkDJUw==", "license": "ISC", "dependencies": { "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", + "jackspeak": "^4.0.1", + "minimatch": "^10.0.0", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" + "path-scurry": "^2.0.0" }, "bin": { "glob": "dist/esm/bin.mjs" }, + "engines": { + "node": "20 || >=22" + }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/globals": { "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", @@ -3209,6 +3310,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", @@ -3228,6 +3339,19 @@ "node": ">=6" } }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -3348,18 +3472,18 @@ } }, "node_modules/jackspeak": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.1.0.tgz", + "integrity": "sha512-9DDdhb5j6cpeitCbvLO7n7J4IxnbM6hoF6O1g4HQ5TfhvvKN8ywDM7668ZhMHRqVmxqhps/F6syWK2KcPxYlkw==", "license": "BlueOak-1.0.0", "dependencies": { "@isaacs/cliui": "^8.0.2" }, + "engines": { + "node": "20 || >=22" + }, "funding": { "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" } }, "node_modules/jake": { @@ -4713,10 +4837,14 @@ "license": "MIT" }, "node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "license": "ISC" + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } }, "node_modules/make-dir": { "version": "4.0.0", @@ -4801,8 +4929,18 @@ "dev": true, "license": "MIT" }, - "node_modules/methods": { - "version": "1.1.2", + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/methods": { + "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", "license": "MIT", @@ -4834,12 +4972,12 @@ } }, "node_modules/mime-types": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.0.tgz", - "integrity": "sha512-XqoSHeCGjVClAmoGFG3lVFqQFRIrTVw2OH3axRqAcfaw+gHWIfnASS92AV+Rl/mk0MupgZTRHQOjxY6YVnzK5w==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", "license": "MIT", "dependencies": { - "mime-db": "^1.53.0" + "mime-db": "^1.54.0" }, "engines": { "node": ">= 0.6" @@ -4856,15 +4994,15 @@ } }, "node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz", + "integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==", "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -4890,9 +5028,9 @@ } }, "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "license": "MIT" }, "node_modules/nanoid": { @@ -4929,6 +5067,13 @@ "node": ">= 0.6" } }, + "node_modules/nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true, + "license": "MIT" + }, "node_modules/node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -5024,6 +5169,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -5150,21 +5305,30 @@ "license": "MIT" }, "node_modules/path-scurry": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", - "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", + "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", "license": "BlueOak-1.0.0", "dependencies": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" }, "engines": { - "node": ">=16 || 14 >=14.18" + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.1.0.tgz", + "integrity": "sha512-QIXZUBJUx+2zHUdQujWejBkcD9+cs94tLn0+YL8UrCh+D5sCXZ4c7LaEH48pNwRY3MLDgqUFyhlCyjJPf1WP0A==", + "license": "ISC", + "engines": { + "node": "20 || >=22" + } + }, "node_modules/path-to-regexp": { "version": "8.2.0", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz", @@ -5195,9 +5359,9 @@ } }, "node_modules/pirates": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", - "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", "dev": true, "license": "MIT", "engines": { @@ -5241,19 +5405,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/prompts": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", @@ -5281,6 +5432,17 @@ "node": ">= 0.10" } }, + "node_modules/pump": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz", + "integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==", + "dev": true, + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, "node_modules/pure-rand": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", @@ -5313,6 +5475,27 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -5338,9 +5521,9 @@ } }, "node_modules/react": { - "version": "19.0.0", - "resolved": "https://registry.npmjs.org/react/-/react-19.0.0.tgz", - "integrity": "sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==", + "version": "19.1.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", + "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==", "license": "MIT", "peer": true, "engines": { @@ -5430,12 +5613,25 @@ "node": ">=10" } }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, "node_modules/router": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/router/-/router-2.1.0.tgz", - "integrity": "sha512-/m/NSLxeYEgWNtyC+WtNHCF7jbGxOibVWKnn+1Psff4dJGOfoXP+MuC/f2CwSmyiHdOIzYnYFp4W6GxWfekaLA==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", "license": "MIT", "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", "is-promise": "^4.0.0", "parseurl": "^1.3.3", "path-to-regexp": "^8.0.0" @@ -5444,6 +5640,53 @@ "node": ">= 18" } }, + "node_modules/router/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/router/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -5487,19 +5730,18 @@ } }, "node_modules/send": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/send/-/send-1.1.0.tgz", - "integrity": "sha512-v67WcEouB5GxbTWL/4NeToqcZiAWEq90N888fczVArY8A79J0L4FD7vj5hm3eUMua5EpoQ59wa/oovY6TLvRUA==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", + "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", "license": "MIT", "dependencies": { "debug": "^4.3.5", - "destroy": "^1.2.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", - "fresh": "^0.5.2", + "fresh": "^2.0.0", "http-errors": "^2.0.0", - "mime-types": "^2.1.35", + "mime-types": "^3.0.1", "ms": "^2.1.3", "on-finished": "^2.4.1", "range-parser": "^1.2.1", @@ -5509,46 +5751,22 @@ "node": ">= 18" } }, - "node_modules/send/node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/send/node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/send/node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" }, "node_modules/serve-static": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.1.0.tgz", - "integrity": "sha512-A3We5UfEjG8Z7VkDv6uItWw6HY2bBSBJT1KtVESn6EOoOr2jAxNhxWCLY3jDE2WcuHXByWju74ck3ZgLwL8xmA==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", "license": "MIT", "dependencies": { "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "parseurl": "^1.3.3", - "send": "^1.0.0" + "send": "^1.2.0" }, "engines": { "node": ">= 18" @@ -5582,13 +5800,14 @@ } }, "node_modules/shelljs": { - "version": "0.8.5", - "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.5.tgz", - "integrity": "sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==", + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.9.2.tgz", + "integrity": "sha512-S3I64fEiKgTZzKCC46zT/Ib9meqofLrQVbpSswtjFfAVDW+AZ54WTnAM/3/yENoxz/V1Cy6u3kiiEbQ4DNphvw==", "dev": true, "license": "BSD-3-Clause", "dependencies": { - "glob": "^7.0.0", + "execa": "^1.0.0", + "fast-glob": "^3.3.2", "interpret": "^1.0.0", "rechoir": "^0.6.2" }, @@ -5596,70 +5815,159 @@ "shjs": "bin/shjs" }, "engines": { - "node": ">=4" + "node": ">=18" } }, - "node_modules/shelljs/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "node_modules/shelljs/node_modules/cross-spawn": { + "version": "6.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.6.tgz", + "integrity": "sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw==", "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + }, + "engines": { + "node": ">=4.8" } }, - "node_modules/shelljs/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", + "node_modules/shelljs/node_modules/execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" }, "engines": { - "node": "*" + "node": ">=6" + } + }, + "node_modules/shelljs/node_modules/get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "pump": "^3.0.0" }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "engines": { + "node": ">=6" } }, - "node_modules/shelljs/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "node_modules/shelljs/node_modules/is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/shelljs/node_modules/npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/shelljs/node_modules/path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/shelljs/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", "dev": true, "license": "ISC", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/shelljs/node_modules/shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", + "dev": true, + "license": "MIT", "dependencies": { - "brace-expansion": "^1.1.7" + "shebang-regex": "^1.0.0" }, "engines": { - "node": "*" + "node": ">=0.10.0" + } + }, + "node_modules/shelljs/node_modules/shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/shelljs/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/shelljs/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" } }, "node_modules/shx": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/shx/-/shx-0.3.4.tgz", - "integrity": "sha512-N6A9MLVqjxZYcVn8hLmtneQWIJtp8IKzMP4eMnx+nqkvXoqinUPCbUFLp2UcWTEIUONhlk0ewxr/jaVGlc+J+g==", + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/shx/-/shx-0.4.0.tgz", + "integrity": "sha512-Z0KixSIlGPpijKgcH6oCMCbltPImvaKy0sGH8AkLRXw1KyzpKtaCTizP2xen+hNDqVF4xxgvA0KXSb9o4Q6hnA==", "dev": true, "license": "MIT", "dependencies": { - "minimist": "^1.2.3", - "shelljs": "^0.8.5" + "minimist": "^1.2.8", + "shelljs": "^0.9.2" }, "bin": { "shx": "lib/cli.js" }, "engines": { - "node": ">=6" + "node": ">=18" } }, "node_modules/side-channel": { @@ -5851,20 +6159,17 @@ } }, "node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "license": "MIT", "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" }, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=8" } }, "node_modules/string-width-cjs": { @@ -5891,12 +6196,6 @@ "node": ">=8" } }, - "node_modules/string-width-cjs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "license": "MIT" - }, "node_modules/string-width-cjs/node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -5909,6 +6208,27 @@ "node": ">=8" } }, + "node_modules/string-width/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-ansi": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", @@ -5956,6 +6276,16 @@ "node": ">=8" } }, + "node_modules/strip-eof": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/strip-final-newline": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", @@ -6121,9 +6451,9 @@ } }, "node_modules/ts-jest": { - "version": "29.2.6", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.2.6.tgz", - "integrity": "sha512-yTNZVZqc8lSixm+QGVFcPe6+yj7+TWZwIesuOWvfcn4B9bz5x4NDzVCQQjOs7Hfouu36aEqfEbo9Qpo+gq8dDg==", + "version": "29.3.0", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.3.0.tgz", + "integrity": "sha512-4bfGBX7Gd1Aqz3SyeDS9O276wEU/BInZxskPrbhZLyv+c1wskDCqDFMJQJLWrIr/fKoAH4GE5dKUlrdyvo+39A==", "dev": true, "license": "MIT", "dependencies": { @@ -6135,6 +6465,7 @@ "lodash.memoize": "^4.1.2", "make-error": "^1.3.6", "semver": "^7.7.1", + "type-fest": "^4.37.0", "yargs-parser": "^21.1.1" }, "bin": { @@ -6182,6 +6513,19 @@ "node": ">=10" } }, + "node_modules/ts-jest/node_modules/type-fest": { + "version": "4.38.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.38.0.tgz", + "integrity": "sha512-2dBz5D5ycHIoliLYLi0Q2V7KRaDlH0uWIvmk7TYlAg5slqwiPv1ezJdZm1QEM0xgk29oYWMCbIG7E6gHpvChlg==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/type-detect": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", @@ -6206,9 +6550,9 @@ } }, "node_modules/type-is": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.0.tgz", - "integrity": "sha512-gd0sGezQYCbWSbkZr75mln4YBidWUN60+devscpLF5mtRDUpiaTvKpBNrdaCvel1NdR2k6vclXybU5fBd2i+nw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", "license": "MIT", "dependencies": { "content-type": "^1.0.5", @@ -6234,9 +6578,9 @@ } }, "node_modules/undici-types": { - "version": "6.19.8", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", - "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", "dev": true, "license": "MIT" }, @@ -6281,9 +6625,9 @@ } }, "node_modules/use-sync-external-store": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.4.0.tgz", - "integrity": "sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz", + "integrity": "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==", "license": "MIT", "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" @@ -6406,36 +6750,51 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "license": "MIT" - }, - "node_modules/wrap-ansi-cjs/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "license": "MIT", "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" + "ansi-regex": "^5.0.1" }, "engines": { "node": ">=8" } }, - "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "license": "MIT" + }, + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", "license": "MIT", "dependencies": { - "ansi-regex": "^5.0.1" + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" }, "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/wrappy": { @@ -6511,51 +6870,6 @@ "node": ">=12" } }, - "node_modules/yargs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/yargs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/yargs/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/yargs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", @@ -6579,9 +6893,9 @@ } }, "node_modules/zod-to-json-schema": { - "version": "3.24.4", - "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.4.tgz", - "integrity": "sha512-0uNlcvgabyrni9Ag8Vghj21drk7+7tp7VTwwR7KxxXXc/3pbXz2PHlDgj3cICahgF1kHm4dExBFj7BXrZJXzig==", + "version": "3.24.5", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.5.tgz", + "integrity": "sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g==", "license": "ISC", "peerDependencies": { "zod": "^3.24.1" diff --git a/package.json b/package.json index d70720c..ff6a9c4 100644 --- a/package.json +++ b/package.json @@ -1,13 +1,13 @@ { "name": "taskqueue-mcp", - "version": "1.2.0", + "version": "1.3.0", "description": "Task Queue MCP Server", "author": "Christopher C. Smith (christopher.smith@promptlytechnologies.com)", "main": "dist/index.js", "type": "module", "bin": { "taskqueue-mcp": "dist/index.js", - "task-manager-cli": "dist/src/client/index.js" + "taskqueue": "dist/src/client/index.js" }, "files": [ "dist/index.js", @@ -41,24 +41,25 @@ "dependencies": { "@ai-sdk/deepseek": "^0.2.2", "@ai-sdk/google": "^1.2.3", - "@ai-sdk/openai": "^1.3.3", - "@modelcontextprotocol/sdk": "^1.7.0", - "ai": "^4.2.6", - "chalk": "^5.3.0", - "commander": "^11.0.0", - "glob": "^10.3.10", - "zod": "^3.22.4", - "zod-to-json-schema": "^3.23.5" + "@ai-sdk/openai": "^1.3.4", + "@modelcontextprotocol/sdk": "^1.8.0", + "ai": "^4.2.8", + "chalk": "^5.4.1", + "cli-table3": "^0.6.5", + "commander": "^13.1.0", + "glob": "^11.0.1", + "zod": "^3.24.2", + "zod-to-json-schema": "^3.24.5" }, "devDependencies": { "@jest/globals": "^29.7.0", - "@types/jest": "^29.5.12", + "@types/jest": "^29.5.14", "@types/json-schema": "^7.0.15", - "@types/node": "^20.11.0", + "@types/node": "^22.13.14", "dotenv": "^16.4.7", "jest": "^29.7.0", - "shx": "^0.3.4", - "ts-jest": "^29.1.2", - "typescript": "^5.3.3" + "shx": "^0.4.0", + "ts-jest": "^29.3.0", + "typescript": "^5.8.2" } } diff --git a/src/client/cli.ts b/src/client/cli.ts index e97cf2c..14fa19d 100644 --- a/src/client/cli.ts +++ b/src/client/cli.ts @@ -9,14 +9,14 @@ import { import { TaskManager } from "../server/TaskManager.js"; import { createError, normalizeError } from "../utils/errors.js"; import { formatCliError } from "./errors.js"; -import fs from "fs/promises"; +import { formatProjectsList, formatTaskProgressTable } from "./taskFormattingUtils.js"; const program = new Command(); program - .name("task-manager-cli") + .name("taskqueue") .description("CLI for the Task Manager MCP Server") - .version("1.2.0") + .version("1.3.0") .option( '-f, --file-path ', 'Specify the path to the tasks JSON file. Overrides TASK_MANAGER_FILE_PATH env var.' @@ -29,7 +29,6 @@ program.hook('preAction', (thisCommand, actionCommand) => { const envFilePath = process.env.TASK_MANAGER_FILE_PATH; const resolvedPath = cliFilePath || envFilePath || undefined; - console.log(chalk.blue(`Using task file path determined by CLI/Env: ${resolvedPath || 'TaskManager Default'}`)); try { taskManager = new TaskManager(resolvedPath); } catch (error) { @@ -151,7 +150,7 @@ program if (completedTasks === totalTasks && approvedTasks === totalTasks) { console.log(chalk.green('\nšŸŽ‰ All tasks are completed and approved!')); - console.log(chalk.blue(`The project can now be finalized using: task-manager-cli finalize ${projectId}`)); + console.log(chalk.blue(`The project can now be finalized using: taskqueue finalize ${projectId}`)); } else { if (totalTasks - completedTasks > 0) { console.log(chalk.yellow(`\n${totalTasks - completedTasks} tasks remaining to be completed.`)); @@ -281,7 +280,7 @@ program console.log(chalk.green('\nšŸŽ‰ Project successfully completed and approved!')); console.log(chalk.gray('You can view the project details anytime using:')); - console.log(chalk.blue(` task-manager-cli list -p ${projectId}`)); + console.log(chalk.blue(` taskqueue list -p ${projectId}`)); } catch (error) { const normalized = normalizeError(error); @@ -307,8 +306,8 @@ program program .command("list") - .description("List all projects and their tasks") - .option('-p, --project ', 'Show details for a specific project') + .description("List project summaries, or list tasks for a specific project") + .option('-p, --project ', 'Show details and tasks for a specific project') .option('-s, --state ', "Filter by task/project state (open, pending_approval, completed, all)") .action(async (options) => { try { @@ -320,160 +319,79 @@ program console.log(chalk.yellow(`Valid states are: ${validStates.join(', ')}`)); process.exit(1); } - // Use 'undefined' if state is 'all' or not provided, as TaskManager methods expect TaskState or undefined const filterState = (stateOption === 'all' || !stateOption) ? undefined : stateOption as TaskState; if (options.project) { // Show details for a specific project const projectId = options.project; - - // Fetch project details for display first - let projectDetailsResponse; try { - projectDetailsResponse = await taskManager.readProject(projectId); - if ('error' in projectDetailsResponse) { - throw projectDetailsResponse.error; - } - if (projectDetailsResponse.status !== "success") { - throw createError(ErrorCode.InvalidResponseFormat, "Unexpected response format from TaskManager"); - } - const project = projectDetailsResponse.data; - - // Fetch tasks for this project, applying state filter - const tasksResponse = await taskManager.listTasks(projectId, filterState); - // Check for success before accessing data - const tasks = tasksResponse.status === 'success' ? tasksResponse.data.tasks : []; - - console.log(chalk.cyan(`\nšŸ“‹ Project ${chalk.bold(projectId)} details:`)); - console.log(` - ${chalk.bold('Initial Prompt:')} ${project.initialPrompt}`); - if (project.projectPlan && project.projectPlan !== project.initialPrompt) { - console.log(` - ${chalk.bold('Project Plan:')} ${project.projectPlan}`); - } - console.log(` - ${chalk.bold('Status:')} ${project.completed ? chalk.green('Completed āœ“') : chalk.yellow('In Progress')}`); - - // Show progress info (using data from readProject) - const totalTasks = project.tasks.length; - const completedTasks = project.tasks.filter((t: { status: string }) => t.status === "done").length; - const approvedTasks = project.tasks.filter((t: { approved: boolean }) => t.approved).length; - - console.log(chalk.cyan(`\nšŸ“Š Progress: ${chalk.bold(`${approvedTasks}/${completedTasks}/${totalTasks}`)} (approved/completed/total)`)); - - // Create a progress bar - if (totalTasks > 0) { - const bar = 'ā–“'.repeat(approvedTasks) + 'ā–’'.repeat(completedTasks - approvedTasks) + 'ā–‘'.repeat(totalTasks - completedTasks); - console.log(` ${bar}`); - } else { - console.log(chalk.yellow(' No tasks in this project yet.')); + const projectResponse = await taskManager.readProject(projectId); + if ('error' in projectResponse) throw projectResponse.error; + if (projectResponse.status !== "success") throw createError(ErrorCode.InvalidResponseFormat, "Unexpected response"); + + const project = projectResponse.data; + + // Filter tasks based on state if provided + const tasksToList = filterState + ? project.tasks.filter((task) => { + if (filterState === 'open') return task.status !== 'done'; + if (filterState === 'pending_approval') return task.status === 'done' && !task.approved; + if (filterState === 'completed') return task.status === 'done' && task.approved; + return true; // Should not happen + }) + : project.tasks; + + // Use the formatter for the progress table - it now includes the header + const projectForTableDisplay = { ...project, tasks: tasksToList }; + console.log(formatTaskProgressTable(projectForTableDisplay)); + + if (tasksToList.length === 0) { + console.log(chalk.yellow(`\nNo tasks found${filterState ? ` matching state '${filterState}'` : ''} in project ${projectId}.`)); + } else if (filterState) { + console.log(chalk.dim(`(Filtered by state: ${filterState})`)); } - if (tasks.length > 0) { - console.log(chalk.cyan('\nšŸ“ Tasks' + (filterState ? ` (filtered by state: ${filterState})` : '') + ':')); - tasks.forEach((t: { - id: string; - title: string; - status: string; - approved: boolean; - description: string; - completedDetails?: string; - toolRecommendations?: string; - ruleRecommendations?: string; - }) => { - const status = t.status === 'done' ? chalk.green('Done āœ“') : t.status === 'in progress' ? chalk.yellow('In Progress ⟳') : chalk.blue('Not Started ā—‹'); - const approved = t.approved ? chalk.green('Yes āœ“') : chalk.red('No āœ—'); - console.log(` - ${chalk.bold(t.id)}: ${t.title}`); - console.log(` Status: ${status}, Approved: ${approved}`); - console.log(` Description: ${t.description}`); - if (t.completedDetails) { - console.log(` Completed Details: ${t.completedDetails}`); - } - if (t.toolRecommendations) { - console.log(` Tool Recommendations: ${t.toolRecommendations}`); - } - if (t.ruleRecommendations) { - console.log(` Rule Recommendations: ${t.ruleRecommendations}`); - } - }); - } else { - console.log(chalk.yellow(`\nNo tasks found${filterState ? ` matching state '${filterState}'` : ''} in project ${projectId}.`)); - } } catch (error: unknown) { - if (error instanceof Error) { - console.error(chalk.red(`Error fetching details for project ${projectId}: ${error.message}`)); - } else { - console.error(chalk.red(`Error fetching details for project ${projectId}: Unknown error`)); - } - // Handle ProjectNotFound specifically if desired, otherwise let generic handler catch - const normalized = normalizeError(error); + const normalized = normalizeError(error); if (normalized.code === ErrorCode.ProjectNotFound) { console.error(chalk.red(`Project ${chalk.bold(projectId)} not found.`)); // Optionally list available projects - const projectsResponse = await taskManager.listProjects(); - if ('error' in projectsResponse) { - throw projectsResponse.error; - } + const projectsResponse = await taskManager.listProjects(); // Fetch summaries if (projectsResponse.status === "success" && projectsResponse.data.projects.length > 0) { console.log(chalk.yellow('Available projects:')); projectsResponse.data.projects.forEach((p: { projectId: string; initialPrompt: string }) => { console.log(` - ${p.projectId}: ${p.initialPrompt.substring(0, 50)}${p.initialPrompt.length > 50 ? '...' : ''}`); }); - } else { + } else if (projectsResponse.status === "success"){ console.log(chalk.yellow('No projects available.')); } + // else: error fetching list, handled by outer catch process.exit(1); + } else { + console.error(chalk.red(formatCliError(normalized))); + process.exit(1); } - throw error; // Re-throw other errors } } else { - // List all projects, applying state filter - const projectsResponse = await taskManager.listProjects(filterState); - // Check for success before accessing data - const projectsToList = projectsResponse.status === 'success' ? projectsResponse.data.projects : []; + // List all projects, potentially filtered + const projectsSummaryResponse = await taskManager.listProjects(filterState); + if ('error' in projectsSummaryResponse) throw projectsSummaryResponse.error; + if (projectsSummaryResponse.status !== "success") throw createError(ErrorCode.InvalidResponseFormat, "Unexpected response"); - if (projectsToList.length === 0) { + const projectSummaries = projectsSummaryResponse.data.projects; + + if (projectSummaries.length === 0) { console.log(chalk.yellow(`No projects found${filterState ? ` matching state '${filterState}'` : ''}.`)); return; } - console.log(chalk.cyan('\nšŸ“‹ Projects List' + (filterState ? ` (filtered by state: ${filterState})` : ''))); - // Fetch full details for progress bar calculation if needed, or use summary data - for (const pSummary of projectsToList) { - try { - const projDetailsResp = await taskManager.readProject(pSummary.projectId); - if ('error' in projDetailsResp) { - throw projDetailsResp.error; - } - if (projDetailsResp.status !== "success") { - throw createError(ErrorCode.InvalidResponseFormat, "Unexpected response format from TaskManager"); - } - const p = projDetailsResp.data; - - const totalTasks = p.tasks.length; - const completedTasks = p.tasks.filter((t: { status: string }) => t.status === "done").length; - const approvedTasks = p.tasks.filter((t: { approved: boolean }) => t.approved).length; - const status = p.completed ? chalk.green('Completed āœ“') : chalk.yellow('In Progress'); - - console.log(`\n${chalk.bold(p.projectId)}: ${status}`); - console.log(` Initial Prompt: ${p.initialPrompt.substring(0, 100)}${p.initialPrompt.length > 100 ? '...' : ''}`); - console.log(` Progress: ${chalk.bold(`${approvedTasks}/${completedTasks}/${totalTasks}`)} (approved/completed/total)`); - - // Create a progress bar - if (totalTasks > 0) { - const bar = 'ā–“'.repeat(approvedTasks) + 'ā–’'.repeat(completedTasks - approvedTasks) + 'ā–‘'.repeat(totalTasks - completedTasks); - console.log(` ${bar}`); - } else { - console.log(chalk.yellow(' No tasks in this project.')); - } - } catch (error: unknown) { - if (error instanceof Error) { - console.error(chalk.red(`Error fetching details for project ${pSummary.projectId}: ${error.message}`)); - } else { - console.error(chalk.red(`Error fetching details for project ${pSummary.projectId}: Unknown error`)); - } - } + // Use the formatter directly with the summary data + console.log(chalk.cyan(formatProjectsList(projectSummaries))); + if (filterState) { + console.log(chalk.dim(`(Filtered by state: ${filterState})`)); } } } catch (error) { - // Handle errors generally - no need for TaskNotDone handling in list command console.error(chalk.red(formatCliError(normalizeError(error)))); process.exit(1); } @@ -486,31 +404,17 @@ program .option("--model ", "LLM model to use", "gpt-4-turbo") .option("--provider ", "LLM provider to use (openai, google, or deepseek)", "openai") .option("--attachment ", "File to attach as context (can be specified multiple times)", collect, []) - .action(async (options) => { + .action(async (options) => { try { console.log(chalk.blue(`Generating project plan from prompt...`)); + console.log(options.attachment); - // Read attachment files if provided - const attachments: string[] = []; - for (const file of options.attachment) { - try { - const content = await fs.readFile(file, 'utf-8'); - attachments.push(content); - } catch (error: unknown) { - if (error instanceof Error) { - console.error(chalk.yellow(`Warning: Could not read attachment file ${chalk.bold(file)}: ${error.message}`)); - } else { - console.error(chalk.yellow(`Warning: Could not read attachment file ${chalk.bold(file)}: Unknown error`)); - } - } - } - - // Call the generateProjectPlan method + // Pass attachment filenames directly to the server const response = await taskManager.generateProjectPlan({ prompt: options.prompt, provider: options.provider, model: options.model, - attachments, + attachments: options.attachment }); if ('error' in response) { @@ -552,17 +456,8 @@ program console.log(`\n${data.message}`); } } catch (err: unknown) { - if (err instanceof Error) { - // Check for API key related errors and format them appropriately - if (err.message.includes('API key') || err.message.includes('authentication') || err.message.includes('unauthorized')) { - console.error(chalk.red(`Error: ${err.message}`)); - } else { - console.error(chalk.yellow(`Warning: ${err.message}`)); - } - } else { const normalized = normalizeError(err); - console.error(chalk.red(formatCliError(normalized))); - } + console.error(`Error: ${chalk.red(formatCliError(normalized))}`); process.exit(1); } }); diff --git a/src/client/errors.ts b/src/client/errors.ts index cb1ccfd..da798d7 100644 --- a/src/client/errors.ts +++ b/src/client/errors.ts @@ -1,9 +1,28 @@ import { StandardError } from "../types/index.js"; /** - * Formats an error message for CLI output + * Formats an error message for CLI output, optionally including relevant details. */ -export function formatCliError(error: StandardError, includeDetails: boolean = false): string { +export function formatCliError(error: StandardError, includeDetails: boolean = true): string { const codePrefix = error.message.includes(`[${error.code}]`) ? '' : `[${error.code}] `; - const message = `${codePrefix}${error.message}`; - return includeDetails && error.details ? `${message}\nDetails: ${JSON.stringify(error.details, null, 2)}` : message; + let message = `${codePrefix}${error.message}`; + + if (includeDetails && error.details) { + // Prioritize showing nested originalError message if it exists and is different + const originalErrorMessage = (error.details as any)?.originalError?.message; + if (originalErrorMessage && typeof originalErrorMessage === 'string' && originalErrorMessage !== error.message) { + message += `\n -> Details: ${originalErrorMessage}`; + } + // Add a fallback for simpler string details or stringified objects if needed, + // but avoid dumping large complex objects unless necessary for debugging. + // Example: uncomment if you often have simple string details + // else if (typeof error.details === 'string') { + // message += `\n -> Details: ${error.details}`; + // } + // Example: uncomment ONLY if you need to see the raw JSON details often + // else { + // message += `\nDetails: ${JSON.stringify(error.details, null, 2)}`; + // } + } + + return message; } \ No newline at end of file diff --git a/src/client/taskFormattingUtils.ts b/src/client/taskFormattingUtils.ts new file mode 100644 index 0000000..0eca4b9 --- /dev/null +++ b/src/client/taskFormattingUtils.ts @@ -0,0 +1,85 @@ +import Table from 'cli-table3'; // Import the library +import chalk from 'chalk'; // Import chalk for consistent styling +import { ListProjectsSuccessData, Project } from "../types/index.js"; + +/** + * Formats the project details and a progress table for its tasks using cli-table3. + * @param project - The project object containing the details and tasks. + * @returns A string representing the formatted project details and task progress table. + */ +export function formatTaskProgressTable(project: Project | undefined): string { + if (!project) return "Project not found"; + + // Build the project details header + let header = chalk.cyan(`\nšŸ“‹ Project ${chalk.bold(project.projectId)} details:\n`); + header += ` - ${chalk.bold('Initial Prompt:')} ${project.initialPrompt}\n`; + if (project.projectPlan && project.projectPlan !== project.initialPrompt) { + header += ` - ${chalk.bold('Project Plan:')} ${project.projectPlan}\n`; + } + header += ` - ${chalk.bold('Status:')} ${project.completed ? chalk.green('Completed āœ“') : chalk.yellow('In Progress')}\n`; + + + const table = new Table({ + head: ['ID', 'Title', 'Description', 'Status', 'Approved', 'Tools', 'Rules'], + colWidths: [10, 25, 40, 15, 10, 7, 7], // Adjust widths as needed + wordWrap: true, // Enable word wrapping for long descriptions + style: { head: ['cyan'] } // Optional styling + }); + + if (project.tasks.length === 0) { + table.push([{ colSpan: 7, content: 'No tasks in this project.', hAlign: 'center' }]); + } else { + for (const task of project.tasks) { + const statusText = task.status === "done" ? "Done" : (task.status === "in progress" ? "In Prog" : "Pending"); + const approvedText = task.approved ? "Yes" : "No"; + const toolsText = task.toolRecommendations ? "[+]" : "[-]"; // Simpler indicators + const rulesText = task.ruleRecommendations ? "[+]" : "[-]"; + // No need to manually truncate description if wordWrap is true and colWidths are set + + table.push([ + task.id, + task.title, + task.description, + statusText, + approvedText, + toolsText, + rulesText + ]); + } + } + + return header + '\n' + table.toString(); // Combine header and table +} + +/** + * Formats a list of project summaries into a markdown table using cli-table3. + * @param projects - An array of project summary objects, matching the structure of ListProjectsSuccessData["projects"]. + * @returns A string representing the formatted projects list table. + */ +export function formatProjectsList(projects: ListProjectsSuccessData["projects"]): string { + + const table = new Table({ + head: ['Project ID', 'Initial Prompt', 'Total', 'Done', 'Approved'], + colWidths: [15, 40, 8, 8, 10], // Adjust widths as needed + wordWrap: true, + style: { head: ['cyan'] } // Optional styling + }); + + if (projects.length === 0) { + table.push([{ colSpan: 5, content: 'No projects found.', hAlign: 'center' }]); + } else { + for (const proj of projects) { + // Truncate long initial prompts manually if desired, even with wordWrap + const shortPrompt = proj.initialPrompt.length > 60 ? proj.initialPrompt.substring(0, 57) + "..." : proj.initialPrompt; + table.push([ + proj.projectId, + shortPrompt, // Use truncated prompt + proj.totalTasks, + proj.completedTasks, + proj.approvedTasks + ]); + } + } + + return '\nProjects List:\n' + table.toString(); +} diff --git a/src/server/FileSystemService.ts b/src/server/FileSystemService.ts index 5991dce..739d79a 100644 --- a/src/server/FileSystemService.ts +++ b/src/server/FileSystemService.ts @@ -1,5 +1,5 @@ import { readFile, writeFile, mkdir } from 'node:fs/promises'; -import { dirname, join } from "node:path"; +import { dirname, join, resolve } from "node:path"; import { homedir } from "node:os"; import { TaskManagerFile, ErrorCode } from "../types/index.js"; import { createError } from "../utils/errors.js"; @@ -176,4 +176,30 @@ export class FileSystemService { } }); } + + /** + * Reads an attachment file from the current working directory + * @param filename The name of the file to read (relative to cwd) + * @returns The contents of the file as a string + * @throws {StandardError} If the file cannot be read + */ + public async readAttachmentFile(filename: string): Promise { + try { + const filePath = resolve(process.cwd(), filename); + return await readFile(filePath, 'utf-8'); + } catch (error) { + if (error instanceof Error && error.message.includes('ENOENT')) { + throw createError( + ErrorCode.FileReadError, + `Attachment file not found: ${filename}`, + { originalError: error } + ); + } + throw createError( + ErrorCode.FileReadError, + `Failed to read attachment file: ${filename}`, + { originalError: error } + ); + } + } } \ No newline at end of file diff --git a/src/server/TaskManager.ts b/src/server/TaskManager.ts index 3ad614c..bd560ae 100644 --- a/src/server/TaskManager.ts +++ b/src/server/TaskManager.ts @@ -18,7 +18,6 @@ import { } from "../types/index.js"; import { createError, createSuccessResponse } from "../utils/errors.js"; import { generateObject, jsonSchema } from "ai"; -import { formatTaskProgressTable, formatProjectsList } from "./taskFormattingUtils.js"; import { FileSystemService } from "./FileSystemService.js"; // Default path follows platform-specific conventions @@ -105,8 +104,6 @@ export class TaskManager { await this.saveTasks(); - const progressTable = formatTaskProgressTable(newProject); - return createSuccessResponse({ projectId, totalTasks: newTasks.length, @@ -115,7 +112,7 @@ export class TaskManager { title: t.title, description: t.description, })), - message: `Project ${projectId} created with ${newTasks.length} tasks.\n${progressTable}`, + message: `Project ${projectId} created with ${newTasks.length} tasks.`, }); } @@ -132,10 +129,50 @@ export class TaskManager { }): Promise> { await this.ensureInitialized(); + // Read all attachment files + const attachmentContents: string[] = []; + for (const filename of attachments) { + try { + console.log("We are about to try to read the file.") + const content = await this.fileSystemService.readAttachmentFile(filename); + attachmentContents.push(content); + } catch (error) { + // Propagate file read errors + throw createError( + ErrorCode.FileReadError, + `Failed to read attachment file: ${filename}`, + { originalError: error } + ); + } + } + + // Define the schema for the LLM's response using jsonSchema helper + const projectPlanSchema = jsonSchema({ + type: "object", + properties: { + projectPlan: { type: "string" }, + tasks: { + type: "array", + items: { + type: "object", + properties: { + title: { type: "string" }, + description: { type: "string" }, + toolRecommendations: { type: "string" }, + ruleRecommendations: { type: "string" }, + }, + required: ["title", "description"], + }, + }, + }, + required: ["tasks"], + }); + // Wrap prompt and attachments in XML tags let llmPrompt = `${prompt}`; - for (const att of attachments) { - llmPrompt += `\n${att}`; + llmPrompt += `\nReturn your output as JSON formatted according to the following schema: ${JSON.stringify(projectPlanSchema, null, 2)}` + for (const content of attachmentContents) { + llmPrompt += `\n${content}`; } // Import and configure the appropriate provider @@ -159,28 +196,7 @@ export class TaskManager { `Invalid provider: ${provider}` ); } - - // Define the schema for the LLM's response using jsonSchema helper - const projectPlanSchema = jsonSchema({ - type: "object", - properties: { - projectPlan: { type: "string" }, - tasks: { - type: "array", - items: { - type: "object", - properties: { - title: { type: "string" }, - description: { type: "string" }, - toolRecommendations: { type: "string" }, - ruleRecommendations: { type: "string" }, - }, - required: ["title", "description"], - }, - }, - }, - required: ["projectPlan", "tasks"], - }); + console.log("set model and provider") interface ProjectPlanOutput { projectPlan: string; @@ -211,6 +227,7 @@ export class TaskManager { } catch (err) { // Handle specific AI SDK errors if (err instanceof Error) { + // Check for specific error names or messages if (err.name === 'NoObjectGeneratedError') { throw createError( ErrorCode.InvalidResponseFormat, @@ -232,20 +249,33 @@ export class TaskManager { { originalError: err } ); } + // --- Updated Check for API Key Errors --- + // Check by name (more robust) or message content + if (err.name === 'LoadAPIKeyError' || err.message.includes('API key is missing')) { + throw createError( + ErrorCode.ConfigurationError, // Use the correct code for config issues + "Invalid or missing API key. Please check your environment variables.", // More specific message + { originalError: err } + ); + } + // Existing check for general auth errors (might still be relevant for other cases) if (err.message.includes('authentication') || err.message.includes('unauthorized')) { throw createError( ErrorCode.ConfigurationError, - "Invalid API key or authentication failed. Please check your environment variables.", + "Authentication failed with the LLM provider. Please check your credentials.", { originalError: err } ); } } - // For unknown errors, preserve the original error but wrap it + // For unknown errors from the LLM/SDK, preserve the original error but wrap it. + // Use a more generic error code here if it's not one of the above. + // Perhaps keep InvalidResponseFormat or create a new one like LLMInteractionError? + // Let's stick with InvalidResponseFormat for now as it often manifests as bad output. throw createError( - ErrorCode.InvalidResponseFormat, - "Failed to generate project plan", - { originalError: err } + ErrorCode.InvalidResponseFormat, // Fallback code + "Failed to generate project plan due to an unexpected error.", // Fallback message + { originalError: err } // Always include original error for debugging ); } } @@ -273,11 +303,10 @@ export class TaskManager { // all tasks done? const allDone = proj.tasks.every((t) => t.status === "done"); if (allDone && !proj.completed) { - const progressTable = formatTaskProgressTable(proj); return { status: "all_tasks_done", data: { - message: `All tasks have been completed. Awaiting project completion approval.\n${progressTable}` + message: `All tasks have been completed. Awaiting project completion approval.` } }; } @@ -287,14 +316,13 @@ export class TaskManager { ); } - const progressTable = formatTaskProgressTable(proj); return { status: "next_task", data: { id: nextTask.id, title: nextTask.title, description: nextTask.description, - message: `Next task is ready. Task approval will be required after completion.\n${progressTable}` + message: `Next task is ready. Task approval will be required after completion.\n` } }; } @@ -448,9 +476,8 @@ export class TaskManager { }); } - const projectsList = formatProjectsList(filteredProjects); return createSuccessResponse({ - message: `Current projects in the system:\n${projectsList}`, + message: `Current projects in the system:`, projects: filteredProjects.map((proj) => ({ projectId: proj.projectId, initialPrompt: proj.initialPrompt, @@ -546,9 +573,8 @@ export class TaskManager { proj.tasks.push(...newTasks); await this.saveTasks(); - const progressTable = formatTaskProgressTable(proj); return createSuccessResponse({ - message: `Added ${newTasks.length} new tasks to project ${projectId}.\n${progressTable}`, + message: `Added ${newTasks.length} new tasks to project ${projectId}.`, newTasks: newTasks.map((t) => ({ id: t.id, title: t.title, @@ -629,9 +655,8 @@ export class TaskManager { proj.tasks.splice(taskIndex, 1); await this.saveTasks(); - const progressTable = formatTaskProgressTable(proj); return createSuccessResponse({ - message: `Task ${taskId} has been deleted from project ${projectId}.\n${progressTable}`, + message: `Task ${taskId} has been deleted from project ${projectId}.`, }); } diff --git a/src/server/taskFormattingUtils.ts b/src/server/taskFormattingUtils.ts deleted file mode 100644 index 5ac891a..0000000 --- a/src/server/taskFormattingUtils.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { Project } from "../types/index.js"; - -/** - * Formats a progress table for the tasks within a given project. - * @param project - The project object containing the tasks. - * @returns A markdown string representing the task progress table. - */ -export function formatTaskProgressTable(project: Project | undefined): string { - if (!project) return "Project not found"; - - let table = "\nProgress Status:\n"; - table += "| Task ID | Title | Description | Status | Approval | Tools | Rules |\n"; - table += "|----------|----------|-------------|--------|----------|-------|-------|\n"; // Adjusted description column width - - for (const task of project.tasks) { - const status = task.status === "done" ? "āœ… Done" : (task.status === "in progress" ? "šŸ”„ In Progress" : "ā³ Not Started"); - const approved = task.approved ? "āœ… Approved" : "ā³ Pending"; - const tools = task.toolRecommendations ? "āœ“" : "-"; - const rules = task.ruleRecommendations ? "āœ“" : "-"; - // Truncate long descriptions for table view - const shortDesc = task.description.length > 50 ? task.description.substring(0, 47) + " ..." : task.description; - table += `| ${task.id} | ${task.title} | ${shortDesc} | ${status} | ${approved} | ${tools} | ${rules} |\n`; - } - - return table; -} - -/** - * Formats a list of projects into a markdown table. - * @param projects - An array of project objects. - * @returns A markdown string representing the projects list table. - */ -export function formatProjectsList(projects: Project[]): string { - let output = "\nProjects List:\n"; - output += - "| Project ID | Initial Prompt | Total Tasks | Completed | Approved |\n"; - output += - "|------------|------------------|-------------|-----------|----------|\n"; - - for (const proj of projects) { - const totalTasks = proj.tasks.length; - const completedTasks = proj.tasks.filter((t) => t.status === "done").length; - const approvedTasks = proj.tasks.filter((t) => t.approved).length; - // Truncate long initial prompts - const shortPrompt = proj.initialPrompt.length > 30 ? proj.initialPrompt.substring(0, 27) + "..." : proj.initialPrompt; - output += `| ${proj.projectId} | ${shortPrompt} | ${totalTasks} | ${completedTasks} | ${approvedTasks} |\n`; - } - - return output; -} diff --git a/src/utils/errors.ts b/src/utils/errors.ts index 93904a9..f22acde 100644 --- a/src/utils/errors.ts +++ b/src/utils/errors.ts @@ -45,28 +45,48 @@ function getCategoryFromCode(code: ErrorCode): ErrorCategory { * Converts any error to a StandardError */ export function normalizeError(error: unknown): StandardError { + // 1. Check if it already looks like a StandardError (duck typing) + if ( + typeof error === 'object' && + error !== null && + 'status' in error && error.status === 'error' && + 'code' in error && typeof error.code === 'string' && + 'category' in error && typeof error.category === 'string' && + 'message' in error && typeof error.message === 'string' && + Object.values(ErrorCode).includes(error.code as ErrorCode) // Verify the code is valid + ) { + // It already conforms to the StandardError structure, return as is. + // We cast because TypeScript knows it's 'object', but we've verified the shape. + return error as StandardError; + } + + // 2. Check if it's an instance of Error if (error instanceof Error) { - // Try to parse error code from message if it exists - const codeMatch = error.message.match(/\[([A-Z_]+)\]/); - if (codeMatch && Object.values(ErrorCode).includes(codeMatch[1] as ErrorCode)) { + const codeMatch = error.message.match(/\[([A-Z_0-9]+)\]/); + // Ensure codeMatch exists and the captured group is a valid ErrorCode + if (codeMatch && codeMatch[1] && Object.values(ErrorCode).includes(codeMatch[1] as ErrorCode)) { + const extractedCode = codeMatch[1] as ErrorCode; + // Remove the code prefix "[CODE]" from the message - use the full match codeMatch[0] for replacement + const cleanedMessage = error.message.replace(codeMatch[0], '').trim(); return createError( - codeMatch[1] as ErrorCode, - error.message.replace(`[${codeMatch[1]}]`, '').trim(), - { stack: error.stack } + extractedCode, + cleanedMessage, + { stack: error.stack } // Keep stack trace if available ); } - // Default to unknown error + // Fallback for generic Errors without a recognized code in the message return createError( - ErrorCode.InvalidArgument, + ErrorCode.InvalidArgument, // Use InvalidArgument for generic errors error.message, { stack: error.stack } ); } + // 3. Handle other types (string, primitive, plain object without structure) return createError( ErrorCode.Unknown, typeof error === 'string' ? error : 'An unknown error occurred', - { originalError: error } + { originalError: error } // Include the original unknown error type ); } \ No newline at end of file diff --git a/tests/integration/cli.integration.test.ts b/tests/integration/cli.integration.test.ts index 0a80914..8192e07 100644 --- a/tests/integration/cli.integration.test.ts +++ b/tests/integration/cli.integration.test.ts @@ -12,7 +12,7 @@ describe("CLI Integration Tests", () => { let tasksFilePath: string; beforeEach(async () => { - tempDir = path.join(os.tmpdir(), `task-manager-cli-test-${Date.now()}`); + tempDir = path.join(os.tmpdir(), `taskqueue-test-${Date.now()}`); await fs.mkdir(tempDir, { recursive: true }); tasksFilePath = path.join(tempDir, "test-tasks.json"); process.env.TASK_MANAGER_FILE_PATH = tasksFilePath; @@ -136,35 +136,6 @@ describe("CLI Integration Tests", () => { expect(noTasks).toContain("No tasks found matching state 'completed' in project proj-1"); }, 5000); - it("should show progress bars and status indicators correctly", async () => { - const { stdout } = await execAsync(`TASK_MANAGER_FILE_PATH=${tasksFilePath} tsx ${CLI_PATH} list`); - - // Check for progress indicators - expect(stdout).toContain("Progress:"); - expect(stdout).toContain("approved/completed/total"); - - // Check for status indicators - expect(stdout).toContain("In Progress"); - expect(stdout).toContain("Completed āœ“"); - }, 5000); - - it("should display tool and rule recommendations when listing tasks", async () => { - // Create a task with tool and rule recommendations - const testData = JSON.parse(await fs.readFile(tasksFilePath, 'utf-8')); - testData.projects[0].tasks[0].toolRecommendations = "Use grep to search for code"; - testData.projects[0].tasks[0].ruleRecommendations = "Follow code style guidelines"; - await fs.writeFile(tasksFilePath, JSON.stringify(testData)); - - // Test listing the specific project with the updated task - const { stdout } = await execAsync(`TASK_MANAGER_FILE_PATH=${tasksFilePath} tsx ${CLI_PATH} list -p proj-1`); - - // Check that recommendations are displayed - expect(stdout).toContain("Tool Recommendations:"); - expect(stdout).toContain("Use grep to search for code"); - expect(stdout).toContain("Rule Recommendations:"); - expect(stdout).toContain("Follow code style guidelines"); - }, 5000); - describe("generate-plan command", () => { beforeEach(() => { // Set mock API keys for testing @@ -181,25 +152,29 @@ describe("CLI Integration Tests", () => { it("should handle missing API key gracefully", async () => { delete process.env.OPENAI_API_KEY; - + const { stderr } = await execAsync( `TASK_MANAGER_FILE_PATH=${tasksFilePath} tsx ${CLI_PATH} generate-plan --prompt "Create a todo app" --provider openai` ).catch(error => error); - + // Verify we get an error with the error code format expect(stderr).toContain("[ERR_"); - // The actual error might not contain "API key" text, so we'll just check for a general error - expect(stderr).toContain("An unknown error occurred"); + // The actual error should contain "API key" text + expect(stderr).toContain("API key"); }, 5000); it("should handle invalid file attachments gracefully", async () => { - const { stderr } = await execAsync( + const { stdout, stderr } = await execAsync( `TASK_MANAGER_FILE_PATH=${tasksFilePath} tsx ${CLI_PATH} generate-plan --prompt "Create app" --attachment nonexistent.txt` - ).catch(error => error); - - // Just verify we get a warning about the attachment - expect(stderr).toContain("Warning:"); - expect(stderr).toContain("nonexistent.txt"); + ).catch(error => ({ stdout: error.stdout, stderr: error.stderr })); + + // Keep these console logs temporarily if helpful for debugging during development + // console.log("Test stdout:", stdout); + // console.log("Test stderr:", stderr); + + // Updated assertion to match the formatCliError output + expect(stderr).toContain("[ERR_4000] Failed to read attachment file: nonexistent.txt"); + expect(stderr).toContain("-> Details: Attachment file not found: nonexistent.txt"); }, 5000); }); }); \ No newline at end of file diff --git a/tests/integration/e2e.integration.test.ts b/tests/integration/e2e.integration.test.ts index bc163dd..2563158 100644 --- a/tests/integration/e2e.integration.test.ts +++ b/tests/integration/e2e.integration.test.ts @@ -5,6 +5,10 @@ import * as path from 'node:path'; import * as os from 'node:os'; import * as fs from 'node:fs/promises'; import process from 'node:process'; +import dotenv from 'dotenv'; + +// Load environment variables from .env file +dotenv.config(); interface ToolResponse { isError: boolean; @@ -34,7 +38,9 @@ describe('MCP Client Integration', () => { env: { TASK_MANAGER_FILE_PATH: testFilePath, NODE_ENV: "test", - DEBUG: "mcp:*" // Enable MCP debug logging + DEBUG: "mcp:*", // Enable MCP debug logging + // Pass the API key from the test runner's env to the child process env + OPENAI_API_KEY: process.env.OPENAI_API_KEY ?? '' } }); @@ -285,4 +291,63 @@ describe('MCP Client Integration', () => { expect(finalizeResult.isError).toBeFalsy(); console.log('Project was successfully finalized after explicit task approval'); }); + + // Skip by default as it requires OpenAI API key + it.skip('should generate a project plan using OpenAI', async () => { + console.log('Testing project plan generation...'); + + // Skip if no OpenAI API key is set + const openaiApiKey = process.env.OPENAI_API_KEY; + if (!openaiApiKey) { + console.log('Skipping test: OPENAI_API_KEY not set'); + return; + } + + // Create a temporary requirements file + const requirementsPath = path.join(tempDir, 'requirements.md'); + const requirements = `# Project Plan Requirements + +- This is a test of whether we are correctly attaching files to our prompt +- Return a JSON project plan with one task +- Task title must be 'AmazingTask' +- Task description must be AmazingDescription +- Project plan attribute should be AmazingPlan`; + + await fs.writeFile(requirementsPath, requirements, 'utf-8'); + + // Test prompt and context + const testPrompt = "Create a step-by-step project plan to build a simple TODO app with React"; + + // Generate project plan + const generateResult = await client.callTool({ + name: "generate_project_plan", + arguments: { + prompt: testPrompt, + provider: "openai", + model: "gpt-4-turbo", + attachments: [requirementsPath] + } + }) as ToolResponse; + + expect(generateResult.isError).toBeFalsy(); + const planData = JSON.parse((generateResult.content[0] as { text: string }).text); + + // Verify the generated plan structure + expect(planData).toHaveProperty('data'); + expect(planData.data).toHaveProperty('tasks'); + expect(Array.isArray(planData.data.tasks)).toBe(true); + expect(planData.data.tasks.length).toBeGreaterThan(0); + + // Verify task structure + const firstTask = planData.data.tasks[0]; + expect(firstTask).toHaveProperty('title'); + expect(firstTask).toHaveProperty('description'); + + // Verify that the generated task adheres to the requirements file context + expect(firstTask.title).toBe('AmazingTask'); + expect(firstTask.description).toBe('AmazingDescription'); + + // The temporary file will be cleaned up by the afterAll hook that removes tempDir + console.log('Successfully generated project plan with tasks'); + }); }); \ No newline at end of file diff --git a/tests/unit/TaskManager.test.ts b/tests/unit/TaskManager.test.ts index 4f2affa..c939bba 100644 --- a/tests/unit/TaskManager.test.ts +++ b/tests/unit/TaskManager.test.ts @@ -35,6 +35,7 @@ const mockSaveTasks = jest.fn() as jest.MockedFunction; const mockLoadTasks = jest.fn() as jest.MockedFunction; const mockReloadTasks = jest.fn() as jest.MockedFunction; +const mockReadAttachmentFile = jest.fn() as jest.MockedFunction; // Create mock functions for FileSystemService static methods const mockGetAppDataDir = jest.fn() as jest.MockedFunction; @@ -47,6 +48,7 @@ jest.unstable_mockModule('../../src/server/FileSystemService.js', () => { calculateMaxIds = mockCalculateMaxIds; loadTasks = mockLoadTasks; reloadTasks = mockReloadTasks; + readAttachmentFile = mockReadAttachmentFile; static getAppDataDir = mockGetAppDataDir; } @@ -125,6 +127,11 @@ describe('TaskManager', () => { return JSON.parse(JSON.stringify(currentMockData)); }); + // Mock readAttachmentFile to return the filename as content for testing + mockReadAttachmentFile.mockImplementation(async (filename: string) => { + return filename; + }); + // CalculateMaxIds uses the helper logic on potentially provided data // Note: TaskManager might rely on its *internal* maxId counters more than calling this directly after init mockCalculateMaxIds.mockImplementation((data: TaskManagerFile) => { @@ -1062,7 +1069,7 @@ describe('TaskManager', () => { attachments: [] })).rejects.toMatchObject({ code: 'ERR_1003', - message: "Invalid API key or authentication failed. Please check your environment variables." + message: "Authentication failed with the LLM provider. Please check your credentials." }); }); diff --git a/tests/unit/errors.test.ts b/tests/unit/errors.test.ts new file mode 100644 index 0000000..2f54142 --- /dev/null +++ b/tests/unit/errors.test.ts @@ -0,0 +1,91 @@ +import { normalizeError, createError } from '../../src/utils/errors.js'; +import { StandardError, ErrorCode, ErrorCategory } from '../../src/types/index.js'; + +describe('normalizeError', () => { + it('should return the same StandardError object if passed a StandardError', () => { + const standardError: StandardError = { + status: 'error', + code: ErrorCode.ProjectNotFound, + category: ErrorCategory.ResourceNotFound, + message: 'Project not found', + }; + + const result = normalizeError(standardError); + // Use 'toBe' to check for referential equality (same object) + expect(result).toBe(standardError); + // Also check deep equality just in case + expect(result).toEqual(standardError); + }); + + it('should correctly parse a StandardError from an Error with a valid code in the message', () => { + const originalError = new Error('[ERR_1000] Missing required parameter: userId'); + const expectedError: StandardError = { + status: 'error', + code: ErrorCode.MissingParameter, + category: ErrorCategory.Validation, + message: 'Missing required parameter: userId', + details: { stack: originalError.stack }, + }; + + const result = normalizeError(originalError); + expect(result).toEqual(expectedError); + }); + + it('should create a StandardError with InvalidArgument code for a generic Error without a code', () => { + const originalError = new Error('Something went wrong'); + const expectedError: StandardError = { + status: 'error', + code: ErrorCode.InvalidArgument, // Current fallback behavior + category: ErrorCategory.Validation, // Derived from InvalidArgument + message: 'Something went wrong', + details: { stack: originalError.stack }, + }; + + const result = normalizeError(originalError); + expect(result).toEqual(expectedError); + }); + + it('should create a StandardError with Unknown code for a string input', () => { + const errorString = 'A string error message'; + const expectedError: StandardError = { + status: 'error', + code: ErrorCode.Unknown, + category: ErrorCategory.Unknown, + message: errorString, + details: { originalError: errorString }, + }; + + const result = normalizeError(errorString); + expect(result).toEqual(expectedError); + }); + + it('should create a StandardError with Unknown code for an object input', () => { + const errorObject = { detail: 'Some custom error object' }; + const expectedError: StandardError = { + status: 'error', + code: ErrorCode.Unknown, + category: ErrorCategory.Unknown, + message: 'An unknown error occurred', + details: { originalError: errorObject }, + }; + + const result = normalizeError(errorObject); + expect(result).toEqual(expectedError); + }); + + it('should handle errors created with createError correctly', () => { + const createdError = createError(ErrorCode.FileReadError, "Could not read file", { path: "/tmp/file" }); + // When createError is used, it doesn't embed the code in the message. + // normalizeError currently relies on finding the code *in the message* for standard Errors. + // Let's test how normalizeError handles an error *object* that looks like a StandardError but isn't one instanceof Error. + + // If we pass the *object* created by createError: + const resultFromObject = normalizeError(createdError); + expect(resultFromObject).toBe(createdError); // Should pass through if it's already the right shape. + + // If we simulate throwing it and catching it (which might wrap it): + // This is more complex to simulate accurately without more context on *how* it might be thrown/caught. + // The main point is covered by the first test: if the caught object *is* a StandardError, it's passed through. + }); + +}); diff --git a/tests/unit/taskFormattingUtils.test.ts b/tests/unit/taskFormattingUtils.test.ts index 5c8f565..3b97385 100644 --- a/tests/unit/taskFormattingUtils.test.ts +++ b/tests/unit/taskFormattingUtils.test.ts @@ -1,6 +1,7 @@ import { describe, it, expect } from '@jest/globals'; -import { formatTaskProgressTable, formatProjectsList } from '../../src/server/taskFormattingUtils.js'; -import { Project, Task } from '../../src/types/index.js'; +// Note: We might need strip-ansi if chalk colors interfere with snapshot testing, but basic string checks should be okay. +import { formatTaskProgressTable, formatProjectsList } from '../../src/client/taskFormattingUtils.js'; +import { Project, Task, ListProjectsSuccessData } from '../../src/types/index.js'; describe('taskFormattingUtils', () => { @@ -20,21 +21,25 @@ describe('taskFormattingUtils', () => { it('should format an empty task list correctly', () => { const project: Project = { ...baseProject, tasks: [] }; - const expectedHeader = "| Task ID | Title | Description | Status | Approval | Tools | Rules |\n"; - const expectedSeparator = "|----------|----------|-------------|--------|----------|-------|-------|\n"; const result = formatTaskProgressTable(project); - expect(result).toContain("\nProgress Status:\n"); - expect(result).toContain(expectedHeader); - expect(result).toContain(expectedSeparator); - // Check that there are no task rows - expect(result.split('\n').length).toBe(5); // Title, Header, Separator, Blank line at start, Blank line at end + // Use toMatch with .* to handle potential ANSI codes from chalk.bold() + expect(result).toMatch(/šŸ“‹ Project .*proj-1.* details:/); + expect(result).toContain('No tasks in this project.'); + expect(result).toContain('ID'); }); it('should format a single task correctly (not started)', () => { const task: Task = { id: 'task-1', title: 'Task One', description: 'Desc One', status: 'not started', approved: false, completedDetails: '' }; const project: Project = { ...baseProject, tasks: [task] }; const result = formatTaskProgressTable(project); - expect(result).toContain('| task-1 | Task One | Desc One | ā³ Not Started | ā³ Pending | - | - |'); + // Use toMatch with .* to handle potential ANSI codes from chalk.bold() + expect(result).toMatch(/šŸ“‹ Project .*proj-1.* details:/); + expect(result).toContain('task-1'); + expect(result).toContain('Task One'); + expect(result).toContain('Desc One'); + expect(result).toContain('Pending'); // Status text + expect(result).toContain('No'); // Approved text + expect(result).toContain('[-]'); // Tools/Rules text }); it('should format a task in progress with recommendations', () => { @@ -50,30 +55,44 @@ describe('taskFormattingUtils', () => { }; const project: Project = { ...baseProject, tasks: [task] }; const result = formatTaskProgressTable(project); - expect(result).toContain('| task-2 | Task Two | Desc Two | šŸ”„ In Progress | ā³ Pending | āœ“ | āœ“ |'); + expect(result).toContain('task-2'); + expect(result).toContain('In Prog'); // Status text + expect(result).toContain('No'); // Approved text + expect(result).toContain('[+]'); // Tools/Rules text }); it('should format a completed and approved task', () => { const task: Task = { id: 'task-3', title: 'Task Three', description: 'Desc Three', status: 'done', approved: true, completedDetails: 'Done details' }; const project: Project = { ...baseProject, tasks: [task] }; const result = formatTaskProgressTable(project); - expect(result).toContain('| task-3 | Task Three | Desc Three | āœ… Done | āœ… Approved | - | - |'); + expect(result).toContain('task-3'); + expect(result).toContain('Done'); // Status text + expect(result).toContain('Yes'); // Approved text + expect(result).toContain('[-]'); // Tools/Rules text }); it('should format a completed but not approved task', () => { const task: Task = { id: 'task-4', title: 'Task Four', description: 'Desc Four', status: 'done', approved: false, completedDetails: 'Done details' }; const project: Project = { ...baseProject, tasks: [task] }; const result = formatTaskProgressTable(project); - expect(result).toContain('| task-4 | Task Four | Desc Four | āœ… Done | ā³ Pending | - | - |'); + expect(result).toContain('task-4'); + expect(result).toContain('Done'); // Status text + expect(result).toContain('No'); // Approved text + expect(result).toContain('[-]'); // Tools/Rules text }); - it('should truncate long descriptions', () => { - const longDescription = 'This is a very long description that definitely exceeds the fifty character limit imposed by the formatting function.'; - const truncatedDescription = 'This is a very long description that definitely ...'; + it('should handle long descriptions with word wrap', () => { + // No longer testing manual truncation, just presence of the text + const longDescription = 'This is a very long description that definitely exceeds the forty character width set for the description column and should wrap.'; const task: Task = { id: 'task-5', title: 'Long Desc Task', description: longDescription, status: 'not started', approved: false, completedDetails: '' }; const project: Project = { ...baseProject, tasks: [task] }; const result = formatTaskProgressTable(project); - expect(result).toContain(`| task-5 | Long Desc Task | ${truncatedDescription} | ā³ Not Started | ā³ Pending | - | - |`); + expect(result).toContain('task-5'); + expect(result).toContain('Long Desc Task'); + // Check for the start of the long description, acknowledging it will be wrapped by the library + expect(result).toContain('This is a very long description that'); + // Removed the check for 'column and should wrap.' as wrapping can make specific substring checks fragile. + expect(result).toContain('Pending'); }); it('should format multiple tasks', () => { @@ -81,81 +100,94 @@ describe('taskFormattingUtils', () => { const task2: Task = { id: 'task-2', title: 'Task Two', description: 'Desc Two', status: 'done', approved: true, completedDetails: '' }; const project: Project = { ...baseProject, tasks: [task1, task2] }; const result = formatTaskProgressTable(project); - expect(result).toContain('| task-1 | Task One | Desc One | ā³ Not Started | ā³ Pending | - | - |'); - expect(result).toContain('| task-2 | Task Two | Desc Two | āœ… Done | āœ… Approved | - | - |'); + // Check for elements of both tasks + expect(result).toContain('task-1'); + expect(result).toContain('Task One'); + expect(result).toContain('Pending'); + expect(result).toContain('No'); + expect(result).toContain('[-]'); + + expect(result).toContain('task-2'); + expect(result).toContain('Task Two'); + expect(result).toContain('Done'); + expect(result).toContain('Yes'); + expect(result).toContain('[-]'); }); }); describe('formatProjectsList', () => { - const baseTask: Task = { id: 'task-1', title: 'T1', description: 'D1', status: 'not started', approved: false, completedDetails: '' }; + type ProjectSummary = ListProjectsSuccessData["projects"][0]; it('should format an empty project list correctly', () => { - const projects: Project[] = []; - const expectedHeader = "| Project ID | Initial Prompt | Total Tasks | Completed | Approved |\n"; - const expectedSeparator = "|------------|------------------|-------------|-----------|----------|\n"; + const projects: ProjectSummary[] = []; const result = formatProjectsList(projects); - expect(result).toContain("\nProjects List:\n"); - expect(result).toContain(expectedHeader); - expect(result).toContain(expectedSeparator); - // Check that there are no project rows - expect(result.split('\n').length).toBe(5); // Title, Header, Separator, Blank line at start, Blank line at end + // Check for the main header and the empty message within the table structure + expect(result).toContain('Projects List:'); + expect(result).toContain('No projects found.'); // Use simpler text check + expect(result).toContain('Project ID'); // Check if header is present }); it('should format a single project correctly', () => { - const project: Project = { - projectId: 'proj-1', - initialPrompt: 'Short prompt', - projectPlan: 'Plan', - completed: false, - autoApprove: false, - tasks: [ - { ...baseTask, status: 'done', approved: true }, - { ...baseTask, id: 'task-2', status: 'in progress' } - ] + const projectSummary: ProjectSummary = { + projectId: 'proj-1', initialPrompt: 'Short prompt', totalTasks: 2, completedTasks: 1, approvedTasks: 1 }; - const result = formatProjectsList([project]); - expect(result).toContain('| proj-1 | Short prompt | 2 | 1 | 1 |'); + const result = formatProjectsList([projectSummary]); + // Check for key data points within the formatted row + expect(result).toContain('proj-1'); + expect(result).toContain('Short prompt'); + expect(result).toContain(' 2 '); // Check for counts with padding + expect(result).toContain(' 1 '); + expect(result).toContain(' 1 '); // Need trailing space if aligned right/center }); it('should format multiple projects', () => { - const project1: Project = { - projectId: 'proj-1', initialPrompt: 'Prompt 1', projectPlan: 'P1', completed: false, autoApprove: false, tasks: [{ ...baseTask }] + const project1: ProjectSummary = { + projectId: 'proj-1', initialPrompt: 'Prompt 1', totalTasks: 1, completedTasks: 0, approvedTasks: 0 }; - const project2: Project = { - projectId: 'proj-2', initialPrompt: 'Prompt 2', projectPlan: 'P2', completed: true, autoApprove: false, tasks: [{ ...baseTask, status: 'done', approved: true }] + const project2: ProjectSummary = { + projectId: 'proj-2', initialPrompt: 'Prompt 2', totalTasks: 3, completedTasks: 2, approvedTasks: 1 }; const result = formatProjectsList([project1, project2]); - expect(result).toContain('| proj-1 | Prompt 1 | 1 | 0 | 0 |'); - expect(result).toContain('| proj-2 | Prompt 2 | 1 | 1 | 1 |'); + // Check for elements of both projects + expect(result).toContain('proj-1'); + expect(result).toContain('Prompt 1'); + expect(result).toContain(' 1 '); + expect(result).toContain(' 0 '); + + expect(result).toContain('proj-2'); + expect(result).toContain('Prompt 2'); + expect(result).toContain(' 3 '); + expect(result).toContain(' 2 '); + expect(result).toContain(' 1 '); // Approved count for proj-2 }); it('should truncate long initial prompts', () => { - const longPrompt = 'This is a very long initial prompt that should be truncated in the list view.'; - const truncatedPrompt = 'This is a very long initial...'; - const project: Project = { - projectId: 'proj-long', initialPrompt: longPrompt, projectPlan: 'Plan', completed: false, autoApprove: false, tasks: [{ ...baseTask }] + // This test remains similar as we kept manual truncation for prompts + const longPrompt = 'This is a very long initial prompt that should be truncated based on the substring logic in the function.'; + // Correct the expected start + const truncatedStart = 'This is a very long initial prompt'; + const ellipsis = '...'; // Check for the ellipsis separately due to potential wrapping + const project: ProjectSummary = { + projectId: 'proj-long', initialPrompt: longPrompt, totalTasks: 1, completedTasks: 0, approvedTasks: 0 }; const result = formatProjectsList([project]); - expect(result).toContain(`| proj-long | ${truncatedPrompt} | 1 | 0 | 0 |`); + expect(result).toContain('proj-long'); + expect(result).toContain(truncatedStart); // Check for the corrected start of the truncated string + expect(result).toContain(ellipsis); // Check for the ellipsis + expect(result).not.toContain('in the function.'); // Ensure the original end is cut off }); - it('should correctly count completed and approved tasks', () => { - const project: Project = { - projectId: 'proj-counts', - initialPrompt: 'Counts Test', - projectPlan: 'Plan', - completed: false, - autoApprove: false, - tasks: [ - { ...baseTask, id: 't1', status: 'done', approved: true }, // Done, Approved - { ...baseTask, id: 't2', status: 'done', approved: false }, // Done, Not Approved - { ...baseTask, id: 't3', status: 'in progress' }, // In Progress - { ...baseTask, id: 't4', status: 'not started' } // Not Started - ] + it('should correctly display pre-calculated completed and approved tasks counts', () => { + const project: ProjectSummary = { + projectId: 'proj-counts', initialPrompt: 'Counts Test', totalTasks: 4, completedTasks: 2, approvedTasks: 1 }; const result = formatProjectsList([project]); - // Expect Total=4, Completed=2, Approved=1 - expect(result).toContain('| proj-counts | Counts Test | 4 | 2 | 1 |'); + // Check for the specific counts formatted in the table + expect(result).toContain('proj-counts'); + expect(result).toContain('Counts Test'); + expect(result).toContain(' 4 '); + expect(result).toContain(' 2 '); + expect(result).toContain(' 1 '); }); }); }); diff --git a/tsconfig.json b/tsconfig.json index 332e364..cc8c905 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,6 +4,7 @@ "module": "NodeNext", "moduleResolution": "NodeNext", "esModuleInterop": true, + "isolatedModules": true, "strict": true, "outDir": "dist", "declaration": true,