From 8e6319e3c384bb7c65e7cee03f619e243ad47103 Mon Sep 17 00:00:00 2001 From: jamubc <150970140+jamubc@users.noreply.github.com> Date: Wed, 23 Jul 2025 02:01:17 -0700 Subject: [PATCH 01/20] patch windows issue - shell:true enabled, added basic sanitizations --- package-lock.json | 995 +---------------------------------- package.json | 5 - src/utils/commandExecutor.ts | 46 +- src/utils/geminiExecutor.ts | 16 +- 4 files changed, 69 insertions(+), 993 deletions(-) diff --git a/package-lock.json b/package-lock.json index 17a2766..c807257 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,10 +10,6 @@ "license": "MIT", "dependencies": { "@modelcontextprotocol/sdk": "^0.5.0", - "ai": "^4.3.17", - "chalk": "^5.0.0", - "d3-shape": "^3.2.0", - "inquirer": "^9.0.0", "prismjs": "^1.30.0", "zod": "^3.25.76", "zod-to-json-schema": "^3.24.6" @@ -22,11 +18,9 @@ "gemini-mcp": "dist/index.js" }, "devDependencies": { - "@types/inquirer": "^9.0.0", "@types/node": "^20.0.0", "archiver": "^7.0.1", "mermaid": "^11.9.0", - "nodemon": "^3.1.10", "tsx": "^4.0.0", "typescript": "^5.0.0", "vitepress": "^1.6.3", @@ -36,76 +30,6 @@ "node": ">=16.0.0" } }, - "node_modules/@ai-sdk/provider": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-1.1.3.tgz", - "integrity": "sha512-qZMxYJ0qqX/RfnuIaab+zp8UAeJn/ygXXAffR5I4N0n1IrvA6qBsjc8hXLmBiMV2zoXlifkacF7sEFnYnjBcqg==", - "license": "Apache-2.0", - "dependencies": { - "json-schema": "^0.4.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@ai-sdk/provider-utils": { - "version": "2.2.8", - "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-2.2.8.tgz", - "integrity": "sha512-fqhG+4sCVv8x7nFzYnFo19ryhAa3w096Kmc3hWxMQfW/TubPOmt3A6tYZhl4mUfQWWQMsuSkLrtjlWuXBVSGQA==", - "license": "Apache-2.0", - "dependencies": { - "@ai-sdk/provider": "1.1.3", - "nanoid": "^3.3.8", - "secure-json-parse": "^2.7.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "zod": "^3.23.8" - } - }, - "node_modules/@ai-sdk/react": { - "version": "1.2.12", - "resolved": "https://registry.npmjs.org/@ai-sdk/react/-/react-1.2.12.tgz", - "integrity": "sha512-jK1IZZ22evPZoQW3vlkZ7wvjYGYF+tRBKXtrcolduIkQ/m/sOAVcVeVDUDvh1T91xCnWCdUGCPZg2avZ90mv3g==", - "license": "Apache-2.0", - "dependencies": { - "@ai-sdk/provider-utils": "2.2.8", - "@ai-sdk/ui-utils": "1.2.11", - "swr": "^2.2.5", - "throttleit": "2.1.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "react": "^18 || ^19 || ^19.0.0-rc", - "zod": "^3.23.8" - }, - "peerDependenciesMeta": { - "zod": { - "optional": true - } - } - }, - "node_modules/@ai-sdk/ui-utils": { - "version": "1.2.11", - "resolved": "https://registry.npmjs.org/@ai-sdk/ui-utils/-/ui-utils-1.2.11.tgz", - "integrity": "sha512-3zcwCc8ezzFlwp3ZD15wAPjf2Au4s3vAbKsXQVyhxODHcmu0iyPO2Eua6D/vicq/AUm/BAo60r97O6HU+EI0+w==", - "license": "Apache-2.0", - "dependencies": { - "@ai-sdk/provider": "1.1.3", - "@ai-sdk/provider-utils": "2.2.8", - "zod-to-json-schema": "^3.24.1" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "zod": "^3.23.8" - } - }, "node_modules/@algolia/autocomplete-core": { "version": "1.17.7", "resolved": "https://registry.npmjs.org/@algolia/autocomplete-core/-/autocomplete-core-1.17.7.tgz", @@ -983,15 +907,6 @@ "mlly": "^1.7.4" } }, - "node_modules/@inquirer/figures": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.12.tgz", - "integrity": "sha512-MJttijd8rMFcKJC8NYmprWr6hD3r9Gd9qUC0XwPNwoEPWSMVJwA2MlXxF+nhZZNMY+HXsWa+o7KY2emWYIn0jQ==", - "license": "MIT", - "engines": { - "node": ">=18" - } - }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -1148,15 +1063,6 @@ "zod": "^3.23.8" } }, - "node_modules/@opentelemetry/api": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", - "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", - "license": "Apache-2.0", - "engines": { - "node": ">=8.0.0" - } - }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -1819,12 +1725,6 @@ "@types/d3-selection": "*" } }, - "node_modules/@types/diff-match-patch": { - "version": "1.0.36", - "resolved": "https://registry.npmjs.org/@types/diff-match-patch/-/diff-match-patch-1.0.36.tgz", - "integrity": "sha512-xFdR6tkm0MWvBfO8xXCSsinYxHcqkQUlcHeSpMC2ukzOb6lwQAfDmW+Qt0AvlGd8HpsS28qKsB+oPeJn9I39jg==", - "license": "MIT" - }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", @@ -1849,17 +1749,6 @@ "@types/unist": "*" } }, - "node_modules/@types/inquirer": { - "version": "9.0.8", - "resolved": "https://registry.npmjs.org/@types/inquirer/-/inquirer-9.0.8.tgz", - "integrity": "sha512-CgPD5kFGWsb8HJ5K7rfWlifao87m4ph8uioU7OTncJevmE/VLIqAAjfQtko578JZg7/f69K4FgqYym3gNr7DeA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/through": "*", - "rxjs": "^7.2.0" - } - }, "node_modules/@types/linkify-it": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", @@ -1905,16 +1794,6 @@ "undici-types": "~6.21.0" } }, - "node_modules/@types/through": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/@types/through/-/through-0.0.33.tgz", - "integrity": "sha512-HsJ+z3QuETzP3cswwtzt2vEIiHBk/dCcHGhbmG5X3ecnwFD/lPrMpliGXxSCg03L9AhrdwA4Oz/qfspkDW+xGQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/trusted-types": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", @@ -2235,32 +2114,6 @@ "node": ">=0.4.0" } }, - "node_modules/ai": { - "version": "4.3.17", - "resolved": "https://registry.npmjs.org/ai/-/ai-4.3.17.tgz", - "integrity": "sha512-uWqIQ94Nb1GTYtYElGHegJMOzv3r2mCKNFlKrqkft9xrfvIahTI5OdcnD5U9612RFGuUNGmSDTO1/YRNFXobaQ==", - "license": "Apache-2.0", - "dependencies": { - "@ai-sdk/provider": "1.1.3", - "@ai-sdk/provider-utils": "2.2.8", - "@ai-sdk/react": "1.2.12", - "@ai-sdk/ui-utils": "1.2.11", - "@opentelemetry/api": "1.9.0", - "jsondiffpatch": "0.6.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "react": "^18 || ^19 || ^19.0.0-rc", - "zod": "^3.23.8" - }, - "peerDependenciesMeta": { - "react": { - "optional": true - } - } - }, "node_modules/algoliasearch": { "version": "5.29.0", "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-5.29.0.tgz", @@ -2286,25 +2139,11 @@ "node": ">= 14.0.0" } }, - "node_modules/ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "license": "MIT", - "dependencies": { - "type-fest": "^0.21.3" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "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" @@ -2314,6 +2153,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, "license": "MIT", "dependencies": { "color-convert": "^2.0.1" @@ -2325,20 +2165,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "license": "ISC", - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, "node_modules/archiver": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/archiver/-/archiver-7.0.1.tgz", @@ -2494,6 +2320,7 @@ "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, "funding": [ { "type": "github", @@ -2510,19 +2337,6 @@ ], "license": "MIT" }, - "node_modules/binary-extensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/birpc": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/birpc/-/birpc-2.4.0.tgz", @@ -2533,17 +2347,6 @@ "url": "https://github.com/sponsors/antfu" } }, - "node_modules/bl": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", - "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", - "license": "MIT", - "dependencies": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } - }, "node_modules/brace-expansion": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", @@ -2554,43 +2357,6 @@ "balanced-match": "^1.0.0" } }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, - "license": "MIT", - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "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": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, "node_modules/buffer-crc32": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-1.0.0.tgz", @@ -2621,18 +2387,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/chalk": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", - "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, "node_modules/character-entities-html4": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", @@ -2655,12 +2409,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/chardet": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", - "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", - "license": "MIT" - }, "node_modules/chevrotain": { "version": "11.0.3", "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-11.0.3.tgz", @@ -2689,77 +2437,11 @@ "chevrotain": "^11.0.0" } }, - "node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dev": true, - "license": "MIT", - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/cli-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", - "license": "MIT", - "dependencies": { - "restore-cursor": "^3.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cli-spinners": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", - "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", - "license": "MIT", - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cli-width": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", - "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", - "license": "ISC", - "engines": { - "node": ">= 12" - } - }, - "node_modules/clone": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", - "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", - "license": "MIT", - "engines": { - "node": ">=0.8" - } - }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -2772,6 +2454,7 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, "license": "MIT" }, "node_modules/comma-separated-tokens": { @@ -2854,13 +2537,6 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, - "license": "MIT" - }, "node_modules/confbox": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.2.tgz", @@ -3323,6 +2999,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "dev": true, "license": "ISC", "engines": { "node": ">=12" @@ -3448,6 +3125,7 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "dev": true, "license": "ISC", "dependencies": { "d3-path": "^3.1.0" @@ -3565,18 +3243,6 @@ } } }, - "node_modules/defaults": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", - "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", - "license": "MIT", - "dependencies": { - "clone": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/delaunator": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.1.tgz", @@ -3600,6 +3266,7 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -3619,12 +3286,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/diff-match-patch": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/diff-match-patch/-/diff-match-patch-1.0.5.tgz", - "integrity": "sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==", - "license": "Apache-2.0" - }, "node_modules/dompurify": { "version": "3.2.6", "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.6.tgz", @@ -3646,6 +3307,7 @@ "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/emoji-regex-xs": { @@ -3743,32 +3405,6 @@ "dev": true, "license": "MIT" }, - "node_modules/external-editor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", - "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", - "license": "MIT", - "dependencies": { - "chardet": "^0.7.0", - "iconv-lite": "^0.4.24", - "tmp": "^0.0.33" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/external-editor/node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/fast-fifo": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", @@ -3776,19 +3412,6 @@ "dev": true, "license": "MIT" }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, - "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/focus-trap": { "version": "7.6.5", "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-7.6.5.tgz", @@ -3878,19 +3501,6 @@ "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": "15.15.0", "resolved": "https://registry.npmjs.org/globals/-/globals-15.15.0.tgz", @@ -3918,15 +3528,6 @@ "dev": true, "license": "MIT" }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/hast-util-to-html": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz", @@ -4015,6 +3616,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, "funding": [ { "type": "github", @@ -4031,42 +3633,12 @@ ], "license": "BSD-3-Clause" }, - "node_modules/ignore-by-default": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", - "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", - "dev": true, - "license": "ISC" - }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "license": "ISC" }, - "node_modules/inquirer": { - "version": "9.3.7", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-9.3.7.tgz", - "integrity": "sha512-LJKFHCSeIRq9hanN14IlOtPSTe3lNES7TYDTE2xxdAy1LS5rYphajK1qtwvj3YmQXvvk0U2Vbmcni8P9EIQW9w==", - "license": "MIT", - "dependencies": { - "@inquirer/figures": "^1.0.3", - "ansi-escapes": "^4.3.2", - "cli-width": "^4.1.0", - "external-editor": "^3.1.0", - "mute-stream": "1.0.0", - "ora": "^5.4.1", - "run-async": "^3.0.0", - "rxjs": "^7.8.1", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^6.2.0", - "yoctocolors-cjs": "^2.1.2" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/internmap": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", @@ -4077,70 +3649,16 @@ "node": ">=12" } }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "license": "MIT", - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "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", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "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-interactive": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", - "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", - "license": "MIT", "engines": { "node": ">=8" } }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, "node_modules/is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", @@ -4154,18 +3672,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-what": { "version": "4.1.16", "resolved": "https://registry.npmjs.org/is-what/-/is-what-4.1.16.tgz", @@ -4213,32 +3719,11 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, "license": "MIT", + "optional": true, "peer": true }, - "node_modules/json-schema": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", - "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", - "license": "(AFL-2.1 OR BSD-3-Clause)" - }, - "node_modules/jsondiffpatch": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/jsondiffpatch/-/jsondiffpatch-0.6.0.tgz", - "integrity": "sha512-3QItJOXp2AP1uv7waBkao5nCvhEv+QmJAd38Ybq7wNI74Q+BBmnLn4EDKz6yI9xGAIQoUF87qHt+kc1IVxB4zQ==", - "license": "MIT", - "dependencies": { - "@types/diff-match-patch": "^1.0.36", - "chalk": "^5.3.0", - "diff-match-patch": "^1.0.5" - }, - "bin": { - "jsondiffpatch": "bin/jsondiffpatch.js" - }, - "engines": { - "node": "^18.0.0 || >=20.0.0" - } - }, "node_modules/katex": { "version": "0.16.22", "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.22.tgz", @@ -4381,43 +3866,13 @@ "dev": true, "license": "MIT" }, - "node_modules/log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "license": "MIT", - "dependencies": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/log-symbols/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, "license": "MIT", + "optional": true, "peer": true, "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" @@ -4608,15 +4063,6 @@ ], "license": "MIT" }, - "node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/minimatch": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", @@ -4696,19 +4142,11 @@ "dev": true, "license": "MIT" }, - "node_modules/mute-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-1.0.0.tgz", - "integrity": "sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==", - "license": "ISC", - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, "node_modules/nanoid": { "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, "funding": [ { "type": "github", @@ -4723,82 +4161,6 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, - "node_modules/nodemon": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.10.tgz", - "integrity": "sha512-WDjw3pJ0/0jMFmyNDp3gvY2YizjLmmOUQo6DEBY+JgdvW/yQ9mEeSw6H5ythl5Ny2ytb7f9C2nIbjSxMNzbJXw==", - "dev": true, - "license": "MIT", - "dependencies": { - "chokidar": "^3.5.2", - "debug": "^4", - "ignore-by-default": "^1.0.1", - "minimatch": "^3.1.2", - "pstree.remy": "^1.1.8", - "semver": "^7.5.3", - "simple-update-notifier": "^2.0.0", - "supports-color": "^5.5.0", - "touch": "^3.1.0", - "undefsafe": "^2.0.5" - }, - "bin": { - "nodemon": "bin/nodemon.js" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/nodemon" - } - }, - "node_modules/nodemon/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/nodemon/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/nodemon/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/nodemon/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/non-layered-tidy-tree-layout": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/non-layered-tidy-tree-layout/-/non-layered-tidy-tree-layout-2.0.2.tgz", @@ -4817,21 +4179,6 @@ "node": ">=0.10.0" } }, - "node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "license": "MIT", - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/oniguruma-to-es": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-3.1.1.tgz", @@ -4844,54 +4191,6 @@ "regex-recursion": "^6.0.2" } }, - "node_modules/ora": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", - "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", - "license": "MIT", - "dependencies": { - "bl": "^4.1.0", - "chalk": "^4.1.0", - "cli-cursor": "^3.1.0", - "cli-spinners": "^2.5.0", - "is-interactive": "^1.0.0", - "is-unicode-supported": "^0.1.0", - "log-symbols": "^4.1.0", - "strip-ansi": "^6.0.0", - "wcwidth": "^1.0.1" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ora/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/package-json-from-dist": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", @@ -4961,19 +4260,6 @@ "dev": true, "license": "ISC" }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/pkg-types": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.2.0.tgz", @@ -5081,13 +4367,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/pstree.remy": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", - "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", - "dev": true, - "license": "MIT" - }, "node_modules/quansync": { "version": "0.2.10", "resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.10.tgz", @@ -5124,7 +4403,9 @@ "version": "18.3.1", "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "dev": true, "license": "MIT", + "optional": true, "peer": true, "dependencies": { "loose-envify": "^1.1.0" @@ -5133,20 +4414,6 @@ "node": ">=0.10.0" } }, - "node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/readdir-glob": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.3.tgz", @@ -5170,19 +4437,6 @@ "node": ">=10" } }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "license": "MIT", - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, "node_modules/regex": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/regex/-/regex-6.0.1.tgz", @@ -5220,19 +4474,6 @@ "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" } }, - "node_modules/restore-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", - "license": "MIT", - "dependencies": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/rfdc": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", @@ -5300,15 +4541,6 @@ "points-on-path": "^0.2.1" } }, - "node_modules/run-async": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-3.0.0.tgz", - "integrity": "sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==", - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, "node_modules/rw": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", @@ -5316,19 +4548,11 @@ "dev": true, "license": "BSD-3-Clause" }, - "node_modules/rxjs": { - "version": "7.8.2", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", - "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.1.0" - } - }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, "funding": [ { "type": "github", @@ -5359,25 +4583,6 @@ "license": "MIT", "peer": true }, - "node_modules/secure-json-parse": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.7.0.tgz", - "integrity": "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==", - "license": "BSD-3-Clause" - }, - "node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", @@ -5424,25 +4629,6 @@ "@types/hast": "^3.0.4" } }, - "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==", - "license": "ISC" - }, - "node_modules/simple-update-notifier": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", - "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "semver": "^7.5.3" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -5501,6 +4687,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, "license": "MIT", "dependencies": { "safe-buffer": "~5.2.0" @@ -5510,6 +4697,7 @@ "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", @@ -5555,6 +4743,7 @@ "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" @@ -5597,31 +4786,6 @@ "node": ">=16" } }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/swr": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/swr/-/swr-2.3.4.tgz", - "integrity": "sha512-bYd2lrhc+VarcpkgWclcUi92wYCpOgMws9Sd1hG1ntAu0NEy+14CbotuFjshBU2kt9rYj9TSmDcybpxpeTU1fg==", - "license": "MIT", - "dependencies": { - "dequal": "^2.0.3", - "use-sync-external-store": "^1.4.0" - }, - "peerDependencies": { - "react": "^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" - } - }, "node_modules/tabbable": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", @@ -5651,18 +4815,6 @@ "b4a": "^1.6.4" } }, - "node_modules/throttleit": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-2.1.0.tgz", - "integrity": "sha512-nt6AMGKW1p/70DF/hGBdJB57B8Tspmbp5gfJ8ilhLnt7kkr2ye7hzD6NVG8GGErk2HWF34igrL2CXmNIkzKqKw==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/tinyexec": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.1.tgz", @@ -5670,31 +4822,6 @@ "dev": true, "license": "MIT" }, - "node_modules/tmp": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", - "license": "MIT", - "dependencies": { - "os-tmpdir": "~1.0.2" - }, - "engines": { - "node": ">=0.6.0" - } - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, "node_modules/toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", @@ -5704,16 +4831,6 @@ "node": ">=0.6" } }, - "node_modules/touch": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", - "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", - "dev": true, - "license": "ISC", - "bin": { - "nodetouch": "bin/nodetouch.js" - } - }, "node_modules/trim-lines": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", @@ -5735,12 +4852,6 @@ "node": ">=6.10" } }, - "node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "license": "0BSD" - }, "node_modules/tsx": { "version": "4.20.3", "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.20.3.tgz", @@ -5761,18 +4872,6 @@ "fsevents": "~2.3.3" } }, - "node_modules/type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/typescript": { "version": "5.8.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", @@ -5794,13 +4893,6 @@ "dev": true, "license": "MIT" }, - "node_modules/undefsafe": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", - "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", - "dev": true, - "license": "MIT" - }, "node_modules/undici-types": { "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", @@ -5890,19 +4982,11 @@ "node": ">= 0.8" } }, - "node_modules/use-sync-external-store": { - "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" - } - }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, "license": "MIT" }, "node_modules/uuid": { @@ -6572,15 +5656,6 @@ } } }, - "node_modules/wcwidth": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", - "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", - "license": "MIT", - "dependencies": { - "defaults": "^1.0.3" - } - }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -6597,20 +5672,6 @@ "node": ">= 8" } }, - "node_modules/wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/wrap-ansi-cjs": { "name": "wrap-ansi", "version": "7.0.0", @@ -6630,18 +5691,6 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/yoctocolors-cjs": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.2.tgz", - "integrity": "sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/zip-stream": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-6.0.1.tgz", diff --git a/package.json b/package.json index 2a547c7..087a6ee 100644 --- a/package.json +++ b/package.json @@ -47,16 +47,11 @@ ], "dependencies": { "@modelcontextprotocol/sdk": "^0.5.0", - "ai": "^4.3.17", - "chalk": "^5.0.0", - "d3-shape": "^3.2.0", - "inquirer": "^9.0.0", "prismjs": "^1.30.0", "zod": "^3.25.76", "zod-to-json-schema": "^3.24.6" }, "devDependencies": { - "@types/inquirer": "^9.0.0", "@types/node": "^20.0.0", "archiver": "^7.0.1", "mermaid": "^11.9.0", diff --git a/src/utils/commandExecutor.ts b/src/utils/commandExecutor.ts index b9b6f1b..4086b12 100644 --- a/src/utils/commandExecutor.ts +++ b/src/utils/commandExecutor.ts @@ -1,27 +1,59 @@ import { spawn } from "child_process"; import { Logger } from "./logger.js"; +// Sanitize command and arguments to prevent shell injection and flag injection +function sanitizeInput(input: string): string { + // Remove or escape dangerous shell metacharacters + let sanitized = input.replace(/[;&|`$(){}[\]<>]/g, ''); + // Prevent flag injection by removing leading dashes + sanitized = sanitized.replace(/^-+/, ''); + return sanitized; +} + +function validateCommand(command: string): boolean { + // Only allow specific whitelisted commands + const allowedCommands = ['gemini', 'gemini.exe', 'echo']; + const baseCommand = command.split(/[/\\]/).pop()?.toLowerCase() || ''; + return allowedCommands.includes(baseCommand); +} + export async function executeCommand( command: string, args: string[], onProgress?: (newOutput: string) => void ): Promise { return new Promise((resolve, reject) => { + // Validate command before execution + if (!validateCommand(command)) { + reject(new Error(`Command not allowed: ${command}. Only gemini, gemini.exe, and npx are permitted.`)); + return; + } + + // Sanitize arguments to prevent shell injection + const sanitizedArgs = args.map(arg => sanitizeInput(arg)); + const startTime = Date.now(); - Logger.commandExecution(command, args, startTime); + Logger.commandExecution(command, sanitizedArgs, startTime); - const childProcess = spawn(command, args, { + const childProcess = spawn(command, sanitizedArgs, { env: process.env, - shell: false, + shell: true, stdio: ["ignore", "pipe", "pipe"], + cwd: process.cwd() }); + if (!childProcess.stdout || !childProcess.stderr) { + childProcess.kill(); + reject(new Error("Process spawning failed: streams not available")); + return; + } + let stdout = ""; let stderr = ""; let isResolved = false; let lastReportedLength = 0; - childProcess.stdout.on("data", (data) => { + childProcess.stdout.on("data", (data: Buffer) => { stdout += data.toString(); // Report new content if callback provided @@ -34,7 +66,7 @@ export async function executeCommand( // CLI level errors - childProcess.stderr.on("data", (data) => { + childProcess.stderr.on("data", (data: Buffer) => { stderr += data.toString(); // find RESOURCE_EXHAUSTED when gemini-2.5-pro quota is exceeded if (stderr.includes("RESOURCE_EXHAUSTED")) { @@ -58,14 +90,14 @@ export async function executeCommand( Logger.error(`Gemini Quota Error: ${JSON.stringify(errorJson, null, 2)}`); } }); - childProcess.on("error", (error) => { + childProcess.on("error", (error: Error) => { if (!isResolved) { isResolved = true; Logger.error(`Process error:`, error); reject(new Error(`Failed to spawn command: ${error.message}`)); } }); - childProcess.on("close", (code) => { + childProcess.on("close", (code: number | null) => { if (!isResolved) { isResolved = true; if (code === 0) { diff --git a/src/utils/geminiExecutor.ts b/src/utils/geminiExecutor.ts index f7e79d3..6397ac1 100644 --- a/src/utils/geminiExecutor.ts +++ b/src/utils/geminiExecutor.ts @@ -91,10 +91,10 @@ ${prompt_processed} if (model) { args.push(CLI.FLAGS.MODEL, model); } if (sandbox) { args.push(CLI.FLAGS.SANDBOX); } - // Ensure @ symbols work cross-platform by wrapping in quotes if needed - const finalPrompt = prompt_processed.includes('@') && !prompt_processed.startsWith('"') - ? `"${prompt_processed}"` - : prompt_processed; + // Always quote the prompt to prevent shell argument splitting + const finalPrompt = prompt_processed.startsWith('"') && prompt_processed.endsWith('"') + ? prompt_processed + : `"${prompt_processed.replace(/"/g, '\\"')}"`; args.push(CLI.FLAGS.PROMPT, finalPrompt); @@ -111,10 +111,10 @@ ${prompt_processed} fallbackArgs.push(CLI.FLAGS.SANDBOX); } - // Same @ symbol handling for fallback - const fallbackPrompt = prompt_processed.includes('@') && !prompt_processed.startsWith('"') - ? `"${prompt_processed}"` - : prompt_processed; + // Same quoting handling for fallback + const fallbackPrompt = prompt_processed.startsWith('"') && prompt_processed.endsWith('"') + ? prompt_processed + : `"${prompt_processed.replace(/"/g, '\\"')}"`; fallbackArgs.push(CLI.FLAGS.PROMPT, fallbackPrompt); try { From 75c2991c7bbb5246715fc3fae96c125a56604d23 Mon Sep 17 00:00:00 2001 From: jamubc <150970140+jamubc@users.noreply.github.com> Date: Tue, 5 Aug 2025 13:34:03 -0700 Subject: [PATCH 02/20] shell + escaping --- src/utils/commandExecutor.ts | 71 ++++++++++++++++++++++++++++++------ 1 file changed, 60 insertions(+), 11 deletions(-) diff --git a/src/utils/commandExecutor.ts b/src/utils/commandExecutor.ts index 4086b12..79adc11 100644 --- a/src/utils/commandExecutor.ts +++ b/src/utils/commandExecutor.ts @@ -3,29 +3,78 @@ import { Logger } from "./logger.js"; // Sanitize command and arguments to prevent shell injection and flag injection function sanitizeInput(input: string): string { - // Remove or escape dangerous shell metacharacters - let sanitized = input.replace(/[;&|`$(){}[\]<>]/g, ''); - // Prevent flag injection by removing leading dashes - sanitized = sanitized.replace(/^-+/, ''); - return sanitized; + // Known safe flags that should not be sanitized + const knownFlags = ['-m', '-p', '-s', '-d', '-a', '-y', '-c', '-v', '-h', '--model', '--prompt', '--sandbox', '--debug', '--all_files', '--yolo', '--checkpointing', '--version', '--help']; + + // If this is a known flag, return it as-is + if (knownFlags.includes(input)) { + return input; + } + + if (process.platform === "win32") { + // Windows/PowerShell specific escaping + let sanitized = input; + + // Escape PowerShell special characters + sanitized = sanitized.replace(/[$`]/g, '`$&'); + + // Handle quotes properly for Windows + if (sanitized.includes(' ') && !sanitized.startsWith('"')) { + sanitized = `"${sanitized.replace(/"/g, '""')}"`; + } + + // Prevent flag injection (but not for known flags) + sanitized = sanitized.replace(/^-+/, ''); + + return sanitized; + } else { + // Unix-like systems + let sanitized = input.replace(/[;&|`$(){}[\]<>]/g, ''); + // Prevent flag injection (but not for known flags) + sanitized = sanitized.replace(/^-+/, ''); + return sanitized; + } } function validateCommand(command: string): boolean { // Only allow specific whitelisted commands - const allowedCommands = ['gemini', 'gemini.exe', 'echo']; + const allowedCommands = ['gemini', 'gemini.exe', 'gemini.cmd', 'gemini.bat', 'echo', 'echo.exe']; const baseCommand = command.split(/[/\\]/).pop()?.toLowerCase() || ''; return allowedCommands.includes(baseCommand); } +function resolveWindowsCommand(command: string): string { + if (process.platform !== "win32") return command; + + // Check if command already has extension + if (command.endsWith('.exe') || command.endsWith('.cmd') || command.endsWith('.bat')) { + return command; + } + + // Try to resolve with Windows extensions + const extensions = ['.exe', '.cmd', '.bat', '']; + for (const ext of extensions) { + const cmdWithExt = command + ext; + // Check if exists in PATH or is absolute path + if (validateCommand(cmdWithExt)) { + return cmdWithExt; + } + } + return command; +} + + export async function executeCommand( command: string, args: string[], onProgress?: (newOutput: string) => void ): Promise { + const resolvedCommand = resolveWindowsCommand(command); + return new Promise((resolve, reject) => { // Validate command before execution - if (!validateCommand(command)) { - reject(new Error(`Command not allowed: ${command}. Only gemini, gemini.exe, and npx are permitted.`)); + if (!validateCommand(resolvedCommand)) { + reject(new Error(`Command not allowed: ${resolvedCommand}. Only gemini, gemini.exe, and npx are permitted.`)); return; } @@ -33,11 +82,11 @@ export async function executeCommand( const sanitizedArgs = args.map(arg => sanitizeInput(arg)); const startTime = Date.now(); - Logger.commandExecution(command, sanitizedArgs, startTime); + Logger.commandExecution(resolvedCommand, sanitizedArgs, startTime); - const childProcess = spawn(command, sanitizedArgs, { + const childProcess = spawn(resolvedCommand, sanitizedArgs, { env: process.env, - shell: true, + shell: process.platform === "win32", stdio: ["ignore", "pipe", "pipe"], cwd: process.cwd() }); From 5b6634252c0791c5ca8b3aedb4a76867d15a4f67 Mon Sep 17 00:00:00 2001 From: jamubc <150970140+jamubc@users.noreply.github.com> Date: Tue, 5 Aug 2025 14:18:03 -0700 Subject: [PATCH 03/20] gemini reference issue --- src/utils/commandExecutor.ts | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/src/utils/commandExecutor.ts b/src/utils/commandExecutor.ts index 79adc11..4af7e81 100644 --- a/src/utils/commandExecutor.ts +++ b/src/utils/commandExecutor.ts @@ -37,10 +37,12 @@ function sanitizeInput(input: string): string { } function validateCommand(command: string): boolean { - // Only allow specific whitelisted commands - const allowedCommands = ['gemini', 'gemini.exe', 'gemini.cmd', 'gemini.bat', 'echo', 'echo.exe']; + // Only allow specific whitelisted commands (without extensions) + const allowedCommands = ['gemini', 'echo']; const baseCommand = command.split(/[/\\]/).pop()?.toLowerCase() || ''; - return allowedCommands.includes(baseCommand); + // Remove any extension for validation + const commandWithoutExt = baseCommand.replace(/\.(exe|cmd|bat|sh)$/, ''); + return allowedCommands.includes(commandWithoutExt); } function resolveWindowsCommand(command: string): string { @@ -51,15 +53,8 @@ function resolveWindowsCommand(command: string): string { return command; } - // Try to resolve with Windows extensions - const extensions = ['.exe', '.cmd', '.bat', '']; - for (const ext of extensions) { - const cmdWithExt = command + ext; - // Check if exists in PATH or is absolute path - if (validateCommand(cmdWithExt)) { - return cmdWithExt; - } - } + // For Windows, just return the command as-is and let Windows handle resolution + // Windows will automatically try .exe, .cmd, .bat extensions return command; } From 4e529e02d1c1cc881ccf78635b60a3f1eb305777 Mon Sep 17 00:00:00 2001 From: jamubc <150970140+jamubc@users.noreply.github.com> Date: Tue, 5 Aug 2025 17:32:42 -0700 Subject: [PATCH 04/20] quotes --- src/utils/commandExecutor.ts | 14 +++++++++++--- src/utils/geminiExecutor.ts | 16 ++++------------ 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/utils/commandExecutor.ts b/src/utils/commandExecutor.ts index 4af7e81..9a3af1e 100644 --- a/src/utils/commandExecutor.ts +++ b/src/utils/commandExecutor.ts @@ -15,16 +15,24 @@ function sanitizeInput(input: string): string { // Windows/PowerShell specific escaping let sanitized = input; + // Don't double-escape already quoted strings + if (sanitized.startsWith('"') && sanitized.endsWith('"')) { + // Already quoted, just escape internal quotes if needed + return sanitized; + } + // Escape PowerShell special characters sanitized = sanitized.replace(/[$`]/g, '`$&'); - // Handle quotes properly for Windows - if (sanitized.includes(' ') && !sanitized.startsWith('"')) { + // Handle quotes properly for Windows - only add quotes if there are spaces + if (sanitized.includes(' ')) { sanitized = `"${sanitized.replace(/"/g, '""')}"`; } // Prevent flag injection (but not for known flags) - sanitized = sanitized.replace(/^-+/, ''); + if (!sanitized.startsWith('"')) { + sanitized = sanitized.replace(/^-+/, ''); + } return sanitized; } else { diff --git a/src/utils/geminiExecutor.ts b/src/utils/geminiExecutor.ts index 6397ac1..89e5c2f 100644 --- a/src/utils/geminiExecutor.ts +++ b/src/utils/geminiExecutor.ts @@ -91,12 +91,8 @@ ${prompt_processed} if (model) { args.push(CLI.FLAGS.MODEL, model); } if (sandbox) { args.push(CLI.FLAGS.SANDBOX); } - // Always quote the prompt to prevent shell argument splitting - const finalPrompt = prompt_processed.startsWith('"') && prompt_processed.endsWith('"') - ? prompt_processed - : `"${prompt_processed.replace(/"/g, '\\"')}"`; - - args.push(CLI.FLAGS.PROMPT, finalPrompt); + // Pass prompt without pre-quoting - let the platform-specific handling in commandExecutor deal with it + args.push(CLI.FLAGS.PROMPT, prompt_processed); try { return await executeCommand(CLI.COMMANDS.GEMINI, args, onProgress); @@ -111,12 +107,8 @@ ${prompt_processed} fallbackArgs.push(CLI.FLAGS.SANDBOX); } - // Same quoting handling for fallback - const fallbackPrompt = prompt_processed.startsWith('"') && prompt_processed.endsWith('"') - ? prompt_processed - : `"${prompt_processed.replace(/"/g, '\\"')}"`; - - fallbackArgs.push(CLI.FLAGS.PROMPT, fallbackPrompt); + // Pass prompt without pre-quoting - same as above + fallbackArgs.push(CLI.FLAGS.PROMPT, prompt_processed); try { const result = await executeCommand(CLI.COMMANDS.GEMINI, fallbackArgs, onProgress); Logger.warn(`Successfully executed with ${MODELS.FLASH} fallback.`); From 0be972ebdd7c8fd4255149bf13b8665b312d8aa4 Mon Sep 17 00:00:00 2001 From: jamubc <150970140+jamubc@users.noreply.github.com> Date: Tue, 5 Aug 2025 17:36:53 -0700 Subject: [PATCH 05/20] 3 --- src/utils/commandExecutor.ts | 23 +++++------------------ 1 file changed, 5 insertions(+), 18 deletions(-) diff --git a/src/utils/commandExecutor.ts b/src/utils/commandExecutor.ts index 9a3af1e..a247e21 100644 --- a/src/utils/commandExecutor.ts +++ b/src/utils/commandExecutor.ts @@ -12,27 +12,14 @@ function sanitizeInput(input: string): string { } if (process.platform === "win32") { - // Windows/PowerShell specific escaping + // Windows: properly escape for command line let sanitized = input; - // Don't double-escape already quoted strings - if (sanitized.startsWith('"') && sanitized.endsWith('"')) { - // Already quoted, just escape internal quotes if needed - return sanitized; - } - - // Escape PowerShell special characters - sanitized = sanitized.replace(/[$`]/g, '`$&'); - - // Handle quotes properly for Windows - only add quotes if there are spaces - if (sanitized.includes(' ')) { - sanitized = `"${sanitized.replace(/"/g, '""')}"`; - } + // Escape existing quotes by doubling them (Windows convention) + sanitized = sanitized.replace(/"/g, '""'); - // Prevent flag injection (but not for known flags) - if (!sanitized.startsWith('"')) { - sanitized = sanitized.replace(/^-+/, ''); - } + // Always wrap in quotes for Windows to handle spaces and newlines + sanitized = `"${sanitized}"`; return sanitized; } else { From 5e2b33ded5f37ce25470b3d672ea4af93ea39845 Mon Sep 17 00:00:00 2001 From: jamubc <150970140+jamubc@users.noreply.github.com> Date: Tue, 5 Aug 2025 20:18:33 -0700 Subject: [PATCH 06/20] 4 --- src/utils/commandExecutor.ts | 33 +++++++++++---------------------- src/utils/geminiExecutor.ts | 8 ++++---- 2 files changed, 15 insertions(+), 26 deletions(-) diff --git a/src/utils/commandExecutor.ts b/src/utils/commandExecutor.ts index a247e21..2c0be3d 100644 --- a/src/utils/commandExecutor.ts +++ b/src/utils/commandExecutor.ts @@ -3,32 +3,21 @@ import { Logger } from "./logger.js"; // Sanitize command and arguments to prevent shell injection and flag injection function sanitizeInput(input: string): string { - // Known safe flags that should not be sanitized - const knownFlags = ['-m', '-p', '-s', '-d', '-a', '-y', '-c', '-v', '-h', '--model', '--prompt', '--sandbox', '--debug', '--all_files', '--yolo', '--checkpointing', '--version', '--help']; - - // If this is a known flag, return it as-is - if (knownFlags.includes(input)) { - return input; + // Don't sanitize known flags + if (input.startsWith('-')) { + return input; // Keep flags as-is } + // For file paths and other arguments, just handle backslashes for Windows if (process.platform === "win32") { - // Windows: properly escape for command line - let sanitized = input; - - // Escape existing quotes by doubling them (Windows convention) - sanitized = sanitized.replace(/"/g, '""'); - - // Always wrap in quotes for Windows to handle spaces and newlines - sanitized = `"${sanitized}"`; - - return sanitized; - } else { - // Unix-like systems - let sanitized = input.replace(/[;&|`$(){}[\]<>]/g, ''); - // Prevent flag injection (but not for known flags) - sanitized = sanitized.replace(/^-+/, ''); - return sanitized; + // Windows paths use backslashes, which need to be escaped in some contexts + // But for gemini CLI, we should keep them as-is since it expects Windows paths + return input; } + + // For Unix-like systems, basic sanitization + let sanitized = input.replace(/[;&|`$(){}[\]<>]/g, ''); + return sanitized; } function validateCommand(command: string): boolean { diff --git a/src/utils/geminiExecutor.ts b/src/utils/geminiExecutor.ts index 89e5c2f..4d6a563 100644 --- a/src/utils/geminiExecutor.ts +++ b/src/utils/geminiExecutor.ts @@ -91,8 +91,8 @@ ${prompt_processed} if (model) { args.push(CLI.FLAGS.MODEL, model); } if (sandbox) { args.push(CLI.FLAGS.SANDBOX); } - // Pass prompt without pre-quoting - let the platform-specific handling in commandExecutor deal with it - args.push(CLI.FLAGS.PROMPT, prompt_processed); + // Wrap prompt in quotes to handle spaces and special characters + args.push(CLI.FLAGS.PROMPT, `"${prompt_processed.replace(/"/g, '\\"')}"`); try { return await executeCommand(CLI.COMMANDS.GEMINI, args, onProgress); @@ -107,8 +107,8 @@ ${prompt_processed} fallbackArgs.push(CLI.FLAGS.SANDBOX); } - // Pass prompt without pre-quoting - same as above - fallbackArgs.push(CLI.FLAGS.PROMPT, prompt_processed); + // Wrap prompt in quotes to handle spaces and special characters + fallbackArgs.push(CLI.FLAGS.PROMPT, `"${prompt_processed.replace(/"/g, '\\"')}"`); try { const result = await executeCommand(CLI.COMMANDS.GEMINI, fallbackArgs, onProgress); Logger.warn(`Successfully executed with ${MODELS.FLASH} fallback.`); From 961b873a9d174dd8efe40044eb469902d2b92177 Mon Sep 17 00:00:00 2001 From: alexx-ftw Date: Wed, 23 Jul 2025 14:39:26 +0100 Subject: [PATCH 07/20] fix windows spawn --- AGENTS.md | 16 ++++++++++++++++ docs/resources/troubleshooting.md | 2 ++ src/utils/commandExecutor.ts | 5 +++++ 3 files changed, 23 insertions(+) create mode 100644 AGENTS.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..ca309dc --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,16 @@ +# AGENT Guidance + +## Development +- Use TypeScript with an object-oriented approach when adding new features. +- Follow test-driven development. Add or update tests under `src` and run `npm test`. +- Lint code with `npm run lint` before committing. +- Build with `npm run build` to ensure TypeScript compiles. + +## Windows Compatibility +- The command executor uses `shell: process.platform === "win32"` to avoid ENOENT errors. +- Documented in `docs/resources/troubleshooting.md`. + +## Repository Scripts +- `npm test` – runs the test suite (currently a placeholder). +- `npm run lint` – checks TypeScript types via `tsc --noEmit`. +- `npm run build` – compiles TypeScript to `dist/`. diff --git a/docs/resources/troubleshooting.md b/docs/resources/troubleshooting.md index 0a7c914..40972fa 100644 --- a/docs/resources/troubleshooting.md +++ b/docs/resources/troubleshooting.md @@ -342,6 +342,8 @@ gemini "Hello" - **NPX flag issues**: Use `--yes` instead of `-y` - **Path problems**: Restart terminal after Node.js installation - **Connection issues**: Ensure Windows Defender isn't blocking Node.js +- **Spawn ENOENT errors**: Upgrade to gemini-mcp-tool >=1.1.5 or ensure the + command executor sets `shell: process.platform === "win32"` ### macOS - **Permission issues**: Use `sudo` if npm install fails diff --git a/src/utils/commandExecutor.ts b/src/utils/commandExecutor.ts index 2c0be3d..fec5fe4 100644 --- a/src/utils/commandExecutor.ts +++ b/src/utils/commandExecutor.ts @@ -65,6 +65,11 @@ export async function executeCommand( const childProcess = spawn(resolvedCommand, sanitizedArgs, { env: process.env, +<<<<<<< HEAD +======= + // Windows requires spawning through the shell to resolve PATH correctly + // See: https://github.com/jamubc/gemini-mcp-tool/issues/9 +>>>>>>> f91e2d3 (fix windows spawn) shell: process.platform === "win32", stdio: ["ignore", "pipe", "pipe"], cwd: process.cwd() From f5014e702ec6806b52722043a494cc9b25271803 Mon Sep 17 00:00:00 2001 From: jamubc <150970140+jamubc@users.noreply.github.com> Date: Mon, 11 Aug 2025 00:57:25 -0700 Subject: [PATCH 08/20] cherry pick windows solution --- src/utils/geminiExecutor.ts | 12 +++++++++++ tests/geminiExecutor.test.ts | 42 ++++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+) create mode 100644 tests/geminiExecutor.test.ts diff --git a/src/utils/geminiExecutor.ts b/src/utils/geminiExecutor.ts index 4d6a563..1b469d7 100644 --- a/src/utils/geminiExecutor.ts +++ b/src/utils/geminiExecutor.ts @@ -91,6 +91,12 @@ ${prompt_processed} if (model) { args.push(CLI.FLAGS.MODEL, model); } if (sandbox) { args.push(CLI.FLAGS.SANDBOX); } + // Quote and escape prompt when executed through a shell (Windows) + const finalPrompt = process.platform === 'win32' + ? `"${prompt_processed.replace(/"/g, '""')}"` + : prompt_processed; + + args.push(CLI.FLAGS.PROMPT, finalPrompt); // Wrap prompt in quotes to handle spaces and special characters args.push(CLI.FLAGS.PROMPT, `"${prompt_processed.replace(/"/g, '\\"')}"`); @@ -107,6 +113,12 @@ ${prompt_processed} fallbackArgs.push(CLI.FLAGS.SANDBOX); } + // Same quoting logic for fallback + const fallbackPrompt = process.platform === 'win32' + ? `"${prompt_processed.replace(/"/g, '""')}"` + : prompt_processed; + + fallbackArgs.push(CLI.FLAGS.PROMPT, fallbackPrompt); // Wrap prompt in quotes to handle spaces and special characters fallbackArgs.push(CLI.FLAGS.PROMPT, `"${prompt_processed.replace(/"/g, '\\"')}"`); try { diff --git a/tests/geminiExecutor.test.ts b/tests/geminiExecutor.test.ts new file mode 100644 index 0000000..3c6a112 --- /dev/null +++ b/tests/geminiExecutor.test.ts @@ -0,0 +1,42 @@ +import { describe, it, afterEach, expect, vi } from 'vitest'; +import { spawn } from 'child_process'; +import { executeGeminiCLI } from '../src/utils/geminiExecutor.js'; + +vi.mock('child_process', () => ({ + spawn: vi.fn(), +})); + +describe('executeGeminiCLI', () => { + const originalPlatform = process.platform; + + afterEach(() => { + vi.resetAllMocks(); + Object.defineProperty(process, 'platform', { value: originalPlatform }); + }); + + it('quotes prompt on win32 to avoid shell parsing issues', async () => { + Object.defineProperty(process, 'platform', { value: 'win32' }); + const mockStdoutOn = vi.fn(); + const mockStderrOn = vi.fn(); + let closeCallback: (code: number) => void = () => {}; + const mockOn = vi.fn((event: string, cb: (code: number) => void) => { + if (event === 'close') { + closeCallback = cb; + } + }); + (spawn as unknown as vi.Mock).mockReturnValue({ + stdout: { on: mockStdoutOn }, + stderr: { on: mockStderrOn }, + on: mockOn, + }); + + const prompt = "Perfect! I've implemented your recommendations: fixed bug"; + const promise = executeGeminiCLI(prompt); + closeCallback(0); + await promise; + + const args = (spawn as unknown as vi.Mock).mock.calls[0][1] as string[]; + const promptArgIndex = args.indexOf('-p') + 1; + expect(args[promptArgIndex]).toBe(`"${prompt.replace(/"/g, '""')}"`); + }); +}); From 0a7ae247bc8a3d1ad06513482d08ac812d7eb125 Mon Sep 17 00:00:00 2001 From: jamubc <150970140+jamubc@users.noreply.github.com> Date: Mon, 11 Aug 2025 00:59:57 -0700 Subject: [PATCH 09/20] remove AGENTS.md Should be a local development file. --- AGENTS.md | 16 ---------------- 1 file changed, 16 deletions(-) delete mode 100644 AGENTS.md diff --git a/AGENTS.md b/AGENTS.md deleted file mode 100644 index ca309dc..0000000 --- a/AGENTS.md +++ /dev/null @@ -1,16 +0,0 @@ -# AGENT Guidance - -## Development -- Use TypeScript with an object-oriented approach when adding new features. -- Follow test-driven development. Add or update tests under `src` and run `npm test`. -- Lint code with `npm run lint` before committing. -- Build with `npm run build` to ensure TypeScript compiles. - -## Windows Compatibility -- The command executor uses `shell: process.platform === "win32"` to avoid ENOENT errors. -- Documented in `docs/resources/troubleshooting.md`. - -## Repository Scripts -- `npm test` – runs the test suite (currently a placeholder). -- `npm run lint` – checks TypeScript types via `tsc --noEmit`. -- `npm run build` – compiles TypeScript to `dist/`. From d81ff3e57a2357661c0c8813f87b6a3b0b060c68 Mon Sep 17 00:00:00 2001 From: jamubc <150970140+jamubc@users.noreply.github.com> Date: Mon, 11 Aug 2025 01:06:03 -0700 Subject: [PATCH 10/20] Update commandExecutor.ts --- src/utils/commandExecutor.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/utils/commandExecutor.ts b/src/utils/commandExecutor.ts index fec5fe4..2c0be3d 100644 --- a/src/utils/commandExecutor.ts +++ b/src/utils/commandExecutor.ts @@ -65,11 +65,6 @@ export async function executeCommand( const childProcess = spawn(resolvedCommand, sanitizedArgs, { env: process.env, -<<<<<<< HEAD -======= - // Windows requires spawning through the shell to resolve PATH correctly - // See: https://github.com/jamubc/gemini-mcp-tool/issues/9 ->>>>>>> f91e2d3 (fix windows spawn) shell: process.platform === "win32", stdio: ["ignore", "pipe", "pipe"], cwd: process.cwd() From b5601d8c74c6717c14ce722def572761804cb55f Mon Sep 17 00:00:00 2001 From: jamubc <150970140+jamubc@users.noreply.github.com> Date: Mon, 11 Aug 2025 02:14:26 -0700 Subject: [PATCH 11/20] fixed changeMode on windows --- src/utils/commandExecutor.ts | 85 +++++++----------------------------- src/utils/geminiExecutor.ts | 54 ++++++++++++++--------- 2 files changed, 50 insertions(+), 89 deletions(-) diff --git a/src/utils/commandExecutor.ts b/src/utils/commandExecutor.ts index 2c0be3d..d9a76b0 100644 --- a/src/utils/commandExecutor.ts +++ b/src/utils/commandExecutor.ts @@ -1,87 +1,34 @@ import { spawn } from "child_process"; import { Logger } from "./logger.js"; -// Sanitize command and arguments to prevent shell injection and flag injection -function sanitizeInput(input: string): string { - // Don't sanitize known flags - if (input.startsWith('-')) { - return input; // Keep flags as-is - } - - // For file paths and other arguments, just handle backslashes for Windows - if (process.platform === "win32") { - // Windows paths use backslashes, which need to be escaped in some contexts - // But for gemini CLI, we should keep them as-is since it expects Windows paths - return input; - } - - // For Unix-like systems, basic sanitization - let sanitized = input.replace(/[;&|`$(){}[\]<>]/g, ''); - return sanitized; -} - -function validateCommand(command: string): boolean { - // Only allow specific whitelisted commands (without extensions) - const allowedCommands = ['gemini', 'echo']; - const baseCommand = command.split(/[/\\]/).pop()?.toLowerCase() || ''; - // Remove any extension for validation - const commandWithoutExt = baseCommand.replace(/\.(exe|cmd|bat|sh)$/, ''); - return allowedCommands.includes(commandWithoutExt); -} - -function resolveWindowsCommand(command: string): string { - if (process.platform !== "win32") return command; - - // Check if command already has extension - if (command.endsWith('.exe') || command.endsWith('.cmd') || command.endsWith('.bat')) { - return command; - } - - // For Windows, just return the command as-is and let Windows handle resolution - // Windows will automatically try .exe, .cmd, .bat extensions - return command; -} - - export async function executeCommand( command: string, args: string[], - onProgress?: (newOutput: string) => void + onProgress?: (newOutput: string) => void, + stdinData?: string ): Promise { - const resolvedCommand = resolveWindowsCommand(command); - return new Promise((resolve, reject) => { - // Validate command before execution - if (!validateCommand(resolvedCommand)) { - reject(new Error(`Command not allowed: ${resolvedCommand}. Only gemini, gemini.exe, and npx are permitted.`)); - return; - } - - // Sanitize arguments to prevent shell injection - const sanitizedArgs = args.map(arg => sanitizeInput(arg)); - const startTime = Date.now(); - Logger.commandExecution(resolvedCommand, sanitizedArgs, startTime); + Logger.commandExecution(command, args, startTime); - const childProcess = spawn(resolvedCommand, sanitizedArgs, { + const childProcess = spawn(command, args, { env: process.env, - shell: process.platform === "win32", - stdio: ["ignore", "pipe", "pipe"], - cwd: process.cwd() + shell: true, + stdio: stdinData ? ["pipe", "pipe", "pipe"] : ["ignore", "pipe", "pipe"], }); - if (!childProcess.stdout || !childProcess.stderr) { - childProcess.kill(); - reject(new Error("Process spawning failed: streams not available")); - return; - } - let stdout = ""; let stderr = ""; let isResolved = false; let lastReportedLength = 0; - childProcess.stdout.on("data", (data: Buffer) => { + // Write stdin data if provided + if (stdinData && childProcess.stdin) { + childProcess.stdin.write(stdinData); + childProcess.stdin.end(); + } + + childProcess.stdout?.on("data", (data) => { stdout += data.toString(); // Report new content if callback provided @@ -94,7 +41,7 @@ export async function executeCommand( // CLI level errors - childProcess.stderr.on("data", (data: Buffer) => { + childProcess.stderr?.on("data", (data) => { stderr += data.toString(); // find RESOURCE_EXHAUSTED when gemini-2.5-pro quota is exceeded if (stderr.includes("RESOURCE_EXHAUSTED")) { @@ -118,14 +65,14 @@ export async function executeCommand( Logger.error(`Gemini Quota Error: ${JSON.stringify(errorJson, null, 2)}`); } }); - childProcess.on("error", (error: Error) => { + childProcess.on("error", (error) => { if (!isResolved) { isResolved = true; Logger.error(`Process error:`, error); reject(new Error(`Failed to spawn command: ${error.message}`)); } }); - childProcess.on("close", (code: number | null) => { + childProcess.on("close", (code) => { if (!isResolved) { isResolved = true; if (code === 0) { diff --git a/src/utils/geminiExecutor.ts b/src/utils/geminiExecutor.ts index 1b469d7..3eb63d0 100644 --- a/src/utils/geminiExecutor.ts +++ b/src/utils/geminiExecutor.ts @@ -88,41 +88,55 @@ ${prompt_processed} } const args = []; - if (model) { args.push(CLI.FLAGS.MODEL, model); } + if (model) { args.push(CLI.FLAGS.MODEL, `"${model}"`); } if (sandbox) { args.push(CLI.FLAGS.SANDBOX); } - // Quote and escape prompt when executed through a shell (Windows) - const finalPrompt = process.platform === 'win32' - ? `"${prompt_processed.replace(/"/g, '""')}"` - : prompt_processed; - - args.push(CLI.FLAGS.PROMPT, finalPrompt); - // Wrap prompt in quotes to handle spaces and special characters - args.push(CLI.FLAGS.PROMPT, `"${prompt_processed.replace(/"/g, '\\"')}"`); + // For complex prompts (changeMode or with @ symbols), use stdin instead of -p flag + // This avoids all shell escaping issues with quotes and newlines + const useStdin = changeMode || prompt_processed.includes('@'); + let stdinData: string | undefined; + + if (useStdin) { + // Pass prompt via stdin to avoid shell escaping issues + stdinData = prompt_processed; + Logger.debug(`Using stdin for prompt (length: ${prompt_processed.length} chars)`); + } else { + // Simple prompts can use -p flag with proper escaping + const escapedPrompt = process.platform === 'win32' + ? prompt_processed.replace(/"/g, '""') // Windows: escape quotes by doubling + : prompt_processed.replace(/"/g, '\\"'); // Unix: escape with backslash + args.push(CLI.FLAGS.PROMPT, `"${escapedPrompt}"`); + } + + // Log the exact command being executed for debugging + Logger.debug(`Executing command: ${CLI.COMMANDS.GEMINI} ${args.join(' ')}${useStdin ? ' [prompt via stdin]' : ''}`); try { - return await executeCommand(CLI.COMMANDS.GEMINI, args, onProgress); + return await executeCommand(CLI.COMMANDS.GEMINI, args, onProgress, stdinData); } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); if (errorMessage.includes(ERROR_MESSAGES.QUOTA_EXCEEDED) && model !== MODELS.FLASH) { Logger.warn(`${ERROR_MESSAGES.QUOTA_EXCEEDED}. Falling back to ${MODELS.FLASH}.`); await sendStatusMessage(STATUS_MESSAGES.FLASH_RETRY); const fallbackArgs = []; - fallbackArgs.push(CLI.FLAGS.MODEL, MODELS.FLASH); + fallbackArgs.push(CLI.FLAGS.MODEL, `"${MODELS.FLASH}"`); if (sandbox) { fallbackArgs.push(CLI.FLAGS.SANDBOX); } - // Same quoting logic for fallback - const fallbackPrompt = process.platform === 'win32' - ? `"${prompt_processed.replace(/"/g, '""')}"` - : prompt_processed; - - fallbackArgs.push(CLI.FLAGS.PROMPT, fallbackPrompt); - // Wrap prompt in quotes to handle spaces and special characters - fallbackArgs.push(CLI.FLAGS.PROMPT, `"${prompt_processed.replace(/"/g, '\\"')}"`); + // Use same stdin logic for fallback + if (!useStdin) { + const escapedPrompt = process.platform === 'win32' + ? prompt_processed.replace(/"/g, '""') + : prompt_processed.replace(/"/g, '\\"'); + fallbackArgs.push(CLI.FLAGS.PROMPT, `"${escapedPrompt}"`); + } + + // Log the fallback command being executed for debugging + Logger.debug(`Executing fallback command: ${CLI.COMMANDS.GEMINI} ${fallbackArgs.join(' ')}${useStdin ? ' [prompt via stdin]' : ''}`); + try { - const result = await executeCommand(CLI.COMMANDS.GEMINI, fallbackArgs, onProgress); + const result = await executeCommand(CLI.COMMANDS.GEMINI, fallbackArgs, onProgress, stdinData); Logger.warn(`Successfully executed with ${MODELS.FLASH} fallback.`); await sendStatusMessage(STATUS_MESSAGES.FLASH_SUCCESS); return result; From ebbe116b851df8f6cee2fb0207eb4c086238683e Mon Sep 17 00:00:00 2001 From: jamubc <150970140+jamubc@users.noreply.github.com> Date: Mon, 11 Aug 2025 02:36:59 -0700 Subject: [PATCH 12/20] comprehensive file handling and multiple flags --- src/utils/chunkCache.ts | 44 ++++++++++++++------ src/utils/commandExecutor.ts | 44 ++++++++++++-------- tests/geminiExecutor.test.ts | 81 ++++++++++++++++++++++++++++++++++-- 3 files changed, 136 insertions(+), 33 deletions(-) diff --git a/src/utils/chunkCache.ts b/src/utils/chunkCache.ts index 3beecc6..d96f866 100644 --- a/src/utils/chunkCache.ts +++ b/src/utils/chunkCache.ts @@ -33,7 +33,7 @@ export function cacheChunks(prompt: string, chunks: EditChunk[]): string { // Generate deterministic cache key from prompt const promptHash = createHash('sha256').update(prompt).digest('hex'); - const cacheKey = promptHash.slice(0, 8); + const cacheKey = promptHash.slice(0, 12); // Longer slice to reduce collision risk const filePath = path.join(CACHE_DIR, `${cacheKey}.json`); // Store with metadata @@ -98,19 +98,25 @@ function cleanExpiredFiles(): void { const filePath = path.join(CACHE_DIR, file); try { - const stats = fs.statSync(filePath); - if (now - stats.mtimeMs > CACHE_TTL) { + const fileContent = fs.readFileSync(filePath, 'utf-8'); + const data: CacheEntry = JSON.parse(fileContent); + + if (now - data.timestamp > CACHE_TTL) { fs.unlinkSync(filePath); cleaned++; } } catch (error) { - // Individual file error - continue with others - Logger.debug(`Error checking file ${file}: ${error}`); + // Individual file error - clean up bad file + Logger.debug(`Error processing file ${file}: ${error}`); + try { + fs.unlinkSync(filePath); + cleaned++; + } catch {} } } if (cleaned > 0) { - Logger.debug(`Cleaned ${cleaned} expired cache files`); + Logger.debug(`Cleaned ${cleaned} expired/invalid cache files`); } } catch (error) { // Non-critical, just log @@ -125,12 +131,26 @@ function enforceFileLimits(): void { try { const files = fs.readdirSync(CACHE_DIR) .filter(f => f.endsWith('.json')) - .map(f => ({ - name: f, - path: path.join(CACHE_DIR, f), - mtime: fs.statSync(path.join(CACHE_DIR, f)).mtimeMs - })) - .sort((a, b) => a.mtime - b.mtime); // Oldest first + .map(f => { + const filePath = path.join(CACHE_DIR, f); + try { + const fileContent = fs.readFileSync(filePath, 'utf-8'); + const data: CacheEntry = JSON.parse(fileContent); + return { + name: f, + path: filePath, + timestamp: data.timestamp + }; + } catch { + // Invalid file - remove immediately + try { + fs.unlinkSync(filePath); + } catch {} + return null; + } + }) + .filter((entry): entry is { name: string; path: string; timestamp: number } => entry !== null) + .sort((a, b) => a.timestamp - b.timestamp); // Oldest first // Remove oldest files if over limit if (files.length > MAX_CACHE_FILES) { diff --git a/src/utils/commandExecutor.ts b/src/utils/commandExecutor.ts index d9a76b0..daced29 100644 --- a/src/utils/commandExecutor.ts +++ b/src/utils/commandExecutor.ts @@ -1,4 +1,5 @@ -import { spawn } from "child_process"; +import { ChildProcessByStdio, spawn } from "child_process"; +import { Readable, Writable } from "stream"; import { Logger } from "./logger.js"; export async function executeCommand( @@ -11,24 +12,32 @@ export async function executeCommand( const startTime = Date.now(); Logger.commandExecution(command, args, startTime); - const childProcess = spawn(command, args, { - env: process.env, - shell: true, - stdio: stdinData ? ["pipe", "pipe", "pipe"] : ["ignore", "pipe", "pipe"], - }); - + let childProcess: ChildProcessByStdio; let stdout = ""; let stderr = ""; let isResolved = false; let lastReportedLength = 0; - - // Write stdin data if provided - if (stdinData && childProcess.stdin) { - childProcess.stdin.write(stdinData); - childProcess.stdin.end(); + + if (stdinData) { + childProcess = spawn(command, args, { + env: process.env, + shell: true, + stdio: ['pipe', 'pipe', 'pipe'], + }) as ChildProcessByStdio; + + if (childProcess.stdin) { + childProcess.stdin.write(stdinData); + childProcess.stdin.end(); + } + } else { + childProcess = spawn(command, args, { + env: process.env, + shell: true, + stdio: ['ignore', 'pipe', 'pipe'], + }) as ChildProcessByStdio; } - - childProcess.stdout?.on("data", (data) => { + + childProcess.stdout.on("data", (data: Buffer) => { stdout += data.toString(); // Report new content if callback provided @@ -39,9 +48,8 @@ export async function executeCommand( } }); - // CLI level errors - childProcess.stderr?.on("data", (data) => { + childProcess.stderr.on("data", (data: Buffer) => { stderr += data.toString(); // find RESOURCE_EXHAUSTED when gemini-2.5-pro quota is exceeded if (stderr.includes("RESOURCE_EXHAUSTED")) { @@ -65,14 +73,14 @@ export async function executeCommand( Logger.error(`Gemini Quota Error: ${JSON.stringify(errorJson, null, 2)}`); } }); - childProcess.on("error", (error) => { + childProcess.on("error", (error: Error) => { if (!isResolved) { isResolved = true; Logger.error(`Process error:`, error); reject(new Error(`Failed to spawn command: ${error.message}`)); } }); - childProcess.on("close", (code) => { + childProcess.on("close", (code: number | null) => { if (!isResolved) { isResolved = true; if (code === 0) { diff --git a/tests/geminiExecutor.test.ts b/tests/geminiExecutor.test.ts index 3c6a112..37f256e 100644 --- a/tests/geminiExecutor.test.ts +++ b/tests/geminiExecutor.test.ts @@ -14,10 +14,12 @@ describe('executeGeminiCLI', () => { Object.defineProperty(process, 'platform', { value: originalPlatform }); }); - it('quotes prompt on win32 to avoid shell parsing issues', async () => { + it('quotes prompt on win32 with -p for simple prompts', async () => { Object.defineProperty(process, 'platform', { value: 'win32' }); const mockStdoutOn = vi.fn(); const mockStderrOn = vi.fn(); + const mockWrite = vi.fn(); + const mockEnd = vi.fn(); let closeCallback: (code: number) => void = () => {}; const mockOn = vi.fn((event: string, cb: (code: number) => void) => { if (event === 'close') { @@ -25,18 +27,91 @@ describe('executeGeminiCLI', () => { } }); (spawn as unknown as vi.Mock).mockReturnValue({ + stdin: { write: mockWrite, end: mockEnd }, stdout: { on: mockStdoutOn }, stderr: { on: mockStderrOn }, on: mockOn, }); - const prompt = "Perfect! I've implemented your recommendations: fixed bug"; + const prompt = 'Perfect! I\'ve said "fixed bug"'; const promise = executeGeminiCLI(prompt); closeCallback(0); await promise; - const args = (spawn as unknown as vi.Mock).mock.calls[0][1] as string[]; + const spawnCall = (spawn as unknown as vi.Mock).mock.calls[0]; + const args = spawnCall[1] as string[]; + const stdio = spawnCall[2].stdio; const promptArgIndex = args.indexOf('-p') + 1; expect(args[promptArgIndex]).toBe(`"${prompt.replace(/"/g, '""')}"`); + expect(stdio).toEqual(['ignore', 'pipe', 'pipe']); + expect(mockWrite).not.toHaveBeenCalled(); + expect(mockEnd).not.toHaveBeenCalled(); + }); + + it('uses stdin for complex prompts with @ on win32', async () => { + Object.defineProperty(process, 'platform', { value: 'win32' }); + const mockStdoutOn = vi.fn(); + const mockStderrOn = vi.fn(); + const mockWrite = vi.fn(); + const mockEnd = vi.fn(); + let closeCallback: (code: number) => void = () => {}; + const mockOn = vi.fn((event: string, cb: (code: number) => void) => { + if (event === 'close') { + closeCallback = cb; + } + }); + (spawn as unknown as vi.Mock).mockReturnValue({ + stdin: { write: mockWrite, end: mockEnd }, + stdout: { on: mockStdoutOn }, + stderr: { on: mockStderrOn }, + on: mockOn, + }); + + const prompt = 'Analyze @src/file.ts and say "fix"'; + const promise = executeGeminiCLI(prompt); + closeCallback(0); + await promise; + + const spawnCall = (spawn as unknown as vi.Mock).mock.calls[0]; + const args = spawnCall[1] as string[]; + const stdio = spawnCall[2].stdio; + expect(args).not.toContain('-p'); + expect(stdio).toEqual(['pipe', 'pipe', 'pipe']); + expect(mockWrite).toHaveBeenCalledWith(prompt); + expect(mockEnd).toHaveBeenCalled(); + }); + + it('uses stdin for changeMode prompts', async () => { + Object.defineProperty(process, 'platform', { value: 'win32' }); + const mockStdoutOn = vi.fn(); + const mockStderrOn = vi.fn(); + const mockWrite = vi.fn(); + const mockEnd = vi.fn(); + let closeCallback: (code: number) => void = () => {}; + const mockOn = vi.fn((event: string, cb: (code: number) => void) => { + if (event === 'close') { + closeCallback = cb; + } + }); + (spawn as unknown as vi.Mock).mockReturnValue({ + stdin: { write: mockWrite, end: mockEnd }, + stdout: { on: mockStdoutOn }, + stderr: { on: mockStderrOn }, + on: mockOn, + }); + + const prompt = 'Debug this'; + const promise = executeGeminiCLI(prompt, undefined, undefined, true); + closeCallback(0); + await promise; + + const spawnCall = (spawn as unknown as vi.Mock).mock.calls[0]; + const args = spawnCall[1] as string[]; + const stdio = spawnCall[2].stdio; + expect(args).not.toContain('-p'); + expect(stdio).toEqual(['pipe', 'pipe', 'pipe']); + expect(mockWrite.mock.calls[0][0]).toContain('[CHANGEMODE INSTRUCTIONS]'); + expect(mockWrite.mock.calls[0][0]).toContain('USER REQUEST:\nDebug this'); + expect(mockEnd).toHaveBeenCalled(); }); }); From 5330a7864cd65fee15738f296d684517a65f2cd9 Mon Sep 17 00:00:00 2001 From: jamubc <150970140+jamubc@users.noreply.github.com> Date: Mon, 11 Aug 2025 02:45:54 -0700 Subject: [PATCH 13/20] update workflows --- .github/workflows/ci.yml | 47 +++++++++++++++++-------------- .github/workflows/deploy-docs.yml | 29 +++++++++++++------ 2 files changed, 47 insertions(+), 29 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f865fbe..fc6a90a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,32 +2,37 @@ name: CI on: push: - branches: [ main ] + branches: [main] pull_request: - branches: [ main ] + branches: [main] jobs: - test: + build-and-test: + name: Build & Test (Node ${{ matrix.node-version }}) runs-on: ubuntu-latest - + strategy: matrix: node-version: [16.x, 18.x, 20.x] - + steps: - - uses: actions/checkout@v4 - - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4 - with: - node-version: ${{ matrix.node-version }} - - - name: Install dependencies - run: npm ci - - - name: Build - run: npm run build - - - name: Run tests - run: npm test - continue-on-error: true \ No newline at end of file + - name: Checkout code + uses: actions/checkout@v4 + + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Lint + run: npm run lint + + - name: Build + run: npm run build + + - name: Run tests + run: npm test \ No newline at end of file diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml index 31c73d4..89306da 100644 --- a/.github/workflows/deploy-docs.yml +++ b/.github/workflows/deploy-docs.yml @@ -11,29 +11,42 @@ permissions: id-token: write concurrency: - group: pages - cancel-in-progress: false + group: "pages" + cancel-in-progress: true jobs: build: + name: Build Docs runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 with: node-version: 20 - - run: npm ci - - run: npm run docs:build - - uses: actions/upload-pages-artifact@v3 + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Build docs + run: npm run docs:build + + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 with: path: docs/.vitepress/dist deploy: + name: Deploy Docs environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} needs: build runs-on: ubuntu-latest steps: - - id: deployment + - name: Deploy to GitHub Pages + id: deployment uses: actions/deploy-pages@v4 \ No newline at end of file From 8f37aafe2222c95831ad7913e3ef197d82ce621b Mon Sep 17 00:00:00 2001 From: jamubc <150970140+jamubc@users.noreply.github.com> Date: Mon, 11 Aug 2025 03:02:06 -0700 Subject: [PATCH 14/20] bump version --- docs/.vitepress/theme/Layout.vue | 2 +- docs/resources/troubleshooting.md | 9 +------ package-lock.json | 43 ++----------------------------- package.json | 2 +- src/index.ts | 2 +- 5 files changed, 6 insertions(+), 52 deletions(-) diff --git a/docs/.vitepress/theme/Layout.vue b/docs/.vitepress/theme/Layout.vue index 7185c30..67d56a6 100644 --- a/docs/.vitepress/theme/Layout.vue +++ b/docs/.vitepress/theme/Layout.vue @@ -6,7 +6,7 @@