diff --git a/.gitignore b/.gitignore index bb65f13..9df63b3 100755 --- a/.gitignore +++ b/.gitignore @@ -98,10 +98,31 @@ temp/ # Local Netlify folder .netlify -# Claude specific +# AI specific .claude/ +.cursor/ +.roo/ +.taskmaster/ +.cline/ +.windsurf/ # Database files *.db *.sqlite -*.sqlite3 \ No newline at end of file +*.sqlite3 + +logs +dev-debug.log +# Editor directories and files +.idea +.vscode +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? +# OS specific + +# Task files +tasks.json +tasks/ diff --git a/package-lock.json b/package-lock.json index 12fc9c1..ce68869 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "claude-code-ui", - "version": "1.5.0", + "version": "1.6.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "claude-code-ui", - "version": "1.5.0", + "version": "1.6.0", "license": "MIT", "dependencies": { "@codemirror/lang-css": "^6.3.1", @@ -39,6 +39,8 @@ "react-dropzone": "^14.2.3", "react-markdown": "^10.1.0", "react-router-dom": "^6.8.1", + "sqlite": "^5.1.1", + "sqlite3": "^5.1.7", "tailwind-merge": "^3.3.1", "ws": "^8.14.2", "xterm": "^5.3.0", @@ -1002,6 +1004,13 @@ "node": ">=18" } }, + "node_modules/@gar/promisify": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", + "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==", + "license": "MIT", + "optional": true + }, "node_modules/@img/sharp-libvips-linux-ppc64": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.0.tgz", @@ -1404,6 +1413,58 @@ "node": ">= 8" } }, + "node_modules/@npmcli/fs": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-1.1.1.tgz", + "integrity": "sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ==", + "license": "ISC", + "optional": true, + "dependencies": { + "@gar/promisify": "^1.0.1", + "semver": "^7.3.5" + } + }, + "node_modules/@npmcli/fs/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==", + "license": "ISC", + "optional": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@npmcli/move-file": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.1.2.tgz", + "integrity": "sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg==", + "deprecated": "This functionality has been moved to @npmcli/fs", + "license": "MIT", + "optional": true, + "dependencies": { + "mkdirp": "^1.0.4", + "rimraf": "^3.0.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@npmcli/move-file/node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "license": "MIT", + "optional": true, + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -1725,6 +1786,16 @@ "tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1" } }, + "node_modules/@tootallnate/once": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", + "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 6" + } + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -1958,6 +2029,13 @@ "license": "MIT", "peer": true }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "license": "ISC", + "optional": true + }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -1992,6 +2070,46 @@ "node": ">= 0.6" } }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/agentkeepalive": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz", + "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "humanize-ms": "^1.2.1" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "license": "MIT", + "optional": true, + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/ansi-regex": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", @@ -2044,6 +2162,28 @@ "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==", "license": "MIT" }, + "node_modules/aproba": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.1.0.tgz", + "integrity": "sha512-tLIEcj5GuR2RSTnxNKdkK0dJ/GrC7P38sUkiDmDuHfsHmbagTFAxDVIBltoklXEVIQ/f14IL8IMJ5pn9Hez1Ew==", + "license": "ISC", + "optional": true + }, + "node_modules/are-we-there-yet": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz", + "integrity": "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==", + "deprecated": "This package is no longer supported.", + "license": "ISC", + "optional": true, + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, "node_modules/arg": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", @@ -2348,6 +2488,138 @@ "node": ">= 0.8" } }, + "node_modules/cacache": { + "version": "15.3.0", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-15.3.0.tgz", + "integrity": "sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ==", + "license": "ISC", + "optional": true, + "dependencies": { + "@npmcli/fs": "^1.0.0", + "@npmcli/move-file": "^1.0.1", + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "glob": "^7.1.4", + "infer-owner": "^1.0.4", + "lru-cache": "^6.0.0", + "minipass": "^3.1.1", + "minipass-collect": "^1.0.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.2", + "mkdirp": "^1.0.3", + "p-map": "^4.0.0", + "promise-inflight": "^1.0.1", + "rimraf": "^3.0.2", + "ssri": "^8.0.1", + "tar": "^6.0.2", + "unique-filename": "^1.1.1" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/cacache/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==", + "license": "MIT", + "optional": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/cacache/node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "license": "ISC", + "optional": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/cacache/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "license": "ISC", + "optional": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/cacache/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "license": "ISC", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/cacache/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "license": "ISC", + "optional": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/cacache/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cacache/node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "license": "MIT", + "optional": true, + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/cacache/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC", + "optional": true + }, "node_modules/call-bind-apply-helpers": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", @@ -2520,6 +2792,16 @@ "url": "https://polar.sh/cva" } }, + "node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=6" + } + }, "node_modules/cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", @@ -2665,6 +2947,16 @@ "simple-swizzle": "^0.2.2" } }, + "node_modules/color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "license": "ISC", + "optional": true, + "bin": { + "color-support": "bin.js" + } + }, "node_modules/comma-separated-tokens": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", @@ -2684,6 +2976,13 @@ "node": ">= 6" } }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "license": "MIT", + "optional": true + }, "node_modules/concat-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", @@ -2727,6 +3026,13 @@ "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" } }, + "node_modules/console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", + "license": "ISC", + "optional": true + }, "node_modules/content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -2892,6 +3198,13 @@ "node": ">=4.0.0" } }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", + "license": "MIT", + "optional": true + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -3011,6 +3324,29 @@ "node": ">= 0.8" } }, + "node_modules/encoding": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", + "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", + "license": "MIT", + "optional": true, + "dependencies": { + "iconv-lite": "^0.6.2" + } + }, + "node_modules/encoding/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/end-of-stream": { "version": "1.4.5", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", @@ -3020,6 +3356,23 @@ "once": "^1.4.0" } }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/err-code": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", + "license": "MIT", + "optional": true + }, "node_modules/es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", @@ -3357,6 +3710,43 @@ "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", "license": "MIT" }, + "node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fs-minipass/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC" + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "license": "ISC", + "optional": true + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -3380,6 +3770,79 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/gauge": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz", + "integrity": "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==", + "deprecated": "This package is no longer supported.", + "license": "ISC", + "optional": true, + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.3", + "console-control-strings": "^1.1.0", + "has-unicode": "^2.0.1", + "signal-exit": "^3.0.7", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.5" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/gauge/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/gauge/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT", + "optional": true + }, + "node_modules/gauge/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", + "optional": true + }, + "node_modules/gauge/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "optional": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/gauge/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "optional": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -3487,6 +3950,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC", + "optional": true + }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -3509,6 +3979,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", + "license": "ISC", + "optional": true + }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", @@ -3571,6 +4048,13 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", + "license": "BSD-2-Clause", + "optional": true + }, "node_modules/http-errors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", @@ -3587,6 +4071,45 @@ "node": ">= 0.8" } }, + "node_modules/http-proxy-agent": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", + "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", + "license": "MIT", + "optional": true, + "dependencies": { + "@tootallnate/once": "1", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", + "optional": true, + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/humanize-ms": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "ms": "^2.0.0" + } + }, "node_modules/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -3619,6 +4142,45 @@ ], "license": "BSD-3-Clause" }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/infer-owner": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", + "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", + "license": "ISC", + "optional": true + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "license": "ISC", + "optional": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", @@ -3637,6 +4199,20 @@ "integrity": "sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==", "license": "MIT" }, + "node_modules/ip-address": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", + "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", + "license": "MIT", + "optional": true, + "dependencies": { + "jsbn": "1.1.0", + "sprintf-js": "^1.1.3" + }, + "engines": { + "node": ">= 12" + } + }, "node_modules/ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -3754,6 +4330,13 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/is-lambda": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", + "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", + "license": "MIT", + "optional": true + }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -3817,6 +4400,13 @@ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "license": "MIT" }, + "node_modules/jsbn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", + "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==", + "license": "MIT", + "optional": true + }, "node_modules/jsesc": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", @@ -4018,6 +4608,67 @@ "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, + "node_modules/make-fetch-happen": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-9.1.0.tgz", + "integrity": "sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg==", + "license": "ISC", + "optional": true, + "dependencies": { + "agentkeepalive": "^4.1.3", + "cacache": "^15.2.0", + "http-cache-semantics": "^4.1.0", + "http-proxy-agent": "^4.0.1", + "https-proxy-agent": "^5.0.0", + "is-lambda": "^1.0.1", + "lru-cache": "^6.0.0", + "minipass": "^3.1.3", + "minipass-collect": "^1.0.2", + "minipass-fetch": "^1.3.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.2", + "promise-retry": "^2.0.1", + "socks-proxy-agent": "^6.0.0", + "ssri": "^8.0.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/make-fetch-happen/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "license": "ISC", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/make-fetch-happen/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/make-fetch-happen/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC", + "optional": true + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -4749,6 +5400,207 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/minipass-collect": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", + "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", + "license": "ISC", + "optional": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-collect/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-collect/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC", + "optional": true + }, + "node_modules/minipass-fetch": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-1.4.1.tgz", + "integrity": "sha512-CGH1eblLq26Y15+Azk7ey4xh0J/XfJfrCox5LDJiKqI2Q2iwOLOKrlmIaODiSQS8d18jalF6y2K2ePUm0CmShw==", + "license": "MIT", + "optional": true, + "dependencies": { + "minipass": "^3.1.0", + "minipass-sized": "^1.0.3", + "minizlib": "^2.0.0" + }, + "engines": { + "node": ">=8" + }, + "optionalDependencies": { + "encoding": "^0.1.12" + } + }, + "node_modules/minipass-fetch/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-fetch/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC", + "optional": true + }, + "node_modules/minipass-flush": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", + "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", + "license": "ISC", + "optional": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-flush/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-flush/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC", + "optional": true + }, + "node_modules/minipass-pipeline": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", + "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", + "license": "ISC", + "optional": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-pipeline/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-pipeline/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC", + "optional": true + }, + "node_modules/minipass-sized": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", + "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", + "license": "ISC", + "optional": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-sized/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-sized/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC", + "optional": true + }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "license": "MIT", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC" + }, "node_modules/mkdirp": { "version": "0.5.6", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", @@ -4888,6 +5740,31 @@ } } }, + "node_modules/node-gyp": { + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-8.4.1.tgz", + "integrity": "sha512-olTJRgUtAb/hOXG0E93wZDs5YiJlgbXxTwQAFHyNlRsXQnYzUaF2aGgujZbw+hR8aF4ZG/rST57bWMWD16jr9w==", + "license": "MIT", + "optional": true, + "dependencies": { + "env-paths": "^2.2.0", + "glob": "^7.1.4", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^9.1.0", + "nopt": "^5.0.0", + "npmlog": "^6.0.0", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.2", + "which": "^2.0.2" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": ">= 10.12.0" + } + }, "node_modules/node-gyp-build": { "version": "4.8.4", "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", @@ -4899,6 +5776,65 @@ "node-gyp-build-test": "build-test.js" } }, + "node_modules/node-gyp/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==", + "license": "MIT", + "optional": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/node-gyp/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "license": "ISC", + "optional": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/node-gyp/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "license": "ISC", + "optional": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/node-gyp/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==", + "license": "ISC", + "optional": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/node-pty": { "version": "1.1.0-beta9", "resolved": "https://registry.npmjs.org/node-pty/-/node-pty-1.1.0-beta9.tgz", @@ -4922,6 +5858,22 @@ "dev": true, "license": "MIT" }, + "node_modules/nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "license": "ISC", + "optional": true, + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -4941,6 +5893,23 @@ "node": ">=0.10.0" } }, + "node_modules/npmlog": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz", + "integrity": "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==", + "deprecated": "This package is no longer supported.", + "license": "ISC", + "optional": true, + "dependencies": { + "are-we-there-yet": "^3.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^4.0.3", + "set-blocking": "^2.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -4992,6 +5961,22 @@ "wrappy": "1" } }, + "node_modules/p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "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", @@ -5032,6 +6017,16 @@ "node": ">= 0.8" } }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -5293,6 +6288,27 @@ "node": ">=10" } }, + "node_modules/promise-inflight": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", + "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", + "license": "ISC", + "optional": true + }, + "node_modules/promise-retry": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", + "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", + "license": "MIT", + "optional": true, + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/prop-types": { "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", @@ -5627,6 +6643,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 4" + } + }, "node_modules/reusify": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", @@ -5637,6 +6663,69 @@ "node": ">=0.10.0" } }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "license": "ISC", + "optional": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/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==", + "license": "MIT", + "optional": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/rimraf/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "license": "ISC", + "optional": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "license": "ISC", + "optional": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/rollup": { "version": "4.45.1", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.45.1.tgz", @@ -5818,6 +6907,13 @@ "node": ">= 0.8.0" } }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "license": "ISC", + "optional": true + }, "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", @@ -6273,6 +7369,47 @@ "is-arrayish": "^0.3.1" } }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.6.tgz", + "integrity": "sha512-pe4Y2yzru68lXCb38aAqRf5gvN8YdjP1lok5o0J7BOHljkyCGKVz7H3vpVIXKD27rj2giOJ7DwVyk/GWrPHDWA==", + "license": "MIT", + "optional": true, + "dependencies": { + "ip-address": "^9.0.5", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-6.2.1.tgz", + "integrity": "sha512-a6KW9G+6B3nWZ1yB8G7pJwL3ggLy1uTzKAgCb7ttblwqdz9fMGJUuTy3uFzEP48FAs9FLILlmzDlE2JJhVQaXQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "agent-base": "^6.0.2", + "debug": "^4.3.3", + "socks": "^2.6.2" + }, + "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", @@ -6298,6 +7435,82 @@ "integrity": "sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ==", "dev": true }, + "node_modules/sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", + "license": "BSD-3-Clause", + "optional": true + }, + "node_modules/sqlite": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/sqlite/-/sqlite-5.1.1.tgz", + "integrity": "sha512-oBkezXa2hnkfuJwUo44Hl9hS3er+YFtueifoajrgidvqsJRQFpc5fKoAkAor1O5ZnLoa28GBScfHXs8j0K358Q==", + "license": "MIT" + }, + "node_modules/sqlite3": { + "version": "5.1.7", + "resolved": "https://registry.npmjs.org/sqlite3/-/sqlite3-5.1.7.tgz", + "integrity": "sha512-GGIyOiFaG+TUra3JIfkI/zGP8yZYLPQ0pl1bH+ODjiX57sPhrLU5sQJn1y9bDKZUFYkX1crlrPfSYt0BKKdkog==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "bindings": "^1.5.0", + "node-addon-api": "^7.0.0", + "prebuild-install": "^7.1.1", + "tar": "^6.1.11" + }, + "optionalDependencies": { + "node-gyp": "8.x" + }, + "peerDependencies": { + "node-gyp": "8.x" + }, + "peerDependenciesMeta": { + "node-gyp": { + "optional": true + } + } + }, + "node_modules/sqlite3/node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "license": "MIT" + }, + "node_modules/ssri": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz", + "integrity": "sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==", + "license": "ISC", + "optional": true, + "dependencies": { + "minipass": "^3.1.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/ssri/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ssri/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC", + "optional": true + }, "node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", @@ -6625,6 +7838,23 @@ "node": ">=8.10.0" } }, + "node_modules/tar": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "license": "ISC", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/tar-fs": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.3.tgz", @@ -6653,6 +7883,42 @@ "node": ">=6" } }, + "node_modules/tar/node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/tar/node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "license": "ISC", + "engines": { + "node": ">=8" + } + }, + "node_modules/tar/node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/tar/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC" + }, "node_modules/thenify": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", @@ -6859,6 +8125,26 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/unique-filename": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", + "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", + "license": "ISC", + "optional": true, + "dependencies": { + "unique-slug": "^2.0.0" + } + }, + "node_modules/unique-slug": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz", + "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==", + "license": "ISC", + "optional": true, + "dependencies": { + "imurmurhash": "^0.1.4" + } + }, "node_modules/unist-util-is": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz", @@ -7159,6 +8445,61 @@ "node": ">= 8" } }, + "node_modules/wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "license": "ISC", + "optional": true, + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, + "node_modules/wide-align/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/wide-align/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT", + "optional": true + }, + "node_modules/wide-align/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "optional": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wide-align/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "optional": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/wrap-ansi": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", diff --git a/package.json b/package.json index b665cb7..58df385 100644 --- a/package.json +++ b/package.json @@ -52,6 +52,8 @@ "react-dropzone": "^14.2.3", "react-markdown": "^10.1.0", "react-router-dom": "^6.8.1", + "sqlite": "^5.1.1", + "sqlite3": "^5.1.7", "tailwind-merge": "^3.3.1", "ws": "^8.14.2", "xterm": "^5.3.0", @@ -68,4 +70,4 @@ "tailwindcss": "^3.4.0", "vite": "^7.0.4" } -} \ No newline at end of file +} diff --git a/public/icons/cursor.svg b/public/icons/cursor.svg new file mode 100644 index 0000000..abadee5 --- /dev/null +++ b/public/icons/cursor.svg @@ -0,0 +1 @@ +Cursor \ No newline at end of file diff --git a/server/cursor-cli.js b/server/cursor-cli.js new file mode 100644 index 0000000..dcb14ca --- /dev/null +++ b/server/cursor-cli.js @@ -0,0 +1,250 @@ +import { spawn } from 'child_process'; +import crossSpawn from 'cross-spawn'; +import { promises as fs } from 'fs'; +import path from 'path'; +import os from 'os'; + +// Use cross-spawn on Windows for better command execution +const spawnFunction = process.platform === 'win32' ? crossSpawn : spawn; + +let activeCursorProcesses = new Map(); // Track active processes by session ID + +async function spawnCursor(command, options = {}, ws) { + return new Promise(async (resolve, reject) => { + const { sessionId, projectPath, cwd, resume, toolsSettings, skipPermissions, model, images } = options; + let capturedSessionId = sessionId; // Track session ID throughout the process + let sessionCreatedSent = false; // Track if we've already sent session-created event + let messageBuffer = ''; // Buffer for accumulating assistant messages + + // Use tools settings passed from frontend, or defaults + const settings = toolsSettings || { + allowedShellCommands: [], + skipPermissions: false + }; + + // Build Cursor CLI command + const args = []; + + // Build flags allowing both resume and prompt together (reply in existing session) + if (resume && sessionId) { + // Resume existing session + args.push('--resume=' + sessionId); + } + + if (command && command.trim()) { + // Provide a prompt (works for both new and resumed sessions) + args.push('-p', command); + + // Add model flag if specified (only meaningful for new sessions; harmless on resume) + if (!resume && model) { + args.push('--model', model); + } + + // Request streaming JSON when we are providing a prompt + args.push('--output-format', 'stream-json'); + } + + // Add skip permissions flag if enabled + if (skipPermissions || settings.skipPermissions) { + args.push('-f'); + console.log('⚠️ Using -f flag (skip permissions)'); + } + + // Use cwd (actual project directory) instead of projectPath + const workingDir = cwd || projectPath || process.cwd(); + + console.log('Spawning Cursor CLI:', 'cursor-agent', args.join(' ')); + console.log('Working directory:', workingDir); + console.log('Session info - Input sessionId:', sessionId, 'Resume:', resume); + + const cursorProcess = spawnFunction('cursor-agent', args, { + cwd: workingDir, + stdio: ['pipe', 'pipe', 'pipe'], + env: { ...process.env } // Inherit all environment variables + }); + + // Store process reference for potential abort + const processKey = capturedSessionId || Date.now().toString(); + activeCursorProcesses.set(processKey, cursorProcess); + + // Handle stdout (streaming JSON responses) + cursorProcess.stdout.on('data', (data) => { + const rawOutput = data.toString(); + console.log('📤 Cursor CLI stdout:', rawOutput); + + const lines = rawOutput.split('\n').filter(line => line.trim()); + + for (const line of lines) { + try { + const response = JSON.parse(line); + console.log('📄 Parsed JSON response:', response); + + // Handle different message types + switch (response.type) { + case 'system': + if (response.subtype === 'init') { + // Capture session ID + if (response.session_id && !capturedSessionId) { + capturedSessionId = response.session_id; + console.log('📝 Captured session ID:', capturedSessionId); + + // Update process key with captured session ID + if (processKey !== capturedSessionId) { + activeCursorProcesses.delete(processKey); + activeCursorProcesses.set(capturedSessionId, cursorProcess); + } + + // Send session-created event only once for new sessions + if (!sessionId && !sessionCreatedSent) { + sessionCreatedSent = true; + ws.send(JSON.stringify({ + type: 'session-created', + sessionId: capturedSessionId, + model: response.model, + cwd: response.cwd + })); + } + } + + // Send system info to frontend + ws.send(JSON.stringify({ + type: 'cursor-system', + data: response + })); + } + break; + + case 'user': + // Forward user message + ws.send(JSON.stringify({ + type: 'cursor-user', + data: response + })); + break; + + case 'assistant': + // Accumulate assistant message chunks + if (response.message && response.message.content && response.message.content.length > 0) { + const textContent = response.message.content[0].text; + messageBuffer += textContent; + + // Send as Claude-compatible format for frontend + ws.send(JSON.stringify({ + type: 'claude-response', + data: { + type: 'content_block_delta', + delta: { + type: 'text_delta', + text: textContent + } + } + })); + } + break; + + case 'result': + // Session complete + console.log('Cursor session result:', response); + + // Send final message if we have buffered content + if (messageBuffer) { + ws.send(JSON.stringify({ + type: 'claude-response', + data: { + type: 'content_block_stop' + } + })); + } + + // Send completion event + ws.send(JSON.stringify({ + type: 'cursor-result', + data: response, + success: response.subtype === 'success' + })); + break; + + default: + // Forward any other message types + ws.send(JSON.stringify({ + type: 'cursor-response', + data: response + })); + } + } catch (parseError) { + console.log('📄 Non-JSON response:', line); + // If not JSON, send as raw text + ws.send(JSON.stringify({ + type: 'cursor-output', + data: line + })); + } + } + }); + + // Handle stderr + cursorProcess.stderr.on('data', (data) => { + console.error('Cursor CLI stderr:', data.toString()); + ws.send(JSON.stringify({ + type: 'cursor-error', + error: data.toString() + })); + }); + + // Handle process completion + cursorProcess.on('close', async (code) => { + console.log(`Cursor CLI process exited with code ${code}`); + + // Clean up process reference + const finalSessionId = capturedSessionId || sessionId || processKey; + activeCursorProcesses.delete(finalSessionId); + + ws.send(JSON.stringify({ + type: 'claude-complete', + exitCode: code, + isNewSession: !sessionId && !!command // Flag to indicate this was a new session + })); + + if (code === 0) { + resolve(); + } else { + reject(new Error(`Cursor CLI exited with code ${code}`)); + } + }); + + // Handle process errors + cursorProcess.on('error', (error) => { + console.error('Cursor CLI process error:', error); + + // Clean up process reference on error + const finalSessionId = capturedSessionId || sessionId || processKey; + activeCursorProcesses.delete(finalSessionId); + + ws.send(JSON.stringify({ + type: 'cursor-error', + error: error.message + })); + + reject(error); + }); + + // Close stdin since Cursor doesn't need interactive input + cursorProcess.stdin.end(); + }); +} + +function abortCursorSession(sessionId) { + const process = activeCursorProcesses.get(sessionId); + if (process) { + console.log(`🛑 Aborting Cursor session: ${sessionId}`); + process.kill('SIGTERM'); + activeCursorProcesses.delete(sessionId); + return true; + } + return false; +} + +export { + spawnCursor, + abortCursorSession +}; \ No newline at end of file diff --git a/server/index.js b/server/index.js index b6103a1..eca0f98 100755 --- a/server/index.js +++ b/server/index.js @@ -38,9 +38,11 @@ import mime from 'mime-types'; import { getProjects, getSessions, getSessionMessages, renameProject, deleteSession, deleteProject, addProjectManually, extractProjectDirectory, clearProjectDirectoryCache } from './projects.js'; import { spawnClaude, abortClaudeSession } from './claude-cli.js'; +import { spawnCursor, abortCursorSession } from './cursor-cli.js'; import gitRoutes from './routes/git.js'; import authRoutes from './routes/auth.js'; import mcpRoutes from './routes/mcp.js'; +import cursorRoutes from './routes/cursor.js'; import { initializeDatabase } from './database/db.js'; import { validateApiKey, authenticateToken, authenticateWebSocket } from './middleware/auth.js'; @@ -175,6 +177,9 @@ app.use('/api/git', authenticateToken, gitRoutes); // MCP API Routes (protected) app.use('/api/mcp', authenticateToken, mcpRoutes); +// Cursor API Routes (protected) +app.use('/api/cursor', authenticateToken, cursorRoutes); + // Static files served after API routes app.use(express.static(path.join(__dirname, '../dist'))); @@ -460,12 +465,39 @@ function handleChatConnection(ws) { console.log('📁 Project:', data.options?.projectPath || 'Unknown'); console.log('🔄 Session:', data.options?.sessionId ? 'Resume' : 'New'); await spawnClaude(data.command, data.options, ws); + } else if (data.type === 'cursor-command') { + console.log('🖱️ Cursor message:', data.command || '[Continue/Resume]'); + console.log('📁 Project:', data.options?.cwd || 'Unknown'); + console.log('🔄 Session:', data.options?.sessionId ? 'Resume' : 'New'); + console.log('🤖 Model:', data.options?.model || 'default'); + await spawnCursor(data.command, data.options, ws); + } else if (data.type === 'cursor-resume') { + // Backward compatibility: treat as cursor-command with resume and no prompt + console.log('🖱️ Cursor resume session (compat):', data.sessionId); + await spawnCursor('', { + sessionId: data.sessionId, + resume: true, + cwd: data.options?.cwd + }, ws); } else if (data.type === 'abort-session') { console.log('🛑 Abort session request:', data.sessionId); - const success = abortClaudeSession(data.sessionId); + const provider = data.provider || 'claude'; + const success = provider === 'cursor' + ? abortCursorSession(data.sessionId) + : abortClaudeSession(data.sessionId); ws.send(JSON.stringify({ type: 'session-aborted', sessionId: data.sessionId, + provider, + success + })); + } else if (data.type === 'cursor-abort') { + console.log('🛑 Abort Cursor session:', data.sessionId); + const success = abortCursorSession(data.sessionId); + ws.send(JSON.stringify({ + type: 'session-aborted', + sessionId: data.sessionId, + provider: 'cursor', success })); } diff --git a/server/routes/cursor.js b/server/routes/cursor.js new file mode 100644 index 0000000..6950f4a --- /dev/null +++ b/server/routes/cursor.js @@ -0,0 +1,637 @@ +import express from 'express'; +import { promises as fs } from 'fs'; +import path from 'path'; +import os from 'os'; +import { spawn } from 'child_process'; +import sqlite3 from 'sqlite3'; +import { open } from 'sqlite'; +import crypto from 'crypto'; + +const router = express.Router(); + +// GET /api/cursor/config - Read Cursor CLI configuration +router.get('/config', async (req, res) => { + try { + const configPath = path.join(os.homedir(), '.cursor', 'cli-config.json'); + + try { + const configContent = await fs.readFile(configPath, 'utf8'); + const config = JSON.parse(configContent); + + res.json({ + success: true, + config: config, + path: configPath + }); + } catch (error) { + // Config doesn't exist or is invalid + console.log('Cursor config not found or invalid:', error.message); + + // Return default config + res.json({ + success: true, + config: { + version: 1, + model: { + modelId: "gpt-5", + displayName: "GPT-5" + }, + permissions: { + allow: [], + deny: [] + } + }, + isDefault: true + }); + } + } catch (error) { + console.error('Error reading Cursor config:', error); + res.status(500).json({ + error: 'Failed to read Cursor configuration', + details: error.message + }); + } +}); + +// POST /api/cursor/config - Update Cursor CLI configuration +router.post('/config', async (req, res) => { + try { + const { permissions, model } = req.body; + const configPath = path.join(os.homedir(), '.cursor', 'cli-config.json'); + + // Read existing config or create default + let config = { + version: 1, + editor: { + vimMode: false + }, + hasChangedDefaultModel: false, + privacyCache: { + ghostMode: false, + privacyMode: 3, + updatedAt: Date.now() + } + }; + + try { + const existing = await fs.readFile(configPath, 'utf8'); + config = JSON.parse(existing); + } catch (error) { + // Config doesn't exist, use defaults + console.log('Creating new Cursor config'); + } + + // Update permissions if provided + if (permissions) { + config.permissions = { + allow: permissions.allow || [], + deny: permissions.deny || [] + }; + } + + // Update model if provided + if (model) { + config.model = model; + config.hasChangedDefaultModel = true; + } + + // Ensure directory exists + const configDir = path.dirname(configPath); + await fs.mkdir(configDir, { recursive: true }); + + // Write updated config + await fs.writeFile(configPath, JSON.stringify(config, null, 2)); + + res.json({ + success: true, + config: config, + message: 'Cursor configuration updated successfully' + }); + } catch (error) { + console.error('Error updating Cursor config:', error); + res.status(500).json({ + error: 'Failed to update Cursor configuration', + details: error.message + }); + } +}); + +// GET /api/cursor/mcp - Read Cursor MCP servers configuration +router.get('/mcp', async (req, res) => { + try { + const mcpPath = path.join(os.homedir(), '.cursor', 'mcp.json'); + + try { + const mcpContent = await fs.readFile(mcpPath, 'utf8'); + const mcpConfig = JSON.parse(mcpContent); + + // Convert to UI-friendly format + const servers = []; + if (mcpConfig.mcpServers && typeof mcpConfig.mcpServers === 'object') { + for (const [name, config] of Object.entries(mcpConfig.mcpServers)) { + const server = { + id: name, + name: name, + type: 'stdio', + scope: 'cursor', + config: {}, + raw: config + }; + + // Determine transport type and extract config + if (config.command) { + server.type = 'stdio'; + server.config.command = config.command; + server.config.args = config.args || []; + server.config.env = config.env || {}; + } else if (config.url) { + server.type = config.transport || 'http'; + server.config.url = config.url; + server.config.headers = config.headers || {}; + } + + servers.push(server); + } + } + + res.json({ + success: true, + servers: servers, + path: mcpPath + }); + } catch (error) { + // MCP config doesn't exist + console.log('Cursor MCP config not found:', error.message); + res.json({ + success: true, + servers: [], + isDefault: true + }); + } + } catch (error) { + console.error('Error reading Cursor MCP config:', error); + res.status(500).json({ + error: 'Failed to read Cursor MCP configuration', + details: error.message + }); + } +}); + +// POST /api/cursor/mcp/add - Add MCP server to Cursor configuration +router.post('/mcp/add', async (req, res) => { + try { + const { name, type = 'stdio', command, args = [], url, headers = {}, env = {} } = req.body; + const mcpPath = path.join(os.homedir(), '.cursor', 'mcp.json'); + + console.log(`➕ Adding MCP server to Cursor config: ${name}`); + + // Read existing config or create new + let mcpConfig = { mcpServers: {} }; + + try { + const existing = await fs.readFile(mcpPath, 'utf8'); + mcpConfig = JSON.parse(existing); + if (!mcpConfig.mcpServers) { + mcpConfig.mcpServers = {}; + } + } catch (error) { + console.log('Creating new Cursor MCP config'); + } + + // Build server config based on type + let serverConfig = {}; + + if (type === 'stdio') { + serverConfig = { + command: command, + args: args, + env: env + }; + } else if (type === 'http' || type === 'sse') { + serverConfig = { + url: url, + transport: type, + headers: headers + }; + } + + // Add server to config + mcpConfig.mcpServers[name] = serverConfig; + + // Ensure directory exists + const mcpDir = path.dirname(mcpPath); + await fs.mkdir(mcpDir, { recursive: true }); + + // Write updated config + await fs.writeFile(mcpPath, JSON.stringify(mcpConfig, null, 2)); + + res.json({ + success: true, + message: `MCP server "${name}" added to Cursor configuration`, + config: mcpConfig + }); + } catch (error) { + console.error('Error adding MCP server to Cursor:', error); + res.status(500).json({ + error: 'Failed to add MCP server', + details: error.message + }); + } +}); + +// DELETE /api/cursor/mcp/:name - Remove MCP server from Cursor configuration +router.delete('/mcp/:name', async (req, res) => { + try { + const { name } = req.params; + const mcpPath = path.join(os.homedir(), '.cursor', 'mcp.json'); + + console.log(`🗑️ Removing MCP server from Cursor config: ${name}`); + + // Read existing config + let mcpConfig = { mcpServers: {} }; + + try { + const existing = await fs.readFile(mcpPath, 'utf8'); + mcpConfig = JSON.parse(existing); + } catch (error) { + return res.status(404).json({ + error: 'Cursor MCP configuration not found' + }); + } + + // Check if server exists + if (!mcpConfig.mcpServers || !mcpConfig.mcpServers[name]) { + return res.status(404).json({ + error: `MCP server "${name}" not found in Cursor configuration` + }); + } + + // Remove server from config + delete mcpConfig.mcpServers[name]; + + // Write updated config + await fs.writeFile(mcpPath, JSON.stringify(mcpConfig, null, 2)); + + res.json({ + success: true, + message: `MCP server "${name}" removed from Cursor configuration`, + config: mcpConfig + }); + } catch (error) { + console.error('Error removing MCP server from Cursor:', error); + res.status(500).json({ + error: 'Failed to remove MCP server', + details: error.message + }); + } +}); + +// POST /api/cursor/mcp/add-json - Add MCP server using JSON format +router.post('/mcp/add-json', async (req, res) => { + try { + const { name, jsonConfig } = req.body; + const mcpPath = path.join(os.homedir(), '.cursor', 'mcp.json'); + + console.log(`➕ Adding MCP server to Cursor config via JSON: ${name}`); + + // Validate and parse JSON config + let parsedConfig; + try { + parsedConfig = typeof jsonConfig === 'string' ? JSON.parse(jsonConfig) : jsonConfig; + } catch (parseError) { + return res.status(400).json({ + error: 'Invalid JSON configuration', + details: parseError.message + }); + } + + // Read existing config or create new + let mcpConfig = { mcpServers: {} }; + + try { + const existing = await fs.readFile(mcpPath, 'utf8'); + mcpConfig = JSON.parse(existing); + if (!mcpConfig.mcpServers) { + mcpConfig.mcpServers = {}; + } + } catch (error) { + console.log('Creating new Cursor MCP config'); + } + + // Add server to config + mcpConfig.mcpServers[name] = parsedConfig; + + // Ensure directory exists + const mcpDir = path.dirname(mcpPath); + await fs.mkdir(mcpDir, { recursive: true }); + + // Write updated config + await fs.writeFile(mcpPath, JSON.stringify(mcpConfig, null, 2)); + + res.json({ + success: true, + message: `MCP server "${name}" added to Cursor configuration via JSON`, + config: mcpConfig + }); + } catch (error) { + console.error('Error adding MCP server to Cursor via JSON:', error); + res.status(500).json({ + error: 'Failed to add MCP server', + details: error.message + }); + } +}); + +// GET /api/cursor/sessions - Get Cursor sessions from SQLite database +router.get('/sessions', async (req, res) => { + try { + const { projectPath } = req.query; + + // Calculate cwdID hash for the project path (Cursor uses MD5 hash) + const cwdId = crypto.createHash('md5').update(projectPath || process.cwd()).digest('hex'); + const cursorChatsPath = path.join(os.homedir(), '.cursor', 'chats', cwdId); + + console.log(`🔍 Looking for Cursor sessions in: ${cursorChatsPath}`); + + // Check if the directory exists + try { + await fs.access(cursorChatsPath); + } catch (error) { + // No sessions for this project + return res.json({ + success: true, + sessions: [], + cwdId: cwdId, + path: cursorChatsPath + }); + } + + // List all session directories + const sessionDirs = await fs.readdir(cursorChatsPath); + const sessions = []; + + for (const sessionId of sessionDirs) { + const sessionPath = path.join(cursorChatsPath, sessionId); + const storeDbPath = path.join(sessionPath, 'store.db'); + + try { + // Check if store.db exists + await fs.access(storeDbPath); + + // Open SQLite database + const db = await open({ + filename: storeDbPath, + driver: sqlite3.Database, + mode: sqlite3.OPEN_READONLY + }); + + // Get metadata from meta table + const metaRows = await db.all(` + SELECT key, value FROM meta + `); + + let sessionData = { + id: sessionId, + name: 'Untitled Session', + createdAt: null, + mode: null, + projectPath: projectPath, + lastMessage: null, + messageCount: 0 + }; + + // Parse meta table entries + for (const row of metaRows) { + if (row.value) { + try { + // Try to decode as hex-encoded JSON + const hexMatch = row.value.toString().match(/^[0-9a-fA-F]+$/); + if (hexMatch) { + const jsonStr = Buffer.from(row.value, 'hex').toString('utf8'); + const data = JSON.parse(jsonStr); + + if (row.key === 'agent') { + sessionData.name = data.name || sessionData.name; + sessionData.createdAt = data.createdAt; + sessionData.mode = data.mode; + sessionData.agentId = data.agentId; + sessionData.latestRootBlobId = data.latestRootBlobId; + } + } else { + // If not hex, use raw value for simple keys + if (row.key === 'name') { + sessionData.name = row.value.toString(); + } + } + } catch (e) { + console.log(`Could not parse meta value for key ${row.key}:`, e.message); + } + } + } + + // Get message count from blobs table + try { + const blobCount = await db.get(` + SELECT COUNT(*) as count FROM blobs + `); + sessionData.messageCount = blobCount.count; + + // Get the most recent blob for preview + const lastBlob = await db.get(` + SELECT data FROM blobs + ORDER BY id DESC + LIMIT 1 + `); + + if (lastBlob && lastBlob.data) { + try { + // Try to extract readable preview from blob (may contain binary with embedded JSON) + const raw = lastBlob.data.toString('utf8'); + let preview = ''; + // Attempt direct JSON parse + try { + const parsed = JSON.parse(raw); + if (parsed?.content) { + if (Array.isArray(parsed.content)) { + const firstText = parsed.content.find(p => p?.type === 'text' && p.text)?.text || ''; + preview = firstText; + } else if (typeof parsed.content === 'string') { + preview = parsed.content; + } + } + } catch (_) {} + if (!preview) { + // Strip non-printable and try to find JSON chunk + const cleaned = raw.replace(/[^\x09\x0A\x0D\x20-\x7E]/g, ''); + const s = cleaned; + const start = s.indexOf('{'); + const end = s.lastIndexOf('}'); + if (start !== -1 && end > start) { + const jsonStr = s.slice(start, end + 1); + try { + const parsed = JSON.parse(jsonStr); + if (parsed?.content) { + if (Array.isArray(parsed.content)) { + const firstText = parsed.content.find(p => p?.type === 'text' && p.text)?.text || ''; + preview = firstText; + } else if (typeof parsed.content === 'string') { + preview = parsed.content; + } + } + } catch (_) { + preview = s; + } + } else { + preview = s; + } + } + if (preview && preview.length > 0) { + sessionData.lastMessage = preview.substring(0, 100) + (preview.length > 100 ? '...' : ''); + } + } catch (e) { + console.log('Could not parse blob data:', e.message); + } + } + } catch (e) { + console.log('Could not read blobs:', e.message); + } + + await db.close(); + + sessions.push(sessionData); + + } catch (error) { + console.log(`Could not read session ${sessionId}:`, error.message); + } + } + + // Sort sessions by creation date (newest first) + sessions.sort((a, b) => { + if (!a.createdAt) return 1; + if (!b.createdAt) return -1; + return new Date(b.createdAt) - new Date(a.createdAt); + }); + + res.json({ + success: true, + sessions: sessions, + cwdId: cwdId, + path: cursorChatsPath + }); + + } catch (error) { + console.error('Error reading Cursor sessions:', error); + res.status(500).json({ + error: 'Failed to read Cursor sessions', + details: error.message + }); + } +}); + +// GET /api/cursor/sessions/:sessionId - Get specific Cursor session from SQLite +router.get('/sessions/:sessionId', async (req, res) => { + try { + const { sessionId } = req.params; + const { projectPath } = req.query; + + // Calculate cwdID hash for the project path + const cwdId = crypto.createHash('md5').update(projectPath || process.cwd()).digest('hex'); + const storeDbPath = path.join(os.homedir(), '.cursor', 'chats', cwdId, sessionId, 'store.db'); + + console.log(`📖 Reading Cursor session from: ${storeDbPath}`); + + // Open SQLite database + const db = await open({ + filename: storeDbPath, + driver: sqlite3.Database, + mode: sqlite3.OPEN_READONLY + }); + + // Get all blobs (conversation data) + const blobs = await db.all(` + SELECT id, data FROM blobs + ORDER BY id ASC + `); + + // Get metadata from meta table + const metaRows = await db.all(` + SELECT key, value FROM meta + `); + + // Parse metadata + let metadata = {}; + for (const row of metaRows) { + if (row.value) { + try { + // Try to decode as hex-encoded JSON + const hexMatch = row.value.toString().match(/^[0-9a-fA-F]+$/); + if (hexMatch) { + const jsonStr = Buffer.from(row.value, 'hex').toString('utf8'); + metadata[row.key] = JSON.parse(jsonStr); + } else { + metadata[row.key] = row.value.toString(); + } + } catch (e) { + metadata[row.key] = row.value.toString(); + } + } + } + + // Parse blob data to extract messages + const messages = []; + for (const blob of blobs) { + try { + // Attempt direct JSON parse first + const raw = blob.data.toString('utf8'); + let parsed; + try { + parsed = JSON.parse(raw); + } catch (_) { + // If not JSON, try to extract JSON from within binary-looking string + const cleaned = raw.replace(/[^\x09\x0A\x0D\x20-\x7E]/g, ''); + const start = cleaned.indexOf('{'); + const end = cleaned.lastIndexOf('}'); + if (start !== -1 && end > start) { + const jsonStr = cleaned.slice(start, end + 1); + try { + parsed = JSON.parse(jsonStr); + } catch (_) { + parsed = null; + } + } + } + if (parsed) { + messages.push({ id: blob.id, content: parsed }); + } else { + // Fallback to cleaned text content + const text = raw.replace(/[^\x09\x0A\x0D\x20-\x7E]/g, '').trim(); + messages.push({ id: blob.id, content: text }); + } + } catch (e) { + messages.push({ id: blob.id, content: blob.data.toString() }); + } + } + + await db.close(); + + res.json({ + success: true, + session: { + id: sessionId, + projectPath: projectPath, + messages: messages, + metadata: metadata, + cwdId: cwdId + } + }); + + } catch (error) { + console.error('Error reading Cursor session:', error); + res.status(500).json({ + error: 'Failed to read Cursor session', + details: error.message + }); + } +}); + +export default router; \ No newline at end of file diff --git a/src/App.jsx b/src/App.jsx index 14231f6..1cbd7eb 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -31,7 +31,7 @@ import { ThemeProvider } from './contexts/ThemeContext'; import { AuthProvider } from './contexts/AuthContext'; import ProtectedRoute from './components/ProtectedRoute'; import { useVersionCheck } from './hooks/useVersionCheck'; -import { api } from './utils/api'; +import { api, authenticatedFetch } from './utils/api'; // Main App component with routing @@ -192,6 +192,27 @@ function AppContent() { const response = await api.projects(); const data = await response.json(); + // Always fetch Cursor sessions for each project so we can combine views + for (let project of data) { + try { + const url = `/api/cursor/sessions?projectPath=${encodeURIComponent(project.fullPath || project.path)}`; + const cursorResponse = await authenticatedFetch(url); + if (cursorResponse.ok) { + const cursorData = await cursorResponse.json(); + if (cursorData.success && cursorData.sessions) { + project.cursorSessions = cursorData.sessions; + } else { + project.cursorSessions = []; + } + } else { + project.cursorSessions = []; + } + } catch (error) { + console.error(`Error fetching Cursor sessions for project ${project.name}:`, error); + project.cursorSessions = []; + } + } + // Optimize to preserve object references when data hasn't changed setProjects(prevProjects => { // If no previous projects, just set the new data @@ -210,7 +231,8 @@ function AppContent() { newProject.displayName !== prevProject.displayName || newProject.fullPath !== prevProject.fullPath || JSON.stringify(newProject.sessionMeta) !== JSON.stringify(prevProject.sessionMeta) || - JSON.stringify(newProject.sessions) !== JSON.stringify(prevProject.sessions) + JSON.stringify(newProject.sessions) !== JSON.stringify(prevProject.sessions) || + JSON.stringify(newProject.cursorSessions) !== JSON.stringify(prevProject.cursorSessions) ); }) || data.length !== prevProjects.length; @@ -236,16 +258,26 @@ function AppContent() { const shouldSwitchTab = !selectedSession || selectedSession.id !== sessionId; // Find the session across all projects for (const project of projects) { - const session = project.sessions?.find(s => s.id === sessionId); + let session = project.sessions?.find(s => s.id === sessionId); if (session) { setSelectedProject(project); - setSelectedSession(session); + setSelectedSession({ ...session, __provider: 'claude' }); // Only switch to chat tab if we're loading a different session if (shouldSwitchTab) { setActiveTab('chat'); } return; } + // Also check Cursor sessions + const cSession = project.cursorSessions?.find(s => s.id === sessionId); + if (cSession) { + setSelectedProject(project); + setSelectedSession({ ...cSession, __provider: 'cursor' }); + if (shouldSwitchTab) { + setActiveTab('chat'); + } + return; + } } // If session not found, it might be a newly created session @@ -270,6 +302,15 @@ function AppContent() { if (activeTab !== 'git' && activeTab !== 'preview') { setActiveTab('chat'); } + + // For Cursor sessions, we need to set the session ID differently + // since they're persistent and not created by Claude + const provider = localStorage.getItem('selected-provider') || 'claude'; + if (provider === 'cursor') { + // Cursor sessions have persistent IDs + sessionStorage.setItem('cursorSessionId', session.id); + } + if (isMobile) { setSidebarOpen(false); } diff --git a/src/components/ChatInterface.jsx b/src/components/ChatInterface.jsx index d61ddd3..a7587b0 100644 --- a/src/components/ChatInterface.jsx +++ b/src/components/ChatInterface.jsx @@ -21,10 +21,11 @@ import ReactMarkdown from 'react-markdown'; import { useDropzone } from 'react-dropzone'; import TodoList from './TodoList'; import ClaudeLogo from './ClaudeLogo.jsx'; +import CursorLogo from './CursorLogo.jsx'; import ClaudeStatus from './ClaudeStatus'; import { MicButton } from './MicButton.jsx'; -import { api } from '../utils/api'; +import { api, authenticatedFetch } from '../utils/api'; // Safe localStorage utility to handle quota exceeded errors const safeLocalStorage = { @@ -189,11 +190,15 @@ const MessageComponent = memo(({ message, index, prevMessage, createDiff, onFile ) : (
- + {(localStorage.getItem('selected-provider') || 'claude') === 'cursor' ? ( + + ) : ( + + )}
)}
- {message.type === 'error' ? 'Error' : 'Claude'} + {message.type === 'error' ? 'Error' : ((localStorage.getItem('selected-provider') || 'claude') === 'cursor' ? 'Cursor' : 'Claude')}
)} @@ -1143,6 +1148,48 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess const [slashPosition, setSlashPosition] = useState(-1); const [visibleMessageCount, setVisibleMessageCount] = useState(100); const [claudeStatus, setClaudeStatus] = useState(null); + const [provider, setProvider] = useState(() => { + return localStorage.getItem('selected-provider') || 'claude'; + }); + const [cursorModel, setCursorModel] = useState(() => { + return localStorage.getItem('cursor-model') || 'gpt-5'; + }); + // When selecting a session from Sidebar, auto-switch provider to match session's origin + useEffect(() => { + if (selectedSession && selectedSession.__provider && selectedSession.__provider !== provider) { + setProvider(selectedSession.__provider); + localStorage.setItem('selected-provider', selectedSession.__provider); + } + }, [selectedSession]); + + // Load Cursor default model from config + useEffect(() => { + if (provider === 'cursor') { + fetch('/api/cursor/config', { + headers: { + 'Authorization': `Bearer ${localStorage.getItem('auth-token')}` + } + }) + .then(res => res.json()) + .then(data => { + if (data.success && data.config?.model?.modelId) { + // Map Cursor model IDs to our simplified names + const modelMap = { + 'gpt-5': 'gpt-5', + 'claude-4-sonnet': 'sonnet-4', + 'sonnet-4': 'sonnet-4', + 'claude-4-opus': 'opus-4.1', + 'opus-4.1': 'opus-4.1' + }; + const mappedModel = modelMap[data.config.model.modelId] || data.config.model.modelId; + if (!localStorage.getItem('cursor-model')) { + setCursorModel(mappedModel); + } + } + }) + .catch(err => console.error('Error loading Cursor config:', err)); + } + }, [provider]); // Memoized diff calculation to prevent recalculating on every render @@ -1184,6 +1231,97 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess } }, []); + // Load Cursor session messages from SQLite via backend + const loadCursorSessionMessages = useCallback(async (projectPath, sessionId) => { + if (!projectPath || !sessionId) return []; + setIsLoadingSessionMessages(true); + try { + const url = `/api/cursor/sessions/${encodeURIComponent(sessionId)}?projectPath=${encodeURIComponent(projectPath)}`; + const res = await authenticatedFetch(url); + if (!res.ok) return []; + const data = await res.json(); + const blobs = data?.session?.messages || []; + const converted = []; + const now = Date.now(); + let idx = 0; + for (const blob of blobs) { + const content = blob.content; + let text = ''; + let role = 'assistant'; + try { + if (typeof content === 'string') { + // Attempt to extract embedded JSON first + const cleaned = content.replace(/[^\x09\x0A\x0D\x20-\x7E]/g, ''); + let extractedTexts = []; + const start = cleaned.indexOf('{'); + const end = cleaned.lastIndexOf('}'); + if (start !== -1 && end !== -1 && end > start) { + const jsonStr = cleaned.slice(start, end + 1); + try { + const parsed = JSON.parse(jsonStr); + if (parsed && parsed.content && Array.isArray(parsed.content)) { + for (const part of parsed.content) { + if (part?.type === 'text' && part.text) { + extractedTexts.push(part.text); + } + } + } + } catch (_) { + // JSON parse failed; fall back to cleaned text + } + } + if (extractedTexts.length > 0) { + extractedTexts.forEach(t => converted.push({ type: 'assistant', content: t, timestamp: new Date(now + (idx++)) })); + continue; + } + // No JSON; use cleaned readable text if any + const readable = cleaned.trim(); + if (readable) { + // Heuristic: short single token like 'hey' → user, otherwise assistant + const isLikelyUser = /^[a-zA-Z0-9.,!?\s]{1,10}$/.test(readable) && readable.toLowerCase().includes('hey'); + role = isLikelyUser ? 'user' : 'assistant'; + text = readable; + } else { + text = ''; + } + } else if (content?.message?.role && content?.message?.content) { + role = content.message.role === 'user' ? 'user' : 'assistant'; + if (Array.isArray(content.message.content)) { + text = content.message.content + .map(p => (typeof p === 'string' ? p : (p?.text || ''))) + .filter(Boolean) + .join('\n'); + } else if (typeof content.message.content === 'string') { + text = content.message.content; + } else { + text = JSON.stringify(content.message.content); + } + } else if (content?.content) { + // Some Cursor blobs may have { content: string } + text = typeof content.content === 'string' ? content.content : JSON.stringify(content.content); + } else { + text = JSON.stringify(content); + } + } catch (e) { + text = String(content); + } + if (text && text.trim()) { + converted.push({ + type: role, + content: text, + timestamp: new Date(now + (idx++)) + }); + } + } + return converted; + } catch (e) { + console.error('Error loading Cursor session messages:', e); + return []; + } finally { + setIsLoadingSessionMessages(false); + } + }, []); + // Actual diff calculation function const calculateDiff = (oldStr, newStr) => { const oldLines = oldStr.split('\n'); @@ -1349,31 +1487,47 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess // Load session messages when session changes const loadMessages = async () => { if (selectedSession && selectedProject) { - setCurrentSessionId(selectedSession.id); + const provider = localStorage.getItem('selected-provider') || 'claude'; - // Only load messages from API if this is a user-initiated session change - // For system-initiated changes, preserve existing messages and rely on WebSocket - if (!isSystemSessionChange) { - const messages = await loadSessionMessages(selectedProject.name, selectedSession.id); - setSessionMessages(messages); - // convertedMessages will be automatically updated via useMemo - // Scroll to bottom after loading session messages if auto-scroll is enabled - if (autoScrollToBottom) { - setTimeout(() => scrollToBottom(), 200); - } + if (provider === 'cursor') { + // For Cursor, set the session ID for resuming + setCurrentSessionId(selectedSession.id); + sessionStorage.setItem('cursorSessionId', selectedSession.id); + + // Load historical messages for Cursor session from SQLite + const projectPath = selectedProject.fullPath || selectedProject.path; + const converted = await loadCursorSessionMessages(projectPath, selectedSession.id); + setSessionMessages([]); + setChatMessages(converted); } else { - // Reset the flag after handling system session change - setIsSystemSessionChange(false); + // For Claude, load messages normally + setCurrentSessionId(selectedSession.id); + + // Only load messages from API if this is a user-initiated session change + // For system-initiated changes, preserve existing messages and rely on WebSocket + if (!isSystemSessionChange) { + const messages = await loadSessionMessages(selectedProject.name, selectedSession.id); + setSessionMessages(messages); + // convertedMessages will be automatically updated via useMemo + // Scroll to bottom after loading session messages if auto-scroll is enabled + if (autoScrollToBottom) { + setTimeout(() => scrollToBottom(), 200); + } + } else { + // Reset the flag after handling system session change + setIsSystemSessionChange(false); + } } } else { setChatMessages([]); setSessionMessages([]); setCurrentSessionId(null); + sessionStorage.removeItem('cursorSessionId'); } }; loadMessages(); - }, [selectedSession, selectedProject, loadSessionMessages, scrollToBottom, isSystemSessionChange]); + }, [selectedSession, selectedProject, loadSessionMessages, loadCursorSessionMessages, scrollToBottom, isSystemSessionChange]); // Update chatMessages when convertedMessages changes useEffect(() => { @@ -1441,6 +1595,22 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess case 'claude-response': const messageData = latestMessage.data.message || latestMessage.data; + // Handle Cursor streaming format (content_block_delta / content_block_stop) + if (messageData && typeof messageData === 'object' && messageData.type) { + if (messageData.type === 'content_block_delta' && messageData.delta?.text) { + setChatMessages(prev => [...prev, { + type: 'assistant', + content: messageData.delta.text, + timestamp: new Date() + }]); + return; + } + if (messageData.type === 'content_block_stop') { + // Nothing specific to do; leave as-is + return; + } + } + // Handle Claude CLI session duplication bug workaround: // When resuming a session, Claude CLI creates a new session instead of resuming. // We detect this by checking for system/init messages with session_id that differs @@ -1605,6 +1775,113 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess }]); break; + case 'cursor-system': + // Handle Cursor system/init messages similar to Claude + try { + const cdata = latestMessage.data; + if (cdata && cdata.type === 'system' && cdata.subtype === 'init' && cdata.session_id) { + // If we already have a session and this differs, switch (duplication/redirect) + if (currentSessionId && cdata.session_id !== currentSessionId) { + console.log('🔄 Cursor session switch detected:', { originalSession: currentSessionId, newSession: cdata.session_id }); + setIsSystemSessionChange(true); + if (onNavigateToSession) { + onNavigateToSession(cdata.session_id); + } + return; + } + // If we don't yet have a session, adopt this one + if (!currentSessionId) { + console.log('🔄 Cursor new session init detected:', { newSession: cdata.session_id }); + setIsSystemSessionChange(true); + if (onNavigateToSession) { + onNavigateToSession(cdata.session_id); + } + return; + } + } + // For other cursor-system messages, avoid dumping raw objects to chat + console.log('Cursor system message:', latestMessage.data); + } catch (e) { + console.warn('Error handling cursor-system message:', e); + } + break; + + case 'cursor-user': + // Handle Cursor user messages (usually echoes) + console.log('Cursor user message:', latestMessage.data); + // Don't add user messages as they're already shown from input + break; + + case 'cursor-tool-use': + // Handle Cursor tool use messages + setChatMessages(prev => [...prev, { + type: 'assistant', + content: `Using tool: ${latestMessage.tool} ${latestMessage.input ? `with ${latestMessage.input}` : ''}`, + timestamp: new Date(), + isToolUse: true, + toolName: latestMessage.tool, + toolInput: latestMessage.input + }]); + break; + + case 'cursor-error': + // Show Cursor errors as error messages in chat + setChatMessages(prev => [...prev, { + type: 'error', + content: `Cursor error: ${latestMessage.error || 'Unknown error'}`, + timestamp: new Date() + }]); + break; + + case 'cursor-result': + // Handle Cursor completion and final result text + setIsLoading(false); + setCanAbortSession(false); + setClaudeStatus(null); + try { + const r = latestMessage.data || {}; + const textResult = typeof r.result === 'string' ? r.result : ''; + if (textResult && textResult.trim()) { + setChatMessages(prev => [...prev, { + type: r.is_error ? 'error' : 'assistant', + content: textResult, + timestamp: new Date() + }]); + } + } catch (e) { + console.warn('Error handling cursor-result message:', e); + } + + // Mark session as inactive + const cursorSessionId = currentSessionId || sessionStorage.getItem('pendingSessionId'); + if (cursorSessionId && onSessionInactive) { + onSessionInactive(cursorSessionId); + } + + // Store session ID for future use + if (cursorSessionId && !currentSessionId) { + setCurrentSessionId(cursorSessionId); + sessionStorage.removeItem('pendingSessionId'); + } + break; + + case 'cursor-output': + // Handle Cursor raw terminal output; strip ANSI and ignore empty control-only payloads + try { + const raw = String(latestMessage.data ?? ''); + const cleaned = raw.replace(/\x1b\[[0-9;?]*[A-Za-z]/g, '').replace(/[\x00-\x08\x0B\x0C\x0E-\x1F]/g, '').trim(); + if (cleaned) { + setChatMessages(prev => [...prev, { + type: 'assistant', + content: cleaned, + timestamp: new Date() + }]); + } + } catch (e) { + console.warn('Error handling cursor-output message:', e); + } + break; + case 'claude-complete': setIsLoading(false); setCanAbortSession(false); @@ -2027,10 +2304,11 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess onSessionActive(sessionToActivate); } - // Get tools settings from localStorage + // Get tools settings from localStorage based on provider const getToolsSettings = () => { try { - const savedSettings = safeLocalStorage.getItem('claude-tools-settings'); + const settingsKey = provider === 'cursor' ? 'cursor-tools-settings' : 'claude-tools-settings'; + const savedSettings = safeLocalStorage.getItem(settingsKey); if (savedSettings) { return JSON.parse(savedSettings); } @@ -2046,20 +2324,40 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess const toolsSettings = getToolsSettings(); - // Send command to Claude CLI via WebSocket with images - sendMessage({ - type: 'claude-command', - command: input, - options: { - projectPath: selectedProject.path, - cwd: selectedProject.fullPath, + // Send command based on provider + if (provider === 'cursor') { + // Send Cursor command (always use cursor-command; include resume/sessionId when replying) + sendMessage({ + type: 'cursor-command', + command: input, sessionId: currentSessionId, - resume: !!currentSessionId, - toolsSettings: toolsSettings, - permissionMode: permissionMode, - images: uploadedImages // Pass images to backend - } - }); + options: { + // Prefer fullPath (actual cwd for project), fallback to path + cwd: selectedProject.fullPath || selectedProject.path, + projectPath: selectedProject.fullPath || selectedProject.path, + sessionId: currentSessionId, + resume: !!currentSessionId, + model: cursorModel, + skipPermissions: toolsSettings?.skipPermissions || false, + toolsSettings: toolsSettings + } + }); + } else { + // Send Claude command (existing code) + sendMessage({ + type: 'claude-command', + command: input, + options: { + projectPath: selectedProject.path, + cwd: selectedProject.fullPath, + sessionId: currentSessionId, + resume: !!currentSessionId, + toolsSettings: toolsSettings, + permissionMode: permissionMode, + images: uploadedImages // Pass images to backend + } + }); + } setInput(''); setAttachedImages([]); @@ -2211,7 +2509,8 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess if (currentSessionId && canAbortSession) { sendMessage({ type: 'abort-session', - sessionId: currentSessionId + sessionId: currentSessionId, + provider: provider }); } }; @@ -2303,10 +2602,14 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess
-
- C +
+ {(localStorage.getItem('selected-provider') || 'claude') === 'cursor' ? ( + + ) : ( + + )}
-
Claude
+
{(localStorage.getItem('selected-provider') || 'claude') === 'cursor' ? 'Cursor' : 'Claude'}
{/* Abort button removed - functionality not yet implemented at backend */}
@@ -2329,12 +2632,66 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess
- {/* Claude Working Status - positioned above the input form */} - + {/* Provider Selection and Working Status - positioned above the input form */} +
+
+ {/* Provider & Model Selection or Fixed Provider for existing session */} +
+ {selectedSession?.__provider ? ( +
+ {selectedSession.__provider === 'cursor' ? ( + + ) : ( + + )} + {selectedSession.__provider} +
+ ) : ( + <> + + {provider === 'cursor' && ( + + )} + + )} +
+ + {/* Status Display */} +
+ +
+
+
{/* Permission Mode Selector with scroll to bottom button - Above input, clickable for mobile */}
diff --git a/src/components/ClaudeStatus.jsx b/src/components/ClaudeStatus.jsx index 52bf39d..c19e37b 100644 --- a/src/components/ClaudeStatus.jsx +++ b/src/components/ClaudeStatus.jsx @@ -1,7 +1,7 @@ import React, { useState, useEffect } from 'react'; import { cn } from '../lib/utils'; -function ClaudeStatus({ status, onAbort, isLoading }) { +function ClaudeStatus({ status, onAbort, isLoading, provider = 'claude' }) { const [elapsedTime, setElapsedTime] = useState(0); const [animationPhase, setAnimationPhase] = useState(0); const [fakeTokens, setFakeTokens] = useState(0); diff --git a/src/components/CursorLogo.jsx b/src/components/CursorLogo.jsx new file mode 100644 index 0000000..18bda9d --- /dev/null +++ b/src/components/CursorLogo.jsx @@ -0,0 +1,9 @@ +import React from 'react'; + +const CursorLogo = ({ className = 'w-5 h-5' }) => { + return ( + Cursor + ); +}; + +export default CursorLogo; diff --git a/src/components/MainContent.jsx b/src/components/MainContent.jsx index 52dc236..645e84d 100644 --- a/src/components/MainContent.jsx +++ b/src/components/MainContent.jsx @@ -157,7 +157,7 @@ function MainContent({ {activeTab === 'chat' && selectedSession ? (

- {selectedSession.summary} + {selectedSession.__provider === 'cursor' ? (selectedSession.name || 'Untitled Session') : (selectedSession.summary || 'New Session')}

{selectedProject.displayName} • {selectedSession.id} diff --git a/src/components/Shell.jsx b/src/components/Shell.jsx index 3033b03..7864812 100644 --- a/src/components/Shell.jsx +++ b/src/components/Shell.jsx @@ -530,11 +530,16 @@ function Shell({ selectedProject, selectedSession, isActive }) {
- {selectedSession && ( - - ({selectedSession.summary.slice(0, 30)}...) - - )} + {selectedSession && (() => { + const displaySessionName = selectedSession.__provider === 'cursor' + ? (selectedSession.name || 'Untitled Session') + : (selectedSession.summary || 'New Session'); + return ( + + ({displaySessionName.slice(0, 30)}...) + + ); + })()} {!selectedSession && ( (New Session) )} @@ -601,7 +606,12 @@ function Shell({ selectedProject, selectedSession, isActive }) {

{selectedSession ? - `Resume session: ${selectedSession.summary.slice(0, 50)}...` : + (() => { + const displaySessionName = selectedSession.__provider === 'cursor' + ? (selectedSession.name || 'Untitled Session') + : (selectedSession.summary || 'New Session'); + return `Resume session: ${displaySessionName.slice(0, 50)}...`; + })() : 'Start a new Claude session' }

diff --git a/src/components/Sidebar.jsx b/src/components/Sidebar.jsx index 9a1e111..36f3bbf 100644 --- a/src/components/Sidebar.jsx +++ b/src/components/Sidebar.jsx @@ -7,6 +7,7 @@ import { Input } from './ui/input'; import { FolderOpen, Folder, Plus, MessageSquare, Clock, ChevronDown, ChevronRight, Edit3, Check, X, Trash2, Settings, FolderPlus, RefreshCw, Sparkles, Edit2, Star, Search } from 'lucide-react'; import { cn } from '../lib/utils'; import ClaudeLogo from './ClaudeLogo'; +import CursorLogo from './CursorLogo.jsx'; import { api } from '../utils/api'; // Move formatTimeAgo outside component to avoid recreation on every render @@ -202,9 +203,12 @@ function Sidebar({ // Helper function to get all sessions for a project (initial + additional) const getAllSessions = (project) => { - const initialSessions = project.sessions || []; - const additional = additionalSessions[project.name] || []; - return [...initialSessions, ...additional]; + // Combine Claude and Cursor sessions; Sidebar will display icon per row + const claudeSessions = [...(project.sessions || []), ...(additionalSessions[project.name] || [])].map(s => ({ ...s, __provider: 'claude' })); + const cursorSessions = (project.cursorSessions || []).map(s => ({ ...s, __provider: 'cursor' })); + // Sort by most recent activity/date + const normalizeDate = (s) => new Date(s.__provider === 'cursor' ? s.createdAt : s.lastActivity); + return [...claudeSessions, ...cursorSessions].sort((a, b) => normalizeDate(b) - normalizeDate(a)); }; // Helper function to get the last activity date for a project @@ -979,11 +983,19 @@ function Sidebar({
) : ( getAllSessions(project).map((session) => { + // Handle both Claude and Cursor session formats + const isCursorSession = session.__provider === 'cursor'; + // Calculate if session is active (within last 10 minutes) - const sessionDate = new Date(session.lastActivity); + const sessionDate = new Date(isCursorSession ? session.createdAt : session.lastActivity); const diffInMinutes = Math.floor((currentTime - sessionDate) / (1000 * 60)); const isActive = diffInMinutes < 10; + // Get session display values + const sessionName = isCursorSession ? (session.name || 'Untitled Session') : (session.summary || 'New Session'); + const sessionTime = isCursorSession ? session.createdAt : session.lastActivity; + const messageCount = session.messageCount || 0; + return (
{/* Active session indicator dot */} @@ -1014,38 +1026,49 @@ function Sidebar({ "w-5 h-5 rounded-md flex items-center justify-center flex-shrink-0", selectedSession?.id === session.id ? "bg-primary/10" : "bg-muted/50" )}> - + {isCursorSession ? ( + + ) : ( + + )}
- {session.summary || 'New Session'} + {sessionName}
-
+
- {formatTimeAgo(session.lastActivity, currentTime)} + {formatTimeAgo(sessionTime, currentTime)} - {session.messageCount > 0 && ( + {messageCount > 0 && ( - {session.messageCount} + {messageCount} )} + {/* Provider tiny icon */} + + {isCursorSession ? ( + + ) : ( + + )} +
- {/* Mobile delete button */} - + {/* Mobile delete button - only for Claude sessions */} + {!isCursorSession && ( + + )}
@@ -1062,26 +1085,39 @@ function Sidebar({ onTouchEnd={handleTouchClick(() => onSessionSelect(session))} >
- + {isCursorSession ? ( + + ) : ( + + )}
- {session.summary || 'New Session'} + {sessionName}
- {formatTimeAgo(session.lastActivity, currentTime)} + {formatTimeAgo(sessionTime, currentTime)} - {session.messageCount > 0 && ( + {messageCount > 0 && ( - {session.messageCount} + {messageCount} )} + {/* Provider tiny icon */} + + {isCursorSession ? ( + + ) : ( + + )} +
- {/* Desktop hover buttons */} + {/* Desktop hover buttons - only for Claude sessions */} + {!isCursorSession && (
{editingSession === session.id ? ( <> @@ -1168,6 +1204,7 @@ function Sidebar({ )}
+ )}
); diff --git a/src/components/ToolsSettings.jsx b/src/components/ToolsSettings.jsx index 0c8eb0a..b0939c3 100644 --- a/src/components/ToolsSettings.jsx +++ b/src/components/ToolsSettings.jsx @@ -41,7 +41,16 @@ function ToolsSettings({ isOpen, onClose, projects = [] }) { const [mcpToolsLoading, setMcpToolsLoading] = useState({}); const [activeTab, setActiveTab] = useState('tools'); const [jsonValidationError, setJsonValidationError] = useState(''); - // Common tool patterns + const [toolsProvider, setToolsProvider] = useState('claude'); // 'claude' or 'cursor' + + // Cursor-specific states + const [cursorAllowedCommands, setCursorAllowedCommands] = useState([]); + const [cursorDisallowedCommands, setCursorDisallowedCommands] = useState([]); + const [cursorSkipPermissions, setCursorSkipPermissions] = useState(false); + const [newCursorCommand, setNewCursorCommand] = useState(''); + const [newCursorDisallowedCommand, setNewCursorDisallowedCommand] = useState(''); + const [cursorMcpServers, setCursorMcpServers] = useState([]); + // Common tool patterns for Claude const commonTools = [ 'Bash(git log:*)', 'Bash(git diff:*)', @@ -58,7 +67,45 @@ function ToolsSettings({ isOpen, onClose, projects = [] }) { 'WebFetch', 'WebSearch' ]; + + // Common shell commands for Cursor + const commonCursorCommands = [ + 'Shell(ls)', + 'Shell(mkdir)', + 'Shell(cd)', + 'Shell(cat)', + 'Shell(echo)', + 'Shell(git status)', + 'Shell(git diff)', + 'Shell(git log)', + 'Shell(npm install)', + 'Shell(npm run)', + 'Shell(python)', + 'Shell(node)' + ]; + // Fetch Cursor MCP servers + const fetchCursorMcpServers = async () => { + try { + const token = localStorage.getItem('auth-token'); + const response = await fetch('/api/cursor/mcp', { + headers: { + 'Authorization': `Bearer ${token}`, + 'Content-Type': 'application/json' + } + }); + + if (response.ok) { + const data = await response.json(); + setCursorMcpServers(data.servers || []); + } else { + console.error('Failed to fetch Cursor MCP servers'); + } + } catch (error) { + console.error('Error fetching Cursor MCP servers:', error); + } + }; + // MCP API functions const fetchMcpServers = async () => { try { @@ -268,7 +315,7 @@ function ToolsSettings({ isOpen, onClose, projects = [] }) { const loadSettings = async () => { try { - // Load from localStorage + // Load Claude settings from localStorage const savedSettings = localStorage.getItem('claude-tools-settings'); if (savedSettings) { @@ -284,9 +331,27 @@ function ToolsSettings({ isOpen, onClose, projects = [] }) { setSkipPermissions(false); setProjectSortOrder('name'); } + + // Load Cursor settings from localStorage + const savedCursorSettings = localStorage.getItem('cursor-tools-settings'); + + if (savedCursorSettings) { + const cursorSettings = JSON.parse(savedCursorSettings); + setCursorAllowedCommands(cursorSettings.allowedCommands || []); + setCursorDisallowedCommands(cursorSettings.disallowedCommands || []); + setCursorSkipPermissions(cursorSettings.skipPermissions || false); + } else { + // Set Cursor defaults + setCursorAllowedCommands([]); + setCursorDisallowedCommands([]); + setCursorSkipPermissions(false); + } // Load MCP servers from API await fetchMcpServers(); + + // Load Cursor MCP servers + await fetchCursorMcpServers(); } catch (error) { console.error('Error loading tool settings:', error); // Set defaults on error @@ -302,7 +367,8 @@ function ToolsSettings({ isOpen, onClose, projects = [] }) { setSaveStatus(null); try { - const settings = { + // Save Claude settings + const claudeSettings = { allowedTools, disallowedTools, skipPermissions, @@ -310,9 +376,17 @@ function ToolsSettings({ isOpen, onClose, projects = [] }) { lastUpdated: new Date().toISOString() }; + // Save Cursor settings + const cursorSettings = { + allowedCommands: cursorAllowedCommands, + disallowedCommands: cursorDisallowedCommands, + skipPermissions: cursorSkipPermissions, + lastUpdated: new Date().toISOString() + }; // Save to localStorage - localStorage.setItem('claude-tools-settings', JSON.stringify(settings)); + localStorage.setItem('claude-tools-settings', JSON.stringify(claudeSettings)); + localStorage.setItem('cursor-tools-settings', JSON.stringify(cursorSettings)); setSaveStatus('success'); @@ -635,6 +709,36 @@ function ToolsSettings({ isOpen, onClose, projects = [] }) { {activeTab === 'tools' && (
+ {/* Provider Tabs */} +
+
+ + +
+
+ + {/* Claude Tools Content */} + {toolsProvider === 'claude' && ( +
+ {/* Skip Permissions */}
@@ -1360,6 +1464,216 @@ function ToolsSettings({ isOpen, onClose, projects = [] }) { )}
)} + + {/* Cursor Tools Content */} + {toolsProvider === 'cursor' && ( +
+ + {/* Skip Permissions for Cursor */} +
+
+ +

+ Cursor Permission Settings +

+
+
+ +
+
+ + {/* Allowed Shell Commands */} +
+
+ +

+ Allowed Shell Commands +

+
+

+ Shell commands that are automatically allowed without prompting for permission +

+ +
+ setNewCursorCommand(e.target.value)} + placeholder='e.g., "Shell(ls)" or "Shell(git status)"' + onKeyPress={(e) => { + if (e.key === 'Enter') { + if (newCursorCommand && !cursorAllowedCommands.includes(newCursorCommand)) { + setCursorAllowedCommands([...cursorAllowedCommands, newCursorCommand]); + setNewCursorCommand(''); + } + } + }} + className="flex-1 h-10 touch-manipulation" + style={{ fontSize: '16px' }} + /> + +
+ + {/* Common commands quick add */} +
+

+ Quick add common commands: +

+
+ {commonCursorCommands.map(cmd => ( + + ))} +
+
+ +
+ {cursorAllowedCommands.map(cmd => ( +
+ + {cmd} + + +
+ ))} + {cursorAllowedCommands.length === 0 && ( +
+ No allowed shell commands configured +
+ )} +
+
+ + {/* Disallowed Shell Commands */} +
+
+ +

+ Disallowed Shell Commands +

+
+

+ Shell commands that should always be denied +

+ +
+ setNewCursorDisallowedCommand(e.target.value)} + placeholder='e.g., "Shell(rm -rf)" or "Shell(sudo)"' + onKeyPress={(e) => { + if (e.key === 'Enter') { + if (newCursorDisallowedCommand && !cursorDisallowedCommands.includes(newCursorDisallowedCommand)) { + setCursorDisallowedCommands([...cursorDisallowedCommands, newCursorDisallowedCommand]); + setNewCursorDisallowedCommand(''); + } + } + }} + className="flex-1 h-10 touch-manipulation" + style={{ fontSize: '16px' }} + /> + +
+ +
+ {cursorDisallowedCommands.map(cmd => ( +
+ + {cmd} + + +
+ ))} + {cursorDisallowedCommands.length === 0 && ( +
+ No disallowed shell commands configured +
+ )} +
+
+ + {/* Help Section */} +
+

+ Cursor Shell Command Examples: +

+
    +
  • "Shell(ls)" - Allow ls command
  • +
  • "Shell(git status)" - Allow git status command
  • +
  • "Shell(mkdir)" - Allow mkdir command
  • +
  • "-f" flag - Skip all permission prompts (dangerous)
  • +
+
+
+ )} +
+ )}