From fbad3a90f83ef0353c030cbde7d254174ea87983 Mon Sep 17 00:00:00 2001 From: viper151 Date: Wed, 15 Apr 2026 12:02:27 +0000 Subject: [PATCH 01/10] chore(release): v1.29.3 --- CHANGELOG.md | 10 ++++++++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 149443d3..ad8eeebf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,16 @@ All notable changes to CloudCLI UI will be documented in this file. +## [1.29.3](https://github.com/siteboon/claudecodeui/compare/v1.29.2...v1.29.3) (2026-04-15) + +### Bug Fixes + +* **version-upgrade-modal:** implement reload countdown and update UI messages ([#655](https://github.com/siteboon/claudecodeui/issues/655)) ([6413042](https://github.com/siteboon/claudecodeui/commit/641304242d7705b54aab65faa4a7673438c92c60)) + +### Maintenance + +* remove unused route (migrated to providers already) ([31f28a2](https://github.com/siteboon/claudecodeui/commit/31f28a2c183f6ead50941027632d7ab64b7bb2d4)) + ## [1.29.2](https://github.com/siteboon/claudecodeui/compare/v1.29.1...v1.29.2) (2026-04-14) ### Bug Fixes diff --git a/package-lock.json b/package-lock.json index 0905f759..d23f8c7b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@cloudcli-ai/cloudcli", - "version": "1.29.2", + "version": "1.29.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@cloudcli-ai/cloudcli", - "version": "1.29.2", + "version": "1.29.3", "hasInstallScript": true, "license": "AGPL-3.0-or-later", "dependencies": { diff --git a/package.json b/package.json index b4d2653f..553f528a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@cloudcli-ai/cloudcli", - "version": "1.29.2", + "version": "1.29.3", "description": "A web-based UI for Claude Code CLI", "type": "module", "main": "dist-server/server/index.js", From 63e996bb77cfa97b1f55f6bdccc50161a75a3eee Mon Sep 17 00:00:00 2001 From: Simos Mikelatos Date: Thu, 16 Apr 2026 09:46:09 +0200 Subject: [PATCH 02/10] refactor(server): extract URL detection and color utils from index.js (#657) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit No behavioral changes — 1:1 code move with imports replacing inline definitions. --- server/index.js | 83 +---------------------------------- server/utils/colors.js | 21 +++++++++ server/utils/url-detection.js | 71 ++++++++++++++++++++++++++++++ 3 files changed, 94 insertions(+), 81 deletions(-) create mode 100644 server/utils/colors.js create mode 100644 server/utils/url-detection.js diff --git a/server/index.js b/server/index.js index 3e5e657d..eee06d9c 100755 --- a/server/index.js +++ b/server/index.js @@ -11,25 +11,7 @@ const __dirname = getModuleDir(import.meta.url); const APP_ROOT = findAppRoot(__dirname); const installMode = fs.existsSync(path.join(APP_ROOT, '.git')) ? 'git' : 'npm'; -// ANSI color codes for terminal output -const colors = { - reset: '\x1b[0m', - bright: '\x1b[1m', - cyan: '\x1b[36m', - green: '\x1b[32m', - yellow: '\x1b[33m', - blue: '\x1b[34m', - dim: '\x1b[2m', -}; - -const c = { - info: (text) => `${colors.cyan}${text}${colors.reset}`, - ok: (text) => `${colors.green}${text}${colors.reset}`, - warn: (text) => `${colors.yellow}${text}${colors.reset}`, - tip: (text) => `${colors.blue}${text}${colors.reset}`, - bright: (text) => `${colors.bright}${text}${colors.reset}`, - dim: (text) => `${colors.dim}${text}${colors.reset}`, -}; +import { c } from './utils/colors.js'; console.log('SERVER_PORT from env:', process.env.SERVER_PORT); @@ -226,68 +208,7 @@ const server = http.createServer(app); const ptySessionsMap = new Map(); const PTY_SESSION_TIMEOUT = 30 * 60 * 1000; const SHELL_URL_PARSE_BUFFER_LIMIT = 32768; -const ANSI_ESCAPE_SEQUENCE_REGEX = /\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~]|\][^\x07]*(?:\x07|\x1B\\))/g; -const TRAILING_URL_PUNCTUATION_REGEX = /[)\]}>.,;:!?]+$/; - -function stripAnsiSequences(value = '') { - return value.replace(ANSI_ESCAPE_SEQUENCE_REGEX, ''); -} - -function normalizeDetectedUrl(url) { - if (!url || typeof url !== 'string') return null; - - const cleaned = url.trim().replace(TRAILING_URL_PUNCTUATION_REGEX, ''); - if (!cleaned) return null; - - try { - const parsed = new URL(cleaned); - if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') { - return null; - } - return parsed.toString(); - } catch { - return null; - } -} - -function extractUrlsFromText(value = '') { - const directMatches = value.match(/https?:\/\/[^\s<>"'`\\\x1b\x07]+/gi) || []; - - // Handle wrapped terminal URLs split across lines by terminal width. - const wrappedMatches = []; - const continuationRegex = /^[A-Za-z0-9\-._~:/?#\[\]@!$&'()*+,;=%]+$/; - const lines = value.split(/\r?\n/); - for (let i = 0; i < lines.length; i++) { - const line = lines[i].trim(); - const startMatch = line.match(/https?:\/\/[^\s<>"'`\\\x1b\x07]+/i); - if (!startMatch) continue; - - let combined = startMatch[0]; - let j = i + 1; - while (j < lines.length) { - const continuation = lines[j].trim(); - if (!continuation) break; - if (!continuationRegex.test(continuation)) break; - combined += continuation; - j++; - } - - wrappedMatches.push(combined.replace(/\r?\n\s*/g, '')); - } - - return Array.from(new Set([...directMatches, ...wrappedMatches])); -} - -function shouldAutoOpenUrlFromOutput(value = '') { - const normalized = value.toLowerCase(); - return ( - normalized.includes('browser didn\'t open') || - normalized.includes('open this url') || - normalized.includes('continue in your browser') || - normalized.includes('press enter to open') || - normalized.includes('open_url:') - ); -} +import { stripAnsiSequences, normalizeDetectedUrl, extractUrlsFromText, shouldAutoOpenUrlFromOutput } from './utils/url-detection.js'; // Single WebSocket server that handles both paths const wss = new WebSocketServer({ diff --git a/server/utils/colors.js b/server/utils/colors.js new file mode 100644 index 00000000..5dfa8a1d --- /dev/null +++ b/server/utils/colors.js @@ -0,0 +1,21 @@ +// ANSI color codes for terminal output +const colors = { + reset: '\x1b[0m', + bright: '\x1b[1m', + cyan: '\x1b[36m', + green: '\x1b[32m', + yellow: '\x1b[33m', + blue: '\x1b[34m', + dim: '\x1b[2m', +}; + +const c = { + info: (text) => `${colors.cyan}${text}${colors.reset}`, + ok: (text) => `${colors.green}${text}${colors.reset}`, + warn: (text) => `${colors.yellow}${text}${colors.reset}`, + tip: (text) => `${colors.blue}${text}${colors.reset}`, + bright: (text) => `${colors.bright}${text}${colors.reset}`, + dim: (text) => `${colors.dim}${text}${colors.reset}`, +}; + +export { colors, c }; diff --git a/server/utils/url-detection.js b/server/utils/url-detection.js new file mode 100644 index 00000000..428b306d --- /dev/null +++ b/server/utils/url-detection.js @@ -0,0 +1,71 @@ +const ANSI_ESCAPE_SEQUENCE_REGEX = /\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~]|\][^\x07]*(?:\x07|\x1B\\))/g; +const TRAILING_URL_PUNCTUATION_REGEX = /[)\]}>.,;:!?]+$/; + +function stripAnsiSequences(value = '') { + return value.replace(ANSI_ESCAPE_SEQUENCE_REGEX, ''); +} + +function normalizeDetectedUrl(url) { + if (!url || typeof url !== 'string') return null; + + const cleaned = url.trim().replace(TRAILING_URL_PUNCTUATION_REGEX, ''); + if (!cleaned) return null; + + try { + const parsed = new URL(cleaned); + if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') { + return null; + } + return parsed.toString(); + } catch { + return null; + } +} + +function extractUrlsFromText(value = '') { + const directMatches = value.match(/https?:\/\/[^\s<>"'`\\\x1b\x07]+/gi) || []; + + // Handle wrapped terminal URLs split across lines by terminal width. + const wrappedMatches = []; + const continuationRegex = /^[A-Za-z0-9\-._~:/?#\[\]@!$&'()*+,;=%]+$/; + const lines = value.split(/\r?\n/); + for (let i = 0; i < lines.length; i++) { + const line = lines[i].trim(); + const startMatch = line.match(/https?:\/\/[^\s<>"'`\\\x1b\x07]+/i); + if (!startMatch) continue; + + let combined = startMatch[0]; + let j = i + 1; + while (j < lines.length) { + const continuation = lines[j].trim(); + if (!continuation) break; + if (!continuationRegex.test(continuation)) break; + combined += continuation; + j++; + } + + wrappedMatches.push(combined.replace(/\r?\n\s*/g, '')); + } + + return Array.from(new Set([...directMatches, ...wrappedMatches])); +} + +function shouldAutoOpenUrlFromOutput(value = '') { + const normalized = value.toLowerCase(); + return ( + normalized.includes('browser didn\'t open') || + normalized.includes('open this url') || + normalized.includes('continue in your browser') || + normalized.includes('press enter to open') || + normalized.includes('open_url:') + ); +} + +export { + ANSI_ESCAPE_SEQUENCE_REGEX, + TRAILING_URL_PUNCTUATION_REGEX, + stripAnsiSequences, + normalizeDetectedUrl, + extractUrlsFromText, + shouldAutoOpenUrlFromOutput +}; From 4c106a5083d90989bbeedaefdbb68f5b3fa6fd58 Mon Sep 17 00:00:00 2001 From: simosmik Date: Thu, 16 Apr 2026 07:58:32 +0000 Subject: [PATCH 03/10] fix: pass pathToClaudeCodeExecutable to SDK when CLAUDE_CLI_PATH is set Closes #468 --- server/claude-sdk.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/server/claude-sdk.js b/server/claude-sdk.js index 918a7bd6..919d8632 100644 --- a/server/claude-sdk.js +++ b/server/claude-sdk.js @@ -148,6 +148,10 @@ function mapCliOptionsToSDK(options = {}) { const sdkOptions = {}; + if (process.env.CLAUDE_CLI_PATH) { + sdkOptions.pathToClaudeCodeExecutable = process.env.CLAUDE_CLI_PATH; + } + // Map working directory if (cwd) { sdkOptions.cwd = cwd; From 09486016e67d97358c228ebc6eb4502ccb0012e4 Mon Sep 17 00:00:00 2001 From: simosmik Date: Thu, 16 Apr 2026 08:08:36 +0000 Subject: [PATCH 04/10] chore: upgrade commit lint to 20.5.0 --- package-lock.json | 206 +++++++++++++++++++--------------------------- package.json | 4 +- 2 files changed, 86 insertions(+), 124 deletions(-) diff --git a/package-lock.json b/package-lock.json index d23f8c7b..6fda6017 100644 --- a/package-lock.json +++ b/package-lock.json @@ -72,8 +72,8 @@ "cloudcli": "dist-server/server/cli.js" }, "devDependencies": { - "@commitlint/cli": "^20.4.3", - "@commitlint/config-conventional": "^20.4.3", + "@commitlint/cli": "^20.5.0", + "@commitlint/config-conventional": "^20.5.0", "@eslint/js": "^9.39.3", "@release-it/conventional-changelog": "^10.0.5", "@types/node": "^22.19.7", @@ -654,17 +654,17 @@ } }, "node_modules/@commitlint/cli": { - "version": "20.4.3", - "resolved": "https://registry.npmjs.org/@commitlint/cli/-/cli-20.4.3.tgz", - "integrity": "sha512-Z37EMoDT7+Upg500vlr/vZrgRsb6Xc5JAA3Tv7BYbobnN/ZpqUeZnSLggBg2+1O+NptRDtyujr2DD1CPV2qwhA==", + "version": "20.5.0", + "resolved": "https://registry.npmjs.org/@commitlint/cli/-/cli-20.5.0.tgz", + "integrity": "sha512-yNkyN/tuKTJS3wdVfsZ2tXDM4G4Gi7z+jW54Cki8N8tZqwKBltbIvUUrSbT4hz1bhW/h0CdR+5sCSpXD+wMKaQ==", "dev": true, "license": "MIT", "dependencies": { - "@commitlint/format": "^20.4.3", - "@commitlint/lint": "^20.4.3", - "@commitlint/load": "^20.4.3", - "@commitlint/read": "^20.4.3", - "@commitlint/types": "^20.4.3", + "@commitlint/format": "^20.5.0", + "@commitlint/lint": "^20.5.0", + "@commitlint/load": "^20.5.0", + "@commitlint/read": "^20.5.0", + "@commitlint/types": "^20.5.0", "tinyexec": "^1.0.0", "yargs": "^17.0.0" }, @@ -676,13 +676,13 @@ } }, "node_modules/@commitlint/config-conventional": { - "version": "20.4.3", - "resolved": "https://registry.npmjs.org/@commitlint/config-conventional/-/config-conventional-20.4.3.tgz", - "integrity": "sha512-9RtLySbYQAs8yEqWEqhSZo9nYhbm57jx7qHXtgRmv/nmeQIjjMcwf6Dl+y5UZcGWgWx435TAYBURONaJIuCjWg==", + "version": "20.5.0", + "resolved": "https://registry.npmjs.org/@commitlint/config-conventional/-/config-conventional-20.5.0.tgz", + "integrity": "sha512-t3Ni88rFw1XMa4nZHgOKJ8fIAT9M2j5TnKyTqJzsxea7FUetlNdYFus9dz+MhIRZmc16P0PPyEfh6X2d/qw8SA==", "dev": true, "license": "MIT", "dependencies": { - "@commitlint/types": "^20.4.3", + "@commitlint/types": "^20.5.0", "conventional-changelog-conventionalcommits": "^9.2.0" }, "engines": { @@ -690,13 +690,13 @@ } }, "node_modules/@commitlint/config-validator": { - "version": "20.4.3", - "resolved": "https://registry.npmjs.org/@commitlint/config-validator/-/config-validator-20.4.3.tgz", - "integrity": "sha512-jCZpZFkcSL3ZEdL5zgUzFRdytv3xPo8iukTe9VA+QGus/BGhpp1xXSVu2B006GLLb2gYUAEGEqv64kTlpZNgmA==", + "version": "20.5.0", + "resolved": "https://registry.npmjs.org/@commitlint/config-validator/-/config-validator-20.5.0.tgz", + "integrity": "sha512-T/Uh6iJUzyx7j35GmHWdIiGRQB+ouZDk0pwAaYq4SXgB54KZhFdJ0vYmxiW6AMYICTIWuyMxDBl1jK74oFp/Gw==", "dev": true, "license": "MIT", "dependencies": { - "@commitlint/types": "^20.4.3", + "@commitlint/types": "^20.5.0", "ajv": "^8.11.0" }, "engines": { @@ -728,13 +728,13 @@ "license": "MIT" }, "node_modules/@commitlint/ensure": { - "version": "20.4.3", - "resolved": "https://registry.npmjs.org/@commitlint/ensure/-/ensure-20.4.3.tgz", - "integrity": "sha512-WcXGKBNn0wBKpX8VlXgxqedyrLxedIlLBCMvdamLnJFEbUGJ9JZmBVx4vhLV3ZyA8uONGOb+CzW0Y9HDbQ+ONQ==", + "version": "20.5.0", + "resolved": "https://registry.npmjs.org/@commitlint/ensure/-/ensure-20.5.0.tgz", + "integrity": "sha512-IpHqAUesBeW1EDDdjzJeaOxU9tnogLAyXLRBn03SHlj1SGENn2JGZqSWGkFvBJkJzfXAuCNtsoYzax+ZPS+puw==", "dev": true, "license": "MIT", "dependencies": { - "@commitlint/types": "^20.4.3", + "@commitlint/types": "^20.5.0", "lodash.camelcase": "^4.3.0", "lodash.kebabcase": "^4.1.1", "lodash.snakecase": "^4.1.1", @@ -756,13 +756,13 @@ } }, "node_modules/@commitlint/format": { - "version": "20.4.3", - "resolved": "https://registry.npmjs.org/@commitlint/format/-/format-20.4.3.tgz", - "integrity": "sha512-UDJVErjLbNghop6j111rsHJYGw6MjCKAi95K0GT2yf4eeiDHy3JDRLWYWEjIaFgO+r+dQSkuqgJ1CdMTtrvHsA==", + "version": "20.5.0", + "resolved": "https://registry.npmjs.org/@commitlint/format/-/format-20.5.0.tgz", + "integrity": "sha512-TI9EwFU/qZWSK7a5qyXMpKPPv3qta7FO4tKW+Wt2al7sgMbLWTsAcDpX1cU8k16TRdsiiet9aOw0zpvRXNJu7Q==", "dev": true, "license": "MIT", "dependencies": { - "@commitlint/types": "^20.4.3", + "@commitlint/types": "^20.5.0", "picocolors": "^1.1.1" }, "engines": { @@ -770,13 +770,13 @@ } }, "node_modules/@commitlint/is-ignored": { - "version": "20.4.3", - "resolved": "https://registry.npmjs.org/@commitlint/is-ignored/-/is-ignored-20.4.3.tgz", - "integrity": "sha512-W5VQKZ7fdJ1X3Tko+h87YZaqRMGN1KvQKXyCM8xFdxzMIf1KCZgN4uLz3osLB1zsFcVS4ZswHY64LI26/9ACag==", + "version": "20.5.0", + "resolved": "https://registry.npmjs.org/@commitlint/is-ignored/-/is-ignored-20.5.0.tgz", + "integrity": "sha512-JWLarAsurHJhPozbuAH6GbP4p/hdOCoqS9zJMfqwswne+/GPs5V0+rrsfOkP68Y8PSLphwtFXV0EzJ+GTXTTGg==", "dev": true, "license": "MIT", "dependencies": { - "@commitlint/types": "^20.4.3", + "@commitlint/types": "^20.5.0", "semver": "^7.6.0" }, "engines": { @@ -797,32 +797,32 @@ } }, "node_modules/@commitlint/lint": { - "version": "20.4.3", - "resolved": "https://registry.npmjs.org/@commitlint/lint/-/lint-20.4.3.tgz", - "integrity": "sha512-CYOXL23e+nRKij81+d0+dymtIi7Owl9QzvblJYbEfInON/4MaETNSLFDI74LDu+YJ0ML5HZyw9Vhp9QpckwQ0A==", + "version": "20.5.0", + "resolved": "https://registry.npmjs.org/@commitlint/lint/-/lint-20.5.0.tgz", + "integrity": "sha512-jiM3hNUdu04jFBf1VgPdjtIPvbuVfDTBAc6L98AWcoLjF5sYqkulBHBzlVWll4rMF1T5zeQFB6r//a+s+BBKlA==", "dev": true, "license": "MIT", "dependencies": { - "@commitlint/is-ignored": "^20.4.3", - "@commitlint/parse": "^20.4.3", - "@commitlint/rules": "^20.4.3", - "@commitlint/types": "^20.4.3" + "@commitlint/is-ignored": "^20.5.0", + "@commitlint/parse": "^20.5.0", + "@commitlint/rules": "^20.5.0", + "@commitlint/types": "^20.5.0" }, "engines": { "node": ">=v18" } }, "node_modules/@commitlint/load": { - "version": "20.4.3", - "resolved": "https://registry.npmjs.org/@commitlint/load/-/load-20.4.3.tgz", - "integrity": "sha512-3cdJOUVP+VcgHa7bhJoWS+Z8mBNXB5aLWMBu7Q7uX8PSeWDzdbrBlR33J1MGGf7r1PZDp+mPPiFktk031PgdRw==", + "version": "20.5.0", + "resolved": "https://registry.npmjs.org/@commitlint/load/-/load-20.5.0.tgz", + "integrity": "sha512-sLhhYTL/KxeOTZjjabKDhwidGZan84XKK1+XFkwDYL/4883kIajcz/dZFAhBJmZPtL8+nBx6bnkzA95YxPeDPw==", "dev": true, "license": "MIT", "dependencies": { - "@commitlint/config-validator": "^20.4.3", + "@commitlint/config-validator": "^20.5.0", "@commitlint/execute-rule": "^20.0.0", - "@commitlint/resolve-extends": "^20.4.3", - "@commitlint/types": "^20.4.3", + "@commitlint/resolve-extends": "^20.5.0", + "@commitlint/types": "^20.5.0", "cosmiconfig": "^9.0.1", "cosmiconfig-typescript-loader": "^6.1.0", "is-plain-obj": "^4.1.0", @@ -844,13 +844,13 @@ } }, "node_modules/@commitlint/parse": { - "version": "20.4.3", - "resolved": "https://registry.npmjs.org/@commitlint/parse/-/parse-20.4.3.tgz", - "integrity": "sha512-hzC3JCo3zs3VkQ833KnGVuWjWIzR72BWZWjQM7tY/7dfKreKAm7fEsy71tIFCRtxf2RtMP2d3RLF1U9yhFSccA==", + "version": "20.5.0", + "resolved": "https://registry.npmjs.org/@commitlint/parse/-/parse-20.5.0.tgz", + "integrity": "sha512-SeKWHBMk7YOTnnEWUhx+d1a9vHsjjuo6Uo1xRfPNfeY4bdYFasCH1dDpAv13Lyn+dDPOels+jP6D2GRZqzc5fA==", "dev": true, "license": "MIT", "dependencies": { - "@commitlint/types": "^20.4.3", + "@commitlint/types": "^20.5.0", "conventional-changelog-angular": "^8.2.0", "conventional-commits-parser": "^6.3.0" }, @@ -859,15 +859,15 @@ } }, "node_modules/@commitlint/read": { - "version": "20.4.3", - "resolved": "https://registry.npmjs.org/@commitlint/read/-/read-20.4.3.tgz", - "integrity": "sha512-j42OWv3L31WfnP8WquVjHZRt03w50Y/gEE8FAyih7GQTrIv2+pZ6VZ6pWLD/ml/3PO+RV2SPtRtTp/MvlTb8rQ==", + "version": "20.5.0", + "resolved": "https://registry.npmjs.org/@commitlint/read/-/read-20.5.0.tgz", + "integrity": "sha512-JDEIJ2+GnWpK8QqwfmW7O42h0aycJEWNqcdkJnyzLD11nf9dW2dWLTVEa8Wtlo4IZFGLPATjR5neA5QlOvIH1w==", "dev": true, "license": "MIT", "dependencies": { "@commitlint/top-level": "^20.4.3", - "@commitlint/types": "^20.4.3", - "git-raw-commits": "^4.0.0", + "@commitlint/types": "^20.5.0", + "git-raw-commits": "^5.0.0", "minimist": "^1.2.8", "tinyexec": "^1.0.0" }, @@ -876,14 +876,14 @@ } }, "node_modules/@commitlint/resolve-extends": { - "version": "20.4.3", - "resolved": "https://registry.npmjs.org/@commitlint/resolve-extends/-/resolve-extends-20.4.3.tgz", - "integrity": "sha512-QucxcOy+00FhS9s4Uy0OyS5HeUV+hbC6OLqkTSIm6fwMdKva+OEavaCDuLtgd9akZZlsUo//XzSmPP3sLKBPog==", + "version": "20.5.0", + "resolved": "https://registry.npmjs.org/@commitlint/resolve-extends/-/resolve-extends-20.5.0.tgz", + "integrity": "sha512-3SHPWUW2v0tyspCTcfSsYml0gses92l6TlogwzvM2cbxDgmhSRc+fldDjvGkCXJrjSM87BBaWYTPWwwyASZRrg==", "dev": true, "license": "MIT", "dependencies": { - "@commitlint/config-validator": "^20.4.3", - "@commitlint/types": "^20.4.3", + "@commitlint/config-validator": "^20.5.0", + "@commitlint/types": "^20.5.0", "global-directory": "^4.0.1", "import-meta-resolve": "^4.0.0", "lodash.mergewith": "^4.6.2", @@ -894,16 +894,16 @@ } }, "node_modules/@commitlint/rules": { - "version": "20.4.3", - "resolved": "https://registry.npmjs.org/@commitlint/rules/-/rules-20.4.3.tgz", - "integrity": "sha512-Yuosd7Grn5qiT7FovngXLyRXTMUbj9PYiSkvUgWK1B5a7+ZvrbWDS7epeUapYNYatCy/KTpPFPbgLUdE+MUrBg==", + "version": "20.5.0", + "resolved": "https://registry.npmjs.org/@commitlint/rules/-/rules-20.5.0.tgz", + "integrity": "sha512-5NdQXQEdnDPT5pK8O39ZA7HohzPRHEsDGU23cyVCNPQy4WegAbAwrQk3nIu7p2sl3dutPk8RZd91yKTrMTnRkQ==", "dev": true, "license": "MIT", "dependencies": { - "@commitlint/ensure": "^20.4.3", + "@commitlint/ensure": "^20.5.0", "@commitlint/message": "^20.4.3", "@commitlint/to-lines": "^20.0.0", - "@commitlint/types": "^20.4.3" + "@commitlint/types": "^20.5.0" }, "engines": { "node": ">=v18" @@ -933,9 +933,9 @@ } }, "node_modules/@commitlint/types": { - "version": "20.4.3", - "resolved": "https://registry.npmjs.org/@commitlint/types/-/types-20.4.3.tgz", - "integrity": "sha512-51OWa1Gi6ODOasPmfJPq6js4pZoomima4XLZZCrkldaH2V5Nb3bVhNXPeT6XV0gubbainSpTw4zi68NqAeCNCg==", + "version": "20.5.0", + "resolved": "https://registry.npmjs.org/@commitlint/types/-/types-20.5.0.tgz", + "integrity": "sha512-ZJoS8oSq2CAZEpc/YI9SulLrdiIyXeHb/OGqGrkUP6Q7YV+0ouNAa7GjqRdXeQPncHQIDz/jbCTlHScvYvO/gA==", "dev": true, "license": "MIT", "dependencies": { @@ -947,14 +947,14 @@ } }, "node_modules/@conventional-changelog/git-client": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@conventional-changelog/git-client/-/git-client-2.5.1.tgz", - "integrity": "sha512-lAw7iA5oTPWOLjiweb7DlGEMDEvzqzLLa6aWOly2FSZ64IwLE8T458rC+o+WvI31Doz6joM7X2DoNog7mX8r4A==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/@conventional-changelog/git-client/-/git-client-2.7.0.tgz", + "integrity": "sha512-j7A8/LBEQ+3rugMzPXoKYzyUPpw/0CBQCyvtTR7Lmu4olG4yRC/Tfkq79Mr3yuPs0SUitlO2HwGP3gitMJnRFw==", "dev": true, "license": "MIT", "dependencies": { "@simple-libs/child-process-utils": "^1.0.0", - "@simple-libs/stream-utils": "^1.1.0", + "@simple-libs/stream-utils": "^1.2.0", "semver": "^7.5.2" }, "engines": { @@ -962,7 +962,7 @@ }, "peerDependencies": { "conventional-commits-filter": "^5.0.0", - "conventional-commits-parser": "^6.1.0" + "conventional-commits-parser": "^6.4.0" }, "peerDependenciesMeta": { "conventional-commits-filter": { @@ -6188,9 +6188,9 @@ } }, "node_modules/conventional-commits-parser": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-6.3.0.tgz", - "integrity": "sha512-RfOq/Cqy9xV9bOA8N+ZH6DlrDR+5S3Mi0B5kACEjESpE+AviIpAptx9a9cFpWCCvgRtWT+0BbUw+e1BZfts9jg==", + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-6.4.0.tgz", + "integrity": "sha512-tvRg7FIBNlyPzjdG8wWRlPHQJJHI7DylhtRGeU9Lq+JuoPh5BKpPRX83ZdLrvXuOSu5Eo/e7SzOQhU4Hd2Miuw==", "dev": true, "license": "MIT", "dependencies": { @@ -6293,13 +6293,13 @@ } }, "node_modules/cosmiconfig-typescript-loader": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/cosmiconfig-typescript-loader/-/cosmiconfig-typescript-loader-6.2.0.tgz", - "integrity": "sha512-GEN39v7TgdxgIoNcdkRE3uiAzQt3UXLyHbRHD6YoL048XAeOomyxaP+Hh/+2C6C2wYjxJ2onhJcsQp+L4YEkVQ==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/cosmiconfig-typescript-loader/-/cosmiconfig-typescript-loader-6.3.0.tgz", + "integrity": "sha512-Akr82WH1Wfqatyiqpj8HDkO2o2KmJRu1FhKfSNJP3K4IdXwHfEyL7MOb62i1AGQVLtIQM+iCE9CGOtrfhR+mmA==", "dev": true, "license": "MIT", "dependencies": { - "jiti": "^2.6.1" + "jiti": "2.6.1" }, "engines": { "node": ">=v18" @@ -6378,19 +6378,6 @@ "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", "license": "MIT" }, - "node_modules/dargs": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/dargs/-/dargs-8.1.0.tgz", - "integrity": "sha512-wAV9QHOsNbwnWdNW2FYvE1P56wtgSbM+3SZcdGiWQILwVjACCXDCI3Ai8QlCjMDB8YK5zySiXZYBiwGmNY3lnw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/data-uri-to-buffer": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", @@ -8523,35 +8510,20 @@ } }, "node_modules/git-raw-commits": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/git-raw-commits/-/git-raw-commits-4.0.0.tgz", - "integrity": "sha512-ICsMM1Wk8xSGMowkOmPrzo2Fgmfo4bMHLNX6ytHjajRJUqvHOw/TFapQ+QG75c3X/tTDDhOSRPGC52dDbNM8FQ==", - "deprecated": "This package is no longer maintained. For the JavaScript API, please use @conventional-changelog/git-client instead.", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/git-raw-commits/-/git-raw-commits-5.0.1.tgz", + "integrity": "sha512-Y+csSm2GD/PCSh6Isd/WiMjNAydu0VBiG9J7EdQsNA5P9uXvLayqjmTsNlK5Gs9IhblFZqOU0yid5Il5JPoLiQ==", "dev": true, "license": "MIT", "dependencies": { - "dargs": "^8.0.0", - "meow": "^12.0.1", - "split2": "^4.0.0" + "@conventional-changelog/git-client": "^2.6.0", + "meow": "^13.0.0" }, "bin": { - "git-raw-commits": "cli.mjs" + "git-raw-commits": "src/cli.js" }, "engines": { - "node": ">=16" - } - }, - "node_modules/git-raw-commits/node_modules/meow": { - "version": "12.1.1", - "resolved": "https://registry.npmjs.org/meow/-/meow-12.1.1.tgz", - "integrity": "sha512-BhXM0Au22RwUneMPwSCnyhTOizdWoIEPU9sp0Aqa1PnDMR5Wv2FGXYDjuzJEIX+Eo2Rb8xuYe5jrnm5QowQFkw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=16.10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=18" } }, "node_modules/git-up": { @@ -15582,16 +15554,6 @@ "dev": true, "license": "CC0-1.0" }, - "node_modules/split2": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", - "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">= 10.x" - } - }, "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", diff --git a/package.json b/package.json index 553f528a..0f504a03 100644 --- a/package.json +++ b/package.json @@ -124,8 +124,8 @@ "ws": "^8.14.2" }, "devDependencies": { - "@commitlint/cli": "^20.4.3", - "@commitlint/config-conventional": "^20.4.3", + "@commitlint/cli": "^20.5.0", + "@commitlint/config-conventional": "^20.5.0", "@eslint/js": "^9.39.3", "@release-it/conventional-changelog": "^10.0.5", "@types/node": "^22.19.7", From 289520814cf3ca36403056739ef22021f78c6033 Mon Sep 17 00:00:00 2001 From: simosmik Date: Thu, 16 Apr 2026 08:37:59 +0000 Subject: [PATCH 05/10] refactor: remove the sqlite3 dependency --- package-lock.json | 831 ++--------------------------- package.json | 4 +- server/projects.js | 19 +- server/providers/cursor/adapter.js | 15 +- server/routes/cursor.js | 28 +- 5 files changed, 53 insertions(+), 844 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6fda6017..2bb0c748 100644 --- a/package-lock.json +++ b/package-lock.json @@ -62,8 +62,6 @@ "rehype-raw": "^7.0.0", "remark-gfm": "^4.0.0", "remark-math": "^6.0.0", - "sqlite": "^5.1.1", - "sqlite3": "^5.1.7", "tailwind-merge": "^3.3.1", "web-push": "^3.6.7", "ws": "^8.14.2" @@ -1700,13 +1698,6 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "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/@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", @@ -2871,34 +2862,6 @@ "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/@octokit/auth-token": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-6.0.0.tgz", @@ -3679,16 +3642,6 @@ "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/@tootallnate/quickjs-emscripten": { "version": "0.23.0", "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", @@ -4634,24 +4587,11 @@ "node": ">= 14" } }, - "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==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "clean-stack": "^2.0.0", @@ -4746,28 +4686,6 @@ "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", @@ -5710,7 +5628,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -5934,16 +5852,6 @@ "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/colorette": { "version": "2.0.20", "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", @@ -5995,7 +5903,7 @@ "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/concat-stream": { @@ -6058,13 +5966,6 @@ "node": "^14.18.0 || >=16.10.0" } }, - "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", @@ -6621,13 +6522,6 @@ "node": ">= 14" } }, - "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", @@ -6854,7 +6748,7 @@ "version": "2.2.1", "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -6877,7 +6771,7 @@ "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==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/error-ex": { @@ -8183,6 +8077,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dev": true, "license": "ISC", "dependencies": { "minipass": "^3.0.0" @@ -8195,6 +8090,7 @@ "version": "3.3.6", "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, "license": "ISC", "dependencies": { "yallist": "^4.0.0" @@ -8207,15 +8103,9 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, "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", @@ -8279,79 +8169,6 @@ "node": ">=10" } }, - "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/generator-function": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", @@ -8678,7 +8495,7 @@ "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==", - "devOptional": true, + "dev": true, "license": "ISC" }, "node_modules/gray-matter": { @@ -8798,13 +8615,6 @@ "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", @@ -9123,7 +8933,7 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", - "devOptional": true, + "dev": true, "license": "BSD-2-Clause" }, "node_modules/http-errors": { @@ -9179,16 +8989,6 @@ "node": ">=16.17.0" } }, - "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/husky": { "version": "9.1.7", "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz", @@ -9361,7 +9161,7 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=0.8.19" @@ -9371,31 +9171,12 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "devOptional": true, + "dev": true, "license": "MIT", "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", @@ -9460,7 +9241,7 @@ "version": "10.0.1", "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz", "integrity": "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">= 12" @@ -9822,7 +9603,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/is-map": { @@ -12014,7 +11795,7 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", - "devOptional": true, + "dev": true, "license": "ISC", "dependencies": { "minipass": "^3.0.0" @@ -12027,7 +11808,7 @@ "version": "3.3.6", "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "devOptional": true, + "dev": true, "license": "ISC", "dependencies": { "yallist": "^4.0.0" @@ -12040,14 +11821,14 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "devOptional": true, + "dev": true, "license": "ISC" }, "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==", - "devOptional": true, + "dev": true, "license": "ISC", "dependencies": { "minipass": "^3.0.0" @@ -12060,7 +11841,7 @@ "version": "3.3.6", "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "devOptional": true, + "dev": true, "license": "ISC", "dependencies": { "yallist": "^4.0.0" @@ -12073,14 +11854,14 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "devOptional": true, + "dev": true, "license": "ISC" }, "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==", - "devOptional": true, + "dev": true, "license": "ISC", "dependencies": { "minipass": "^3.0.0" @@ -12093,7 +11874,7 @@ "version": "3.3.6", "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "devOptional": true, + "dev": true, "license": "ISC", "dependencies": { "yallist": "^4.0.0" @@ -12106,13 +11887,14 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "devOptional": true, + "dev": true, "license": "ISC" }, "node_modules/minizlib": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dev": true, "license": "MIT", "dependencies": { "minipass": "^3.0.0", @@ -12126,6 +11908,7 @@ "version": "3.3.6", "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, "license": "ISC", "dependencies": { "yallist": "^4.0.0" @@ -12138,6 +11921,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, "license": "ISC" }, "node_modules/mkdirp": { @@ -12575,23 +12359,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "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/nypm": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/nypm/-/nypm-0.6.2.tgz", @@ -12933,7 +12700,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "aggregate-error": "^3.0.0" @@ -13116,16 +12883,6 @@ "node": ">=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", @@ -13481,18 +13238,11 @@ "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", "license": "MIT" }, - "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==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "err-code": "^2.0.2", @@ -14397,7 +14147,7 @@ "version": "0.12.0", "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">= 4" @@ -14420,69 +14170,6 @@ "dev": true, "license": "MIT" }, - "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", @@ -14769,13 +14456,6 @@ "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/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", @@ -15446,7 +15126,7 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">= 6.0.0", @@ -15457,7 +15137,7 @@ "version": "2.8.7", "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz", "integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "ip-address": "^10.0.1", @@ -15560,395 +15240,6 @@ "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", "license": "BSD-3-Clause" }, - "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/@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/sqlite3/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/sqlite3/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/sqlite3/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/sqlite3/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/sqlite3/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/sqlite3/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/sqlite3/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/sqlite3/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/sqlite3/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/sqlite3/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/sqlite3/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/sqlite3/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/sqlite3/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/sqlite3/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/sqlite3/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/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/sqlite3/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/sqlite3/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/sqlite3/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/sqlite3/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/sqlite3/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/sqlite3/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/sqlite3/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/sqlite3/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/ssri": { "version": "10.0.6", "resolved": "https://registry.npmjs.org/ssri/-/ssri-10.0.6.tgz", @@ -16460,6 +15751,7 @@ "version": "6.2.1", "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "dev": true, "license": "ISC", "dependencies": { "chownr": "^2.0.0", @@ -16505,6 +15797,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "dev": true, "license": "ISC", "engines": { "node": ">=10" @@ -16514,6 +15807,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "dev": true, "license": "ISC", "engines": { "node": ">=8" @@ -16523,6 +15817,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, "license": "MIT", "bin": { "mkdirp": "bin/cmd.js" @@ -16535,6 +15830,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, "license": "ISC" }, "node_modules/thenify": { @@ -18165,61 +17461,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "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/wildcard-match": { "version": "5.1.4", "resolved": "https://registry.npmjs.org/wildcard-match/-/wildcard-match-5.1.4.tgz", diff --git a/package.json b/package.json index 0f504a03..457d132b 100644 --- a/package.json +++ b/package.json @@ -117,9 +117,7 @@ "rehype-raw": "^7.0.0", "remark-gfm": "^4.0.0", "remark-math": "^6.0.0", - "sqlite": "^5.1.1", - "sqlite3": "^5.1.7", - "tailwind-merge": "^3.3.1", +"tailwind-merge": "^3.3.1", "web-push": "^3.6.7", "ws": "^8.14.2" }, diff --git a/server/projects.js b/server/projects.js index d8ccaeb7..c7000890 100755 --- a/server/projects.js +++ b/server/projects.js @@ -62,8 +62,7 @@ import fsSync from 'fs'; import path from 'path'; import readline from 'readline'; import crypto from 'crypto'; -import sqlite3 from 'sqlite3'; -import { open } from 'sqlite'; +import Database from 'better-sqlite3'; import os from 'os'; import sessionManager from './sessionManager.js'; import { applyCustomSessionNames } from './database/db.js'; @@ -1305,16 +1304,10 @@ async function getCursorSessions(projectPath) { } catch (_) { } // Open SQLite database - const db = await open({ - filename: storeDbPath, - driver: sqlite3.Database, - mode: sqlite3.OPEN_READONLY - }); + const db = new Database(storeDbPath, { readonly: true, fileMustExist: true }); // Get metadata from meta table - const metaRows = await db.all(` - SELECT key, value FROM meta - `); + const metaRows = db.prepare('SELECT key, value FROM meta').all(); // Parse metadata let metadata = {}; @@ -1336,11 +1329,9 @@ async function getCursorSessions(projectPath) { } // Get message count - const messageCountResult = await db.get(` - SELECT COUNT(*) as count FROM blobs - `); + const messageCountResult = db.prepare('SELECT COUNT(*) as count FROM blobs').get(); - await db.close(); + db.close(); // Extract session info const sessionName = metadata.title || metadata.sessionTitle || 'Untitled Session'; diff --git a/server/providers/cursor/adapter.js b/server/providers/cursor/adapter.js index c86215ff..ef94ea1d 100644 --- a/server/providers/cursor/adapter.js +++ b/server/providers/cursor/adapter.js @@ -20,21 +20,16 @@ const PROVIDER = 'cursor'; * @returns {Promise>} */ async function loadCursorBlobs(sessionId, projectPath) { - // Lazy-import sqlite so the module doesn't fail if sqlite3 is unavailable - const { default: sqlite3 } = await import('sqlite3'); - const { open } = await import('sqlite'); + // Lazy-import better-sqlite3 so the module doesn't fail if it's unavailable + const { default: Database } = await import('better-sqlite3'); const cwdId = crypto.createHash('md5').update(projectPath || process.cwd()).digest('hex'); const storeDbPath = path.join(os.homedir(), '.cursor', 'chats', cwdId, sessionId, 'store.db'); - const db = await open({ - filename: storeDbPath, - driver: sqlite3.Database, - mode: sqlite3.OPEN_READONLY, - }); + const db = new Database(storeDbPath, { readonly: true, fileMustExist: true }); try { - const allBlobs = await db.all('SELECT rowid, id, data FROM blobs'); + const allBlobs = db.prepare('SELECT rowid, id, data FROM blobs').all(); const blobMap = new Map(); const parentRefs = new Map(); @@ -129,7 +124,7 @@ async function loadCursorBlobs(sessionId, projectPath) { return messages; } finally { - await db.close(); + db.close(); } } diff --git a/server/routes/cursor.js b/server/routes/cursor.js index 7e395023..3fc0270a 100644 --- a/server/routes/cursor.js +++ b/server/routes/cursor.js @@ -2,8 +2,7 @@ import express from 'express'; import { promises as fs } from 'fs'; import path from 'path'; import os from 'os'; -import sqlite3 from 'sqlite3'; -import { open } from 'sqlite'; +import Database from 'better-sqlite3'; import crypto from 'crypto'; import { CURSOR_MODELS } from '../../shared/modelConstants.js'; import { applyCustomSessionNames } from '../database/db.js'; @@ -386,16 +385,10 @@ router.get('/sessions', async (req, res) => { } catch (_) {} // Open SQLite database - const db = await open({ - filename: storeDbPath, - driver: sqlite3.Database, - mode: sqlite3.OPEN_READONLY - }); + const db = new Database(storeDbPath, { readonly: true, fileMustExist: true }); // Get metadata from meta table - const metaRows = await db.all(` - SELECT key, value FROM meta - `); + const metaRows = db.prepare('SELECT key, value FROM meta').all(); let sessionData = { id: sessionId, @@ -457,20 +450,11 @@ router.get('/sessions', async (req, res) => { // Get message count from JSON blobs only (actual messages, not DAG structure) try { - const blobCount = await db.get(` - SELECT COUNT(*) as count - FROM blobs - WHERE substr(data, 1, 1) = X'7B' - `); + const blobCount = db.prepare(`SELECT COUNT(*) as count FROM blobs WHERE substr(data, 1, 1) = X'7B'`).get(); sessionData.messageCount = blobCount.count; // Get the most recent JSON blob for preview (actual message, not DAG structure) - const lastBlob = await db.get(` - SELECT data FROM blobs - WHERE substr(data, 1, 1) = X'7B' - ORDER BY rowid DESC - LIMIT 1 - `); + const lastBlob = db.prepare(`SELECT data FROM blobs WHERE substr(data, 1, 1) = X'7B' ORDER BY rowid DESC LIMIT 1`).get(); if (lastBlob && lastBlob.data) { try { @@ -525,7 +509,7 @@ router.get('/sessions', async (req, res) => { console.log('Could not read blobs:', e.message); } - await db.close(); + db.close(); // Finalize createdAt: use parsed meta value when valid, else fall back to store.db mtime if (!sessionData.createdAt) { From e9c7a5041c31a6f7b2032f06abe19c52d3d4cd8c Mon Sep 17 00:00:00 2001 From: simosmik Date: Thu, 16 Apr 2026 09:05:56 +0000 Subject: [PATCH 06/10] feat: deleting from sidebar will now ask whether to remove all data as well --- server/index.js | 7 ++- server/projects.js | 63 ++++++++++--------- .../sidebar/hooks/useSidebarController.ts | 4 +- .../view/subcomponents/SidebarModals.tsx | 42 ++++++------- src/i18n/locales/de/sidebar.json | 20 +++--- src/i18n/locales/en/sidebar.json | 20 +++--- src/i18n/locales/ja/sidebar.json | 20 +++--- src/i18n/locales/ko/sidebar.json | 20 +++--- src/i18n/locales/ru/sidebar.json | 20 +++--- src/i18n/locales/zh-CN/sidebar.json | 20 +++--- src/utils/api.js | 11 +++- 11 files changed, 135 insertions(+), 112 deletions(-) diff --git a/server/index.js b/server/index.js index eee06d9c..86ded977 100755 --- a/server/index.js +++ b/server/index.js @@ -494,12 +494,15 @@ app.put('/api/sessions/:sessionId/rename', authenticateToken, async (req, res) = } }); -// Delete project endpoint (force=true to delete with sessions) +// Delete project endpoint +// force=true to allow removal even when sessions exist +// deleteData=true to also delete session/memory files on disk (destructive) app.delete('/api/projects/:projectName', authenticateToken, async (req, res) => { try { const { projectName } = req.params; const force = req.query.force === 'true'; - await deleteProject(projectName, force); + const deleteData = req.query.deleteData === 'true'; + await deleteProject(projectName, force, deleteData); res.json({ success: true }); } catch (error) { res.status(500).json({ error: error.message }); diff --git a/server/projects.js b/server/projects.js index c7000890..67184af0 100755 --- a/server/projects.js +++ b/server/projects.js @@ -1163,8 +1163,9 @@ async function isProjectEmpty(projectName) { } } -// Delete a project (force=true to delete even with sessions) -async function deleteProject(projectName, force = false) { +// Remove a project from the UI. +// When deleteData=true, also delete session/memory files on disk (destructive). +async function deleteProject(projectName, force = false, deleteData = false) { const projectDir = path.join(os.homedir(), '.claude', 'projects', projectName); try { @@ -1174,48 +1175,50 @@ async function deleteProject(projectName, force = false) { } const config = await loadProjectConfig(); - let projectPath = config[projectName]?.path || config[projectName]?.originalPath; - // Fallback to extractProjectDirectory if projectPath is not in config - if (!projectPath) { - projectPath = await extractProjectDirectory(projectName); - } + // Destructive path: delete underlying data when explicitly requested + if (deleteData) { + let projectPath = config[projectName]?.path || config[projectName]?.originalPath; + if (!projectPath) { + projectPath = await extractProjectDirectory(projectName); + } - // Remove the project directory (includes all Claude sessions) - await fs.rm(projectDir, { recursive: true, force: true }); + // Remove the Claude project directory (session logs, memory, subagent data) + await fs.rm(projectDir, { recursive: true, force: true }); - // Delete all Codex sessions associated with this project - if (projectPath) { - try { - const codexSessions = await getCodexSessions(projectPath, { limit: 0 }); - for (const session of codexSessions) { - try { - await deleteCodexSession(session.id); - } catch (err) { - console.warn(`Failed to delete Codex session ${session.id}:`, err.message); + // Delete Codex sessions associated with this project + if (projectPath) { + try { + const codexSessions = await getCodexSessions(projectPath, { limit: 0 }); + for (const session of codexSessions) { + try { + await deleteCodexSession(session.id); + } catch (err) { + console.warn(`Failed to delete Codex session ${session.id}:`, err.message); + } } + } catch (err) { + console.warn('Failed to delete Codex sessions:', err.message); } - } catch (err) { - console.warn('Failed to delete Codex sessions:', err.message); - } - // Delete Cursor sessions directory if it exists - try { - const hash = crypto.createHash('md5').update(projectPath).digest('hex'); - const cursorProjectDir = path.join(os.homedir(), '.cursor', 'chats', hash); - await fs.rm(cursorProjectDir, { recursive: true, force: true }); - } catch (err) { - // Cursor dir may not exist, ignore + // Delete Cursor sessions directory if it exists + try { + const hash = crypto.createHash('md5').update(projectPath).digest('hex'); + const cursorProjectDir = path.join(os.homedir(), '.cursor', 'chats', hash); + await fs.rm(cursorProjectDir, { recursive: true, force: true }); + } catch (err) { + // Cursor dir may not exist, ignore + } } } - // Remove from project config + // Always remove from project config delete config[projectName]; await saveProjectConfig(config); return true; } catch (error) { - console.error(`Error deleting project ${projectName}:`, error); + console.error(`Error removing project ${projectName}:`, error); throw error; } } diff --git a/src/components/sidebar/hooks/useSidebarController.ts b/src/components/sidebar/hooks/useSidebarController.ts index 37570a55..b21f13ad 100644 --- a/src/components/sidebar/hooks/useSidebarController.ts +++ b/src/components/sidebar/hooks/useSidebarController.ts @@ -452,7 +452,7 @@ export function useSidebarController({ [getProjectSessions], ); - const confirmDeleteProject = useCallback(async () => { + const confirmDeleteProject = useCallback(async (deleteData = false) => { if (!deleteConfirmation) { return; } @@ -464,7 +464,7 @@ export function useSidebarController({ setDeletingProjects((prev) => new Set([...prev, project.name])); try { - const response = await api.deleteProject(project.name, !isEmpty); + const response = await api.deleteProject(project.name, !isEmpty, deleteData); if (response.ok) { onProjectDelete?.(project.name); diff --git a/src/components/sidebar/view/subcomponents/SidebarModals.tsx b/src/components/sidebar/view/subcomponents/SidebarModals.tsx index 3f6cd4f8..6d6d69d3 100644 --- a/src/components/sidebar/view/subcomponents/SidebarModals.tsx +++ b/src/components/sidebar/view/subcomponents/SidebarModals.tsx @@ -1,6 +1,6 @@ import { useMemo } from 'react'; import ReactDOM from 'react-dom'; -import { AlertTriangle, Trash2 } from 'lucide-react'; +import { AlertTriangle, EyeOff, Trash2 } from 'lucide-react'; import type { TFunction } from 'i18next'; import { Button } from '../../../../shared/view/ui'; import Settings from '../../../settings/view/Settings'; @@ -22,7 +22,7 @@ type SidebarModalsProps = { onProjectCreated: () => void; deleteConfirmation: DeleteProjectConfirmation | null; onCancelDeleteProject: () => void; - onConfirmDeleteProject: () => void; + onConfirmDeleteProject: (deleteData?: boolean) => void; sessionDeleteConfirmation: SessionDeleteConfirmation | null; onCancelDeleteSession: () => void; onConfirmDeleteSession: () => void; @@ -104,8 +104,8 @@ export default function SidebarModals({
-
- +
+

@@ -119,32 +119,32 @@ export default function SidebarModals({ ?

{deleteConfirmation.sessionCount > 0 && ( -
-

- {t('deleteConfirmation.sessionCount', { count: deleteConfirmation.sessionCount })} -

-

- {t('deleteConfirmation.allConversationsDeleted')} -

-
+

+ {t('deleteConfirmation.sessionCount', { count: deleteConfirmation.sessionCount })} +

)} -

- {t('deleteConfirmation.cannotUndo')} -

-
- +
diff --git a/src/i18n/locales/de/sidebar.json b/src/i18n/locales/de/sidebar.json index 8b26756c..b2df5190 100644 --- a/src/i18n/locales/de/sidebar.json +++ b/src/i18n/locales/de/sidebar.json @@ -2,7 +2,7 @@ "projects": { "title": "Projekte", "newProject": "Neues Projekt", - "deleteProject": "Projekt löschen", + "deleteProject": "Projekt entfernen", "renameProject": "Projekt umbenennen", "noProjects": "Keine Projekte gefunden", "loadingProjects": "Projekte werden geladen...", @@ -40,7 +40,7 @@ "createProject": "Neues Projekt erstellen", "refresh": "Projekte und Sitzungen aktualisieren (Strg+R)", "renameProject": "Projekt umbenennen (F2)", - "deleteProject": "Leeres Projekt löschen (Entf)", + "deleteProject": "Projekt aus Seitenleiste entfernen (Entf)", "addToFavorites": "Zu Favoriten hinzufügen", "removeFromFavorites": "Aus Favoriten entfernen", "editSessionName": "Sitzungsname manuell bearbeiten", @@ -95,14 +95,14 @@ "deleteSuccess": "Erfolgreich gelöscht", "errorOccurred": "Ein Fehler ist aufgetreten", "deleteSessionConfirm": "Möchtest du diese Sitzung wirklich löschen? Diese Aktion kann nicht rückgängig gemacht werden.", - "deleteProjectConfirm": "Möchtest du dieses leere Projekt wirklich löschen? Diese Aktion kann nicht rückgängig gemacht werden.", + "deleteProjectConfirm": "Projekt aus der Seitenleiste entfernen? Deine Projektdateien, Erinnerungen und Sitzungsdaten werden nicht gelöscht.", "enterProjectPath": "Bitte gib einen Projektpfad ein", "deleteSessionFailed": "Sitzung konnte nicht gelöscht werden. Bitte erneut versuchen.", "deleteSessionError": "Fehler beim Löschen der Sitzung. Bitte erneut versuchen.", "renameSessionFailed": "Sitzung konnte nicht umbenannt werden. Bitte erneut versuchen.", "renameSessionError": "Fehler beim Umbenennen der Sitzung. Bitte erneut versuchen.", - "deleteProjectFailed": "Projekt konnte nicht gelöscht werden. Bitte erneut versuchen.", - "deleteProjectError": "Fehler beim Löschen des Projekts. Bitte erneut versuchen.", + "deleteProjectFailed": "Projekt konnte nicht entfernt werden. Bitte erneut versuchen.", + "deleteProjectError": "Fehler beim Entfernen des Projekts. Bitte erneut versuchen.", "createProjectFailed": "Projekt konnte nicht erstellt werden. Bitte erneut versuchen.", "createProjectError": "Fehler beim Erstellen des Projekts. Bitte erneut versuchen." }, @@ -122,12 +122,14 @@ "projectsScanned_other": "{{count}} Projekte durchsucht" }, "deleteConfirmation": { - "deleteProject": "Projekt löschen", + "deleteProject": "Projekt entfernen", "deleteSession": "Sitzung löschen", - "confirmDelete": "Möchtest du wirklich löschen", + "confirmDelete": "Was möchtest du mit", "sessionCount_one": "Dieses Projekt enthält {{count}} Unterhaltung.", "sessionCount_other": "Dieses Projekt enthält {{count}} Unterhaltungen.", - "allConversationsDeleted": "Alle Unterhaltungen werden dauerhaft gelöscht.", - "cannotUndo": "Diese Aktion kann nicht rückgängig gemacht werden." + "removeFromSidebar": "Nur aus der Seitenleiste entfernen", + "deleteAllData": "Alle Daten dauerhaft löschen", + "allConversationsDeleted": "Das Projekt wird aus der Seitenleiste entfernt. Deine Dateien, Erinnerungen und Sitzungsdaten bleiben erhalten.", + "cannotUndo": "Du kannst das Projekt später erneut hinzufügen." } } diff --git a/src/i18n/locales/en/sidebar.json b/src/i18n/locales/en/sidebar.json index eab0a3f3..7b3452cb 100644 --- a/src/i18n/locales/en/sidebar.json +++ b/src/i18n/locales/en/sidebar.json @@ -2,7 +2,7 @@ "projects": { "title": "Projects", "newProject": "New Project", - "deleteProject": "Delete Project", + "deleteProject": "Remove Project", "renameProject": "Rename Project", "noProjects": "No projects found", "loadingProjects": "Loading projects...", @@ -40,7 +40,7 @@ "createProject": "Create new project", "refresh": "Refresh projects and sessions (Ctrl+R)", "renameProject": "Rename project (F2)", - "deleteProject": "Delete empty project (Delete)", + "deleteProject": "Remove project from sidebar (Delete)", "addToFavorites": "Add to favorites", "removeFromFavorites": "Remove from favorites", "editSessionName": "Manually edit session name", @@ -95,14 +95,14 @@ "deleteSuccess": "Deleted successfully", "errorOccurred": "An error occurred", "deleteSessionConfirm": "Are you sure you want to delete this session? This action cannot be undone.", - "deleteProjectConfirm": "Are you sure you want to delete this empty project? This action cannot be undone.", + "deleteProjectConfirm": "Remove this project from the sidebar? Your project files, memories, and session data will not be deleted.", "enterProjectPath": "Please enter a project path", "deleteSessionFailed": "Failed to delete session. Please try again.", "deleteSessionError": "Error deleting session. Please try again.", "renameSessionFailed": "Failed to rename session. Please try again.", "renameSessionError": "Error renaming session. Please try again.", - "deleteProjectFailed": "Failed to delete project. Please try again.", - "deleteProjectError": "Error deleting project. Please try again.", + "deleteProjectFailed": "Failed to remove project. Please try again.", + "deleteProjectError": "Error removing project. Please try again.", "createProjectFailed": "Failed to create project. Please try again.", "createProjectError": "Error creating project. Please try again." }, @@ -122,12 +122,14 @@ "projectsScanned_other": "{{count}} projects scanned" }, "deleteConfirmation": { - "deleteProject": "Delete Project", + "deleteProject": "Remove Project", "deleteSession": "Delete Session", - "confirmDelete": "Are you sure you want to delete", + "confirmDelete": "What would you like to do with", "sessionCount_one": "This project contains {{count}} conversation.", "sessionCount_other": "This project contains {{count}} conversations.", - "allConversationsDeleted": "All conversations will be permanently deleted.", - "cannotUndo": "This action cannot be undone." + "removeFromSidebar": "Remove from sidebar only", + "deleteAllData": "Delete all data permanently", + "allConversationsDeleted": "The project will be removed from the sidebar. Your files, memories, and session data will be preserved.", + "cannotUndo": "You can re-add the project later." } } diff --git a/src/i18n/locales/ja/sidebar.json b/src/i18n/locales/ja/sidebar.json index 4590c752..33c17399 100644 --- a/src/i18n/locales/ja/sidebar.json +++ b/src/i18n/locales/ja/sidebar.json @@ -2,7 +2,7 @@ "projects": { "title": "プロジェクト", "newProject": "新規プロジェクト", - "deleteProject": "プロジェクトを削除", + "deleteProject": "プロジェクトを除去", "renameProject": "プロジェクト名を変更", "noProjects": "プロジェクトが見つかりません", "loadingProjects": "プロジェクトを読み込んでいます...", @@ -40,7 +40,7 @@ "createProject": "新しいプロジェクトを作成", "refresh": "プロジェクトとセッションを更新 (Ctrl+R)", "renameProject": "プロジェクト名を変更 (F2)", - "deleteProject": "空のプロジェクトを削除 (Delete)", + "deleteProject": "サイドバーからプロジェクトを除去 (Delete)", "addToFavorites": "お気に入りに追加", "removeFromFavorites": "お気に入りから削除", "editSessionName": "セッション名を手動で編集", @@ -94,14 +94,14 @@ "deleteSuccess": "削除しました", "errorOccurred": "エラーが発生しました", "deleteSessionConfirm": "このセッションを削除してもよろしいですか?この操作は取り消せません。", - "deleteProjectConfirm": "この空のプロジェクトを削除してもよろしいですか?この操作は取り消せません。", + "deleteProjectConfirm": "サイドバーからこのプロジェクトを除去しますか?プロジェクトファイル、メモリ、セッションデータは削除されません。", "enterProjectPath": "プロジェクトのパスを入力してください", "deleteSessionFailed": "セッションの削除に失敗しました。もう一度お試しください。", "deleteSessionError": "セッションの削除でエラーが発生しました。もう一度お試しください。", "renameSessionFailed": "セッション名の変更に失敗しました。もう一度お試しください。", "renameSessionError": "セッション名の変更でエラーが発生しました。もう一度お試しください。", - "deleteProjectFailed": "プロジェクトの削除に失敗しました。もう一度お試しください。", - "deleteProjectError": "プロジェクトの削除でエラーが発生しました。もう一度お試しください。", + "deleteProjectFailed": "プロジェクトの除去に失敗しました。もう一度お試しください。", + "deleteProjectError": "プロジェクトの除去でエラーが発生しました。もう一度お試しください。", "createProjectFailed": "プロジェクトの作成に失敗しました。もう一度お試しください。", "createProjectError": "プロジェクトの作成でエラーが発生しました。もう一度お試しください。" }, @@ -109,11 +109,13 @@ "updateAvailable": "アップデートあり" }, "deleteConfirmation": { - "deleteProject": "プロジェクトを削除", + "deleteProject": "プロジェクトを除去", "deleteSession": "セッションを削除", - "confirmDelete": "本当に削除しますか?", + "confirmDelete": "このプロジェクトをどうしますか:", "sessionCount": "このプロジェクトには{{count}}件の会話があります。", - "allConversationsDeleted": "すべての会話が完全に削除されます。", - "cannotUndo": "この操作は取り消せません。" + "removeFromSidebar": "サイドバーからのみ除去", + "deleteAllData": "すべてのデータを完全に削除", + "allConversationsDeleted": "プロジェクトはサイドバーから除去されます。ファイル、メモリ、セッションデータは保持されます。", + "cannotUndo": "後からプロジェクトを再追加できます。" } } diff --git a/src/i18n/locales/ko/sidebar.json b/src/i18n/locales/ko/sidebar.json index cda7eab2..d7fcafaa 100644 --- a/src/i18n/locales/ko/sidebar.json +++ b/src/i18n/locales/ko/sidebar.json @@ -2,7 +2,7 @@ "projects": { "title": "프로젝트", "newProject": "새 프로젝트", - "deleteProject": "프로젝트 삭제", + "deleteProject": "프로젝트 제거", "renameProject": "프로젝트 이름 변경", "noProjects": "프로젝트가 없습니다", "loadingProjects": "프로젝트 로딩 중...", @@ -40,7 +40,7 @@ "createProject": "새 프로젝트 생성", "refresh": "프로젝트 및 세션 새로고침 (Ctrl+R)", "renameProject": "프로젝트 이름 변경 (F2)", - "deleteProject": "빈 프로젝트 삭제 (Delete)", + "deleteProject": "사이드바에서 프로젝트 제거 (Delete)", "addToFavorites": "즐겨찾기에 추가", "removeFromFavorites": "즐겨찾기에서 제거", "editSessionName": "세션 이름 직접 편집", @@ -94,14 +94,14 @@ "deleteSuccess": "삭제되었습니다", "errorOccurred": "오류가 발생했습니다", "deleteSessionConfirm": "이 세션을 삭제하시겠습니까? 이 작업은 취소할 수 없습니다.", - "deleteProjectConfirm": "이 빈 프로젝트를 삭제하시겠습니까? 이 작업은 취소할 수 없습니다.", + "deleteProjectConfirm": "사이드바에서 이 프로젝트를 제거하시겠습니까? 프로젝트 파일, 메모리 및 세션 데이터는 삭제되지 않습니다.", "enterProjectPath": "프로젝트 경로를 입력해주세요", "deleteSessionFailed": "세션 삭제 실패. 다시 시도해주세요.", "deleteSessionError": "세션 삭제 오류. 다시 시도해주세요.", "renameSessionFailed": "세션 이름 변경 실패. 다시 시도해주세요.", "renameSessionError": "세션 이름 변경 오류. 다시 시도해주세요.", - "deleteProjectFailed": "프로젝트 삭제 실패. 다시 시도해주세요.", - "deleteProjectError": "프로젝트 삭제 오류. 다시 시도해주세요.", + "deleteProjectFailed": "프로젝트 제거 실패. 다시 시도해주세요.", + "deleteProjectError": "프로젝트 제거 오류. 다시 시도해주세요.", "createProjectFailed": "프로젝트 생성 실패. 다시 시도해주세요.", "createProjectError": "프로젝트 생성 오류. 다시 시도해주세요." }, @@ -109,12 +109,14 @@ "updateAvailable": "업데이트 가능" }, "deleteConfirmation": { - "deleteProject": "프로젝트 삭제", + "deleteProject": "프로젝트 제거", "deleteSession": "세션 삭제", - "confirmDelete": "정말 삭제하시겠습니까", + "confirmDelete": "이 프로젝트를 어떻게 하시겠습니까:", "sessionCount_one": "이 프로젝트에는 {{count}}개의 대화가 있습니다.", "sessionCount_other": "이 프로젝트에는 {{count}}개의 대화가 있습니다.", - "allConversationsDeleted": "모든 대화가 영구적으로 삭제됩니다.", - "cannotUndo": "이 작업은 취소할 수 없습니다." + "removeFromSidebar": "사이드바에서만 제거", + "deleteAllData": "모든 데이터 영구 삭제", + "allConversationsDeleted": "프로젝트가 사이드바에서 제거됩니다. 파일, 메모리 및 세션 데이터는 보존됩니다.", + "cannotUndo": "나중에 프로젝트를 다시 추가할 수 있습니다." } } \ No newline at end of file diff --git a/src/i18n/locales/ru/sidebar.json b/src/i18n/locales/ru/sidebar.json index 71250d2f..9af33d54 100644 --- a/src/i18n/locales/ru/sidebar.json +++ b/src/i18n/locales/ru/sidebar.json @@ -2,7 +2,7 @@ "projects": { "title": "Проекты", "newProject": "Новый проект", - "deleteProject": "Удалить проект", + "deleteProject": "Убрать проект", "renameProject": "Переименовать проект", "noProjects": "Проекты не найдены", "loadingProjects": "Загрузка проектов...", @@ -40,7 +40,7 @@ "createProject": "Создать новый проект", "refresh": "Обновить проекты и сеансы (Ctrl+R)", "renameProject": "Переименовать проект (F2)", - "deleteProject": "Удалить пустой проект (Delete)", + "deleteProject": "Убрать проект из боковой панели (Delete)", "addToFavorites": "Добавить в избранное", "removeFromFavorites": "Удалить из избранного", "editSessionName": "Вручную редактировать имя сеанса", @@ -95,14 +95,14 @@ "deleteSuccess": "Успешно удалено", "errorOccurred": "Произошла ошибка", "deleteSessionConfirm": "Вы уверены, что хотите удалить этот сеанс? Это действие нельзя отменить.", - "deleteProjectConfirm": "Вы уверены, что хотите удалить этот пустой проект? Это действие нельзя отменить.", + "deleteProjectConfirm": "Убрать этот проект из боковой панели? Файлы проекта, воспоминания и данные сеансов не будут удалены.", "enterProjectPath": "Пожалуйста, введите путь к проекту", "deleteSessionFailed": "Не удалось удалить сеанс. Попробуйте снова.", "deleteSessionError": "Ошибка при удалении сеанса. Попробуйте снова.", "renameSessionFailed": "Не удалось переименовать сеанс. Попробуйте снова.", "renameSessionError": "Ошибка при переименовании сеанса. Попробуйте снова.", - "deleteProjectFailed": "Не удалось удалить проект. Попробуйте снова.", - "deleteProjectError": "Ошибка при удалении проекта. Попробуйте снова.", + "deleteProjectFailed": "Не удалось убрать проект. Попробуйте снова.", + "deleteProjectError": "Ошибка при удалении проекта из списка. Попробуйте снова.", "createProjectFailed": "Не удалось создать проект. Попробуйте снова.", "createProjectError": "Ошибка при создании проекта. Попробуйте снова." }, @@ -126,14 +126,16 @@ "projectsScanned_other": "{{count}} проектов просканировано" }, "deleteConfirmation": { - "deleteProject": "Удалить проект", + "deleteProject": "Убрать проект", "deleteSession": "Удалить сеанс", - "confirmDelete": "Вы уверены, что хотите удалить", + "confirmDelete": "Что вы хотите сделать с", "sessionCount_one": "Этот проект содержит {{count}} разговор.", "sessionCount_few": "Этот проект содержит {{count}} разговора.", "sessionCount_many": "Этот проект содержит {{count}} разговоров.", "sessionCount_other": "Этот проект содержит {{count}} разговоров.", - "allConversationsDeleted": "Все разговоры будут удалены навсегда.", - "cannotUndo": "Это действие нельзя отменить." + "removeFromSidebar": "Убрать только из боковой панели", + "deleteAllData": "Удалить все данные навсегда", + "allConversationsDeleted": "Проект будет убран из боковой панели. Ваши файлы, воспоминания и данные сеансов сохранятся.", + "cannotUndo": "Вы сможете добавить проект позже." } } diff --git a/src/i18n/locales/zh-CN/sidebar.json b/src/i18n/locales/zh-CN/sidebar.json index 3a28778c..85053d92 100644 --- a/src/i18n/locales/zh-CN/sidebar.json +++ b/src/i18n/locales/zh-CN/sidebar.json @@ -2,7 +2,7 @@ "projects": { "title": "项目", "newProject": "新建项目", - "deleteProject": "删除项目", + "deleteProject": "移除项目", "renameProject": "重命名项目", "noProjects": "未找到项目", "loadingProjects": "加载项目中...", @@ -40,7 +40,7 @@ "createProject": "创建新项目", "refresh": "刷新项目和会话 (Ctrl+R)", "renameProject": "重命名项目 (F2)", - "deleteProject": "删除空项目 (Delete)", + "deleteProject": "从侧边栏移除项目 (Delete)", "addToFavorites": "添加到收藏", "removeFromFavorites": "从收藏移除", "editSessionName": "手动编辑会话名称", @@ -95,14 +95,14 @@ "deleteSuccess": "删除成功", "errorOccurred": "发生错误", "deleteSessionConfirm": "确定要删除此会话吗?此操作无法撤销。", - "deleteProjectConfirm": "确定要删除此空项目吗?此操作无法撤销。", + "deleteProjectConfirm": "从侧边栏移除此项目?您的项目文件、记忆和会话数据不会被删除。", "enterProjectPath": "请输入项目路径", "deleteSessionFailed": "删除会话失败,请重试。", "deleteSessionError": "删除会话时出错,请重试。", "renameSessionFailed": "重命名会话失败,请重试。", "renameSessionError": "重命名会话时出错,请重试。", - "deleteProjectFailed": "删除项目失败,请重试。", - "deleteProjectError": "删除项目时出错,请重试。", + "deleteProjectFailed": "移除项目失败,请重试。", + "deleteProjectError": "移除项目时出错,请重试。", "createProjectFailed": "创建项目失败,请重试。", "createProjectError": "创建项目时出错,请重试。" }, @@ -122,12 +122,14 @@ "projectsScanned_other": "{{count}} 个项目已扫描" }, "deleteConfirmation": { - "deleteProject": "删除项目", + "deleteProject": "移除项目", "deleteSession": "删除会话", - "confirmDelete": "您确定要删除", + "confirmDelete": "您想如何处理", "sessionCount_one": "此项目包含 {{count}} 个对话。", "sessionCount_other": "此项目包含 {{count}} 个对话。", - "allConversationsDeleted": "所有对话将被永久删除。", - "cannotUndo": "此操作无法撤销。" + "removeFromSidebar": "仅从侧边栏移除", + "deleteAllData": "永久删除所有数据", + "allConversationsDeleted": "项目将从侧边栏中移除。您的文件、记忆和会话数据将会保留。", + "cannotUndo": "您可以稍后重新添加此项目。" } } diff --git a/src/utils/api.js b/src/utils/api.js index 438cab82..9132c16f 100644 --- a/src/utils/api.js +++ b/src/utils/api.js @@ -89,10 +89,15 @@ export const api = { authenticatedFetch(`/api/gemini/sessions/${sessionId}`, { method: 'DELETE', }), - deleteProject: (projectName, force = false) => - authenticatedFetch(`/api/projects/${projectName}${force ? '?force=true' : ''}`, { + deleteProject: (projectName, force = false, deleteData = false) => { + const params = new URLSearchParams(); + if (force) params.set('force', 'true'); + if (deleteData) params.set('deleteData', 'true'); + const qs = params.toString(); + return authenticatedFetch(`/api/projects/${projectName}${qs ? `?${qs}` : ''}`, { method: 'DELETE', - }), + }); + }, searchConversationsUrl: (query, limit = 50) => { const token = localStorage.getItem('auth-token'); const params = new URLSearchParams({ q: query, limit: String(limit) }); From 9ef1ab533de5aa725575e755bb6cbe98b9ba7f66 Mon Sep 17 00:00:00 2001 From: Simos Mikelatos Date: Thu, 16 Apr 2026 12:32:25 +0200 Subject: [PATCH 07/10] Refactor CLI authentication module location (#660) * refactor: move cli-auth.js to the providers folder * fix: expired oauth token returns no error message --- server/claude-sdk.js | 13 +- server/cursor-cli.js | 9 +- server/gemini-cli.js | 18 +- server/openai-codex.js | 10 +- server/providers/claude/status.js | 136 ++++++ server/providers/codex/status.js | 78 ++++ server/providers/cursor/status.js | 128 +++++ server/providers/gemini/status.js | 111 +++++ server/providers/registry.js | 27 +- server/providers/types.js | 13 + server/routes/cli-auth.js | 441 +----------------- .../chat/view/subcomponents/ChatComposer.tsx | 5 +- 12 files changed, 555 insertions(+), 434 deletions(-) create mode 100644 server/providers/claude/status.js create mode 100644 server/providers/codex/status.js create mode 100644 server/providers/cursor/status.js create mode 100644 server/providers/gemini/status.js diff --git a/server/claude-sdk.js b/server/claude-sdk.js index 919d8632..1489962b 100644 --- a/server/claude-sdk.js +++ b/server/claude-sdk.js @@ -26,13 +26,14 @@ import { } from './services/notification-orchestrator.js'; import { claudeAdapter } from './providers/claude/adapter.js'; import { createNormalizedMessage } from './providers/types.js'; +import { getStatusChecker } from './providers/registry.js'; const activeSessions = new Map(); const pendingToolApprovals = new Map(); const TOOL_APPROVAL_TIMEOUT_MS = parseInt(process.env.CLAUDE_TOOL_APPROVAL_TIMEOUT_MS, 10) || 55000; -const TOOLS_REQUIRING_INTERACTION = new Set(['AskUserQuestion']); +const TOOLS_REQUIRING_INTERACTION = new Set(['AskUserQuestion', 'ExitPlanMode']); function createRequestId() { if (typeof crypto.randomUUID === 'function') { @@ -705,8 +706,14 @@ async function queryClaudeSDK(command, options = {}, ws) { // Clean up temporary image files on error await cleanupTempFiles(tempImagePaths, tempDir); + // Check if Claude CLI is installed for a clearer error message + const installed = getStatusChecker('claude')?.checkInstalled() ?? true; + const errorContent = !installed + ? 'Claude Code is not installed. Please install it first: https://docs.anthropic.com/en/docs/claude-code' + : error.message; + // Send error to WebSocket - ws.send(createNormalizedMessage({ kind: 'error', content: error.message, sessionId: capturedSessionId || sessionId || null, provider: 'claude' })); + ws.send(createNormalizedMessage({ kind: 'error', content: errorContent, sessionId: capturedSessionId || sessionId || null, provider: 'claude' })); notifyRunFailed({ userId: ws?.userId || null, provider: 'claude', @@ -714,8 +721,6 @@ async function queryClaudeSDK(command, options = {}, ws) { sessionName: sessionSummary, error }); - - throw error; } } diff --git a/server/cursor-cli.js b/server/cursor-cli.js index aedd7e0b..f6193369 100644 --- a/server/cursor-cli.js +++ b/server/cursor-cli.js @@ -3,6 +3,7 @@ import crossSpawn from 'cross-spawn'; import { notifyRunFailed, notifyRunStopped } from './services/notification-orchestrator.js'; import { cursorAdapter } from './providers/cursor/adapter.js'; import { createNormalizedMessage } from './providers/types.js'; +import { getStatusChecker } from './providers/registry.js'; // Use cross-spawn on Windows for better command execution const spawnFunction = process.platform === 'win32' ? crossSpawn : spawn; @@ -294,7 +295,13 @@ async function spawnCursor(command, options = {}, ws) { const finalSessionId = capturedSessionId || sessionId || processKey; activeCursorProcesses.delete(finalSessionId); - ws.send(createNormalizedMessage({ kind: 'error', content: error.message, sessionId: capturedSessionId || sessionId || null, provider: 'cursor' })); + // Check if Cursor CLI is installed for a clearer error message + const installed = getStatusChecker('cursor')?.checkInstalled() ?? true; + const errorContent = !installed + ? 'Cursor CLI is not installed. Please install it from https://cursor.com' + : error.message; + + ws.send(createNormalizedMessage({ kind: 'error', content: errorContent, sessionId: capturedSessionId || sessionId || null, provider: 'cursor' })); notifyTerminalState({ error }); settleOnce(() => reject(error)); diff --git a/server/gemini-cli.js b/server/gemini-cli.js index 86472707..62aa5307 100644 --- a/server/gemini-cli.js +++ b/server/gemini-cli.js @@ -10,6 +10,7 @@ import sessionManager from './sessionManager.js'; import GeminiResponseHandler from './gemini-response-handler.js'; import { notifyRunFailed, notifyRunStopped } from './services/notification-orchestrator.js'; import { createNormalizedMessage } from './providers/types.js'; +import { getStatusChecker } from './providers/registry.js'; let activeGeminiProcesses = new Map(); // Track active processes by session ID @@ -380,6 +381,15 @@ async function spawnGemini(command, options = {}, ws) { notifyTerminalState({ code }); resolve(); } else { + // code 127 = shell "command not found" — check installation + if (code === 127) { + const installed = getStatusChecker('gemini')?.checkInstalled() ?? true; + if (!installed) { + const socketSessionId = typeof ws.getSessionId === 'function' ? ws.getSessionId() : finalSessionId; + ws.send(createNormalizedMessage({ kind: 'error', content: 'Gemini CLI is not installed. Please install it first: https://github.com/google-gemini/gemini-cli', sessionId: socketSessionId, provider: 'gemini' })); + } + } + notifyTerminalState({ code, error: code === null ? 'Gemini CLI process was terminated or timed out' : null @@ -394,8 +404,14 @@ async function spawnGemini(command, options = {}, ws) { const finalSessionId = capturedSessionId || sessionId || processKey; activeGeminiProcesses.delete(finalSessionId); + // Check if Gemini CLI is installed for a clearer error message + const installed = getStatusChecker('gemini')?.checkInstalled() ?? true; + const errorContent = !installed + ? 'Gemini CLI is not installed. Please install it first: https://github.com/google-gemini/gemini-cli' + : error.message; + const errorSessionId = typeof ws.getSessionId === 'function' ? ws.getSessionId() : finalSessionId; - ws.send(createNormalizedMessage({ kind: 'error', content: error.message, sessionId: errorSessionId, provider: 'gemini' })); + ws.send(createNormalizedMessage({ kind: 'error', content: errorContent, sessionId: errorSessionId, provider: 'gemini' })); notifyTerminalState({ error }); reject(error); diff --git a/server/openai-codex.js b/server/openai-codex.js index 0169a3b6..99a8e435 100644 --- a/server/openai-codex.js +++ b/server/openai-codex.js @@ -17,6 +17,7 @@ import { Codex } from '@openai/codex-sdk'; import { notifyRunFailed, notifyRunStopped } from './services/notification-orchestrator.js'; import { codexAdapter } from './providers/codex/adapter.js'; import { createNormalizedMessage } from './providers/types.js'; +import { getStatusChecker } from './providers/registry.js'; // Track active sessions const activeCodexSessions = new Map(); @@ -308,7 +309,14 @@ export async function queryCodex(command, options = {}, ws) { if (!wasAborted) { console.error('[Codex] Error:', error); - sendMessage(ws, createNormalizedMessage({ kind: 'error', content: error.message, sessionId: currentSessionId, provider: 'codex' })); + + // Check if Codex SDK is available for a clearer error message + const installed = getStatusChecker('codex')?.checkInstalled() ?? true; + const errorContent = !installed + ? 'Codex CLI is not configured. Please set up authentication first.' + : error.message; + + sendMessage(ws, createNormalizedMessage({ kind: 'error', content: errorContent, sessionId: currentSessionId, provider: 'codex' })); if (!terminalFailure) { notifyRunFailed({ userId: ws?.userId || null, diff --git a/server/providers/claude/status.js b/server/providers/claude/status.js new file mode 100644 index 00000000..c0d7d231 --- /dev/null +++ b/server/providers/claude/status.js @@ -0,0 +1,136 @@ +/** + * Claude Provider Status + * + * Checks whether Claude Code CLI is installed and whether the user + * has valid authentication credentials. + * + * @module providers/claude/status + */ + +import { execFileSync } from 'child_process'; +import { promises as fs } from 'fs'; +import path from 'path'; +import os from 'os'; + +/** + * Check if Claude Code CLI is installed and available. + * Uses CLAUDE_CLI_PATH env var if set, otherwise looks for 'claude' in PATH. + * @returns {boolean} + */ +export function checkInstalled() { + const cliPath = process.env.CLAUDE_CLI_PATH || 'claude'; + try { + execFileSync(cliPath, ['--version'], { stdio: 'ignore', timeout: 5000 }); + return true; + } catch { + return false; + } +} + +/** + * Full status check: installation + authentication. + * @returns {Promise} + */ +export async function checkStatus() { + const installed = checkInstalled(); + + if (!installed) { + return { + installed, + authenticated: false, + email: null, + method: null, + error: 'Claude Code CLI is not installed' + }; + } + + const credentialsResult = await checkCredentials(); + + if (credentialsResult.authenticated) { + return { + installed, + authenticated: true, + email: credentialsResult.email || 'Authenticated', + method: credentialsResult.method || null, + error: null + }; + } + + return { + installed, + authenticated: false, + email: credentialsResult.email || null, + method: credentialsResult.method || null, + error: credentialsResult.error || 'Not authenticated' + }; +} + +// ─── Internal helpers ─────────────────────────────────────────────────────── + +async function loadSettingsEnv() { + try { + const settingsPath = path.join(os.homedir(), '.claude', 'settings.json'); + const content = await fs.readFile(settingsPath, 'utf8'); + const settings = JSON.parse(content); + + if (settings?.env && typeof settings.env === 'object') { + return settings.env; + } + } catch { + // Ignore missing or malformed settings. + } + + return {}; +} + +/** + * Checks Claude authentication credentials. + * + * Priority 1: ANTHROPIC_API_KEY environment variable + * Priority 1b: ~/.claude/settings.json env values + * Priority 2: ~/.claude/.credentials.json OAuth tokens + */ +async function checkCredentials() { + if (process.env.ANTHROPIC_API_KEY && process.env.ANTHROPIC_API_KEY.trim()) { + return { authenticated: true, email: 'API Key Auth', method: 'api_key' }; + } + + const settingsEnv = await loadSettingsEnv(); + + if (typeof settingsEnv.ANTHROPIC_API_KEY === 'string' && settingsEnv.ANTHROPIC_API_KEY.trim()) { + return { authenticated: true, email: 'API Key Auth', method: 'api_key' }; + } + + if (typeof settingsEnv.ANTHROPIC_AUTH_TOKEN === 'string' && settingsEnv.ANTHROPIC_AUTH_TOKEN.trim()) { + return { authenticated: true, email: 'Configured via settings.json', method: 'api_key' }; + } + + try { + const credPath = path.join(os.homedir(), '.claude', '.credentials.json'); + const content = await fs.readFile(credPath, 'utf8'); + const creds = JSON.parse(content); + + const oauth = creds.claudeAiOauth; + if (oauth && oauth.accessToken) { + const isExpired = oauth.expiresAt && Date.now() >= oauth.expiresAt; + if (!isExpired) { + return { + authenticated: true, + email: creds.email || creds.user || null, + method: 'credentials_file' + }; + } + + return { + authenticated: false, + email: creds.email || creds.user || null, + method: 'credentials_file', + error: 'OAuth token has expired. Please re-authenticate with claude login' + }; + } + + return { authenticated: false, email: null, method: null }; + } catch { + return { authenticated: false, email: null, method: null }; + } +} diff --git a/server/providers/codex/status.js b/server/providers/codex/status.js new file mode 100644 index 00000000..cf1c273f --- /dev/null +++ b/server/providers/codex/status.js @@ -0,0 +1,78 @@ +/** + * Codex Provider Status + * + * Checks whether the user has valid Codex authentication credentials. + * Codex uses an SDK that makes direct API calls (no external binary), + * so installation check always returns true if the server is running. + * + * @module providers/codex/status + */ + +import { promises as fs } from 'fs'; +import path from 'path'; +import os from 'os'; + +/** + * Check if Codex is installed. + * Codex SDK is bundled with this application — no external binary needed. + * @returns {boolean} + */ +export function checkInstalled() { + return true; +} + +/** + * Full status check: installation + authentication. + * @returns {Promise} + */ +export async function checkStatus() { + const installed = checkInstalled(); + const result = await checkCredentials(); + + return { + installed, + authenticated: result.authenticated, + email: result.email || null, + error: result.error || null + }; +} + +// ─── Internal helpers ─────────────────────────────────────────────────────── + +async function checkCredentials() { + try { + const authPath = path.join(os.homedir(), '.codex', 'auth.json'); + const content = await fs.readFile(authPath, 'utf8'); + const auth = JSON.parse(content); + + const tokens = auth.tokens || {}; + + if (tokens.id_token || tokens.access_token) { + let email = 'Authenticated'; + if (tokens.id_token) { + try { + const parts = tokens.id_token.split('.'); + if (parts.length >= 2) { + const payload = JSON.parse(Buffer.from(parts[1], 'base64url').toString('utf8')); + email = payload.email || payload.user || 'Authenticated'; + } + } catch { + email = 'Authenticated'; + } + } + + return { authenticated: true, email }; + } + + if (auth.OPENAI_API_KEY) { + return { authenticated: true, email: 'API Key Auth' }; + } + + return { authenticated: false, email: null, error: 'No valid tokens found' }; + } catch (error) { + if (error.code === 'ENOENT') { + return { authenticated: false, email: null, error: 'Codex not configured' }; + } + return { authenticated: false, email: null, error: error.message }; + } +} diff --git a/server/providers/cursor/status.js b/server/providers/cursor/status.js new file mode 100644 index 00000000..127e35b7 --- /dev/null +++ b/server/providers/cursor/status.js @@ -0,0 +1,128 @@ +/** + * Cursor Provider Status + * + * Checks whether cursor-agent CLI is installed and whether the user + * is logged in. + * + * @module providers/cursor/status + */ + +import { execFileSync, spawn } from 'child_process'; + +/** + * Check if cursor-agent CLI is installed. + * @returns {boolean} + */ +export function checkInstalled() { + try { + execFileSync('cursor-agent', ['--version'], { stdio: 'ignore', timeout: 5000 }); + return true; + } catch { + return false; + } +} + +/** + * Full status check: installation + authentication. + * @returns {Promise} + */ +export async function checkStatus() { + const installed = checkInstalled(); + + if (!installed) { + return { + installed, + authenticated: false, + email: null, + error: 'Cursor CLI is not installed' + }; + } + + const result = await checkCursorLogin(); + + return { + installed, + authenticated: result.authenticated, + email: result.email || null, + error: result.error || null + }; +} + +// ─── Internal helpers ─────────────────────────────────────────────────────── + +function checkCursorLogin() { + return new Promise((resolve) => { + let processCompleted = false; + + const timeout = setTimeout(() => { + if (!processCompleted) { + processCompleted = true; + if (childProcess) { + childProcess.kill(); + } + resolve({ + authenticated: false, + email: null, + error: 'Command timeout' + }); + } + }, 5000); + + let childProcess; + try { + childProcess = spawn('cursor-agent', ['status']); + } catch { + clearTimeout(timeout); + processCompleted = true; + resolve({ + authenticated: false, + email: null, + error: 'Cursor CLI not found or not installed' + }); + return; + } + + let stdout = ''; + let stderr = ''; + + childProcess.stdout.on('data', (data) => { + stdout += data.toString(); + }); + + childProcess.stderr.on('data', (data) => { + stderr += data.toString(); + }); + + childProcess.on('close', (code) => { + if (processCompleted) return; + processCompleted = true; + clearTimeout(timeout); + + if (code === 0) { + const emailMatch = stdout.match(/Logged in as ([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})/i); + + if (emailMatch) { + resolve({ authenticated: true, email: emailMatch[1] }); + } else if (stdout.includes('Logged in')) { + resolve({ authenticated: true, email: 'Logged in' }); + } else { + resolve({ authenticated: false, email: null, error: 'Not logged in' }); + } + } else { + resolve({ authenticated: false, email: null, error: stderr || 'Not logged in' }); + } + }); + + childProcess.on('error', () => { + if (processCompleted) return; + processCompleted = true; + clearTimeout(timeout); + + resolve({ + authenticated: false, + email: null, + error: 'Cursor CLI not found or not installed' + }); + }); + }); +} diff --git a/server/providers/gemini/status.js b/server/providers/gemini/status.js new file mode 100644 index 00000000..385f889f --- /dev/null +++ b/server/providers/gemini/status.js @@ -0,0 +1,111 @@ +/** + * Gemini Provider Status + * + * Checks whether Gemini CLI is installed and whether the user + * has valid authentication credentials. + * + * @module providers/gemini/status + */ + +import { execFileSync } from 'child_process'; +import { promises as fs } from 'fs'; +import path from 'path'; +import os from 'os'; + +/** + * Check if Gemini CLI is installed. + * Uses GEMINI_PATH env var if set, otherwise looks for 'gemini' in PATH. + * @returns {boolean} + */ +export function checkInstalled() { + const cliPath = process.env.GEMINI_PATH || 'gemini'; + try { + execFileSync(cliPath, ['--version'], { stdio: 'ignore', timeout: 5000 }); + return true; + } catch { + return false; + } +} + +/** + * Full status check: installation + authentication. + * @returns {Promise} + */ +export async function checkStatus() { + const installed = checkInstalled(); + + if (!installed) { + return { + installed, + authenticated: false, + email: null, + error: 'Gemini CLI is not installed' + }; + } + + const result = await checkCredentials(); + + return { + installed, + authenticated: result.authenticated, + email: result.email || null, + error: result.error || null + }; +} + +// ─── Internal helpers ─────────────────────────────────────────────────────── + +async function checkCredentials() { + if (process.env.GEMINI_API_KEY && process.env.GEMINI_API_KEY.trim()) { + return { authenticated: true, email: 'API Key Auth' }; + } + + try { + const credsPath = path.join(os.homedir(), '.gemini', 'oauth_creds.json'); + const content = await fs.readFile(credsPath, 'utf8'); + const creds = JSON.parse(content); + + if (creds.access_token) { + let email = 'OAuth Session'; + + try { + const tokenRes = await fetch(`https://oauth2.googleapis.com/tokeninfo?access_token=${creds.access_token}`); + if (tokenRes.ok) { + const tokenInfo = await tokenRes.json(); + if (tokenInfo.email) { + email = tokenInfo.email; + } + } else if (!creds.refresh_token) { + return { + authenticated: false, + email: null, + error: 'Access token invalid and no refresh token found' + }; + } else { + // Token might be expired but we have a refresh token, so CLI will refresh it + email = await getActiveAccountEmail() || email; + } + } catch { + // Network error, fallback to checking local accounts file + email = await getActiveAccountEmail() || email; + } + + return { authenticated: true, email }; + } + + return { authenticated: false, email: null, error: 'No valid tokens found in oauth_creds' }; + } catch { + return { authenticated: false, email: null, error: 'Gemini CLI not configured' }; + } +} + +async function getActiveAccountEmail() { + try { + const accPath = path.join(os.homedir(), '.gemini', 'google_accounts.json'); + const accContent = await fs.readFile(accPath, 'utf8'); + const accounts = JSON.parse(accContent); + return accounts.active || null; + } catch { + return null; + } +} diff --git a/server/providers/registry.js b/server/providers/registry.js index 236c909e..4f62b60b 100644 --- a/server/providers/registry.js +++ b/server/providers/registry.js @@ -1,8 +1,9 @@ /** * Provider Registry * - * Centralizes provider adapter lookup. All code that needs a provider adapter - * should go through this registry instead of importing individual adapters directly. + * Centralizes provider adapter and status checker lookup. All code that needs + * a provider adapter or status checker should go through this registry instead + * of importing individual modules directly. * * @module providers/registry */ @@ -12,6 +13,11 @@ import { cursorAdapter } from './cursor/adapter.js'; import { codexAdapter } from './codex/adapter.js'; import { geminiAdapter } from './gemini/adapter.js'; +import * as claudeStatus from './claude/status.js'; +import * as cursorStatus from './cursor/status.js'; +import * as codexStatus from './codex/status.js'; +import * as geminiStatus from './gemini/status.js'; + /** * @typedef {import('./types.js').ProviderAdapter} ProviderAdapter * @typedef {import('./types.js').SessionProvider} SessionProvider @@ -20,12 +26,20 @@ import { geminiAdapter } from './gemini/adapter.js'; /** @type {Map} */ const providers = new Map(); +/** @type {Map boolean, checkStatus: () => Promise }>} */ +const statusCheckers = new Map(); + // Register built-in providers providers.set('claude', claudeAdapter); providers.set('cursor', cursorAdapter); providers.set('codex', codexAdapter); providers.set('gemini', geminiAdapter); +statusCheckers.set('claude', claudeStatus); +statusCheckers.set('cursor', cursorStatus); +statusCheckers.set('codex', codexStatus); +statusCheckers.set('gemini', geminiStatus); + /** * Get a provider adapter by name. * @param {string} name - Provider name (e.g., 'claude', 'cursor', 'codex', 'gemini') @@ -35,6 +49,15 @@ export function getProvider(name) { return providers.get(name); } +/** + * Get a provider status checker by name. + * @param {string} name - Provider name + * @returns {{ checkInstalled: () => boolean, checkStatus: () => Promise } | undefined} + */ +export function getStatusChecker(name) { + return statusCheckers.get(name); +} + /** * Get all registered provider names. * @returns {string[]} diff --git a/server/providers/types.js b/server/providers/types.js index 5541525b..9867b077 100644 --- a/server/providers/types.js +++ b/server/providers/types.js @@ -69,6 +69,19 @@ * @property {object} [tokenUsage] - Token usage data (provider-specific) */ +// ─── Provider Status ──────────────────────────────────────────────────────── + +/** + * Result of a provider status check (installation + authentication). + * + * @typedef {Object} ProviderStatus + * @property {boolean} installed - Whether the provider's CLI/SDK is available + * @property {boolean} authenticated - Whether valid credentials exist + * @property {string|null} email - User email or auth method identifier + * @property {string|null} [method] - Auth method (e.g. 'api_key', 'credentials_file') + * @property {string|null} [error] - Error message if not installed or not authenticated + */ + // ─── Provider Adapter Interface ────────────────────────────────────────────── /** diff --git a/server/routes/cli-auth.js b/server/routes/cli-auth.js index 78ffa30b..4183e83f 100644 --- a/server/routes/cli-auth.js +++ b/server/routes/cli-auth.js @@ -1,434 +1,27 @@ +/** + * CLI Auth Routes + * + * Thin router that delegates to per-provider status checkers + * registered in the provider registry. + * + * @module routes/cli-auth + */ + import express from 'express'; -import { spawn } from 'child_process'; -import fs from 'fs/promises'; -import path from 'path'; -import os from 'os'; +import { getAllProviders, getStatusChecker } from '../providers/registry.js'; const router = express.Router(); -router.get('/claude/status', async (req, res) => { - try { - const credentialsResult = await checkClaudeCredentials(); - - if (credentialsResult.authenticated) { - return res.json({ - authenticated: true, - email: credentialsResult.email || 'Authenticated', - method: credentialsResult.method // 'api_key' or 'credentials_file' - }); - } - - return res.json({ - authenticated: false, - email: null, - method: null, - error: credentialsResult.error || 'Not authenticated' - }); - - } catch (error) { - console.error('Error checking Claude auth status:', error); - res.status(500).json({ - authenticated: false, - email: null, - method: null, - error: error.message - }); - } -}); - -router.get('/cursor/status', async (req, res) => { - try { - const result = await checkCursorStatus(); - - res.json({ - authenticated: result.authenticated, - email: result.email, - error: result.error - }); - - } catch (error) { - console.error('Error checking Cursor auth status:', error); - res.status(500).json({ - authenticated: false, - email: null, - error: error.message - }); - } -}); - -router.get('/codex/status', async (req, res) => { - try { - const result = await checkCodexCredentials(); - - res.json({ - authenticated: result.authenticated, - email: result.email, - error: result.error - }); - - } catch (error) { - console.error('Error checking Codex auth status:', error); - res.status(500).json({ - authenticated: false, - email: null, - error: error.message - }); - } -}); - -router.get('/gemini/status', async (req, res) => { - try { - const result = await checkGeminiCredentials(); - - res.json({ - authenticated: result.authenticated, - email: result.email, - error: result.error - }); - - } catch (error) { - console.error('Error checking Gemini auth status:', error); - res.status(500).json({ - authenticated: false, - email: null, - error: error.message - }); - } -}); - -async function loadClaudeSettingsEnv() { - try { - const settingsPath = path.join(os.homedir(), '.claude', 'settings.json'); - const content = await fs.readFile(settingsPath, 'utf8'); - const settings = JSON.parse(content); - - if (settings?.env && typeof settings.env === 'object') { - return settings.env; - } - } catch (error) { - // Ignore missing or malformed settings and fall back to other auth sources. - } - - return {}; -} - -/** - * Checks Claude authentication credentials using two methods with priority order: - * - * Priority 1: ANTHROPIC_API_KEY environment variable - * Priority 1b: ~/.claude/settings.json env values - * Priority 2: ~/.claude/.credentials.json OAuth tokens - * - * The Claude Agent SDK prioritizes environment variables over authenticated subscriptions. - * This matching behavior ensures consistency with how the SDK authenticates. - * - * References: - * - https://support.claude.com/en/articles/12304248-managing-api-key-environment-variables-in-claude-code - * "Claude Code prioritizes environment variable API keys over authenticated subscriptions" - * - https://platform.claude.com/docs/en/agent-sdk/overview - * SDK authentication documentation - * - * @returns {Promise} Authentication status with { authenticated, email, method } - * - authenticated: boolean indicating if valid credentials exist - * - email: user email or auth method identifier - * - method: 'api_key' for env var, 'credentials_file' for OAuth tokens - */ -async function checkClaudeCredentials() { - // Priority 1: Check for ANTHROPIC_API_KEY environment variable - // The SDK checks this first and uses it if present, even if OAuth tokens exist. - // When set, API calls are charged via pay-as-you-go rates instead of subscription. - if (process.env.ANTHROPIC_API_KEY && process.env.ANTHROPIC_API_KEY.trim()) { - return { - authenticated: true, - email: 'API Key Auth', - method: 'api_key' - }; - } - - // Priority 1b: Check ~/.claude/settings.json env values. - // Claude Code can read proxy/auth values from settings.json even when the - // CloudCLI server process itself was not started with those env vars exported. - const settingsEnv = await loadClaudeSettingsEnv(); - - if (typeof settingsEnv.ANTHROPIC_API_KEY === 'string' && settingsEnv.ANTHROPIC_API_KEY.trim()) { - return { - authenticated: true, - email: 'API Key Auth', - method: 'api_key' - }; - } - - if (typeof settingsEnv.ANTHROPIC_AUTH_TOKEN === 'string' && settingsEnv.ANTHROPIC_AUTH_TOKEN.trim()) { - return { - authenticated: true, - email: 'Configured via settings.json', - method: 'api_key' - }; - } - - // Priority 2: Check ~/.claude/.credentials.json for OAuth tokens - // This is the standard authentication method used by Claude CLI after running - // 'claude /login' or 'claude setup-token' commands. - try { - const credPath = path.join(os.homedir(), '.claude', '.credentials.json'); - const content = await fs.readFile(credPath, 'utf8'); - const creds = JSON.parse(content); - - const oauth = creds.claudeAiOauth; - if (oauth && oauth.accessToken) { - const isExpired = oauth.expiresAt && Date.now() >= oauth.expiresAt; - - if (!isExpired) { - return { - authenticated: true, - email: creds.email || creds.user || null, - method: 'credentials_file' - }; - } - } - - return { - authenticated: false, - email: null, - method: null - }; - } catch (error) { - return { - authenticated: false, - email: null, - method: null - }; - } -} - -function checkCursorStatus() { - return new Promise((resolve) => { - let processCompleted = false; - - const timeout = setTimeout(() => { - if (!processCompleted) { - processCompleted = true; - if (childProcess) { - childProcess.kill(); - } - resolve({ - authenticated: false, - email: null, - error: 'Command timeout' - }); - } - }, 5000); - - let childProcess; +for (const provider of getAllProviders()) { + router.get(`/${provider}/status`, async (req, res) => { try { - childProcess = spawn('cursor-agent', ['status']); - } catch (err) { - clearTimeout(timeout); - processCompleted = true; - resolve({ - authenticated: false, - email: null, - error: 'Cursor CLI not found or not installed' - }); - return; + const checker = getStatusChecker(provider); + res.json(await checker.checkStatus()); + } catch (error) { + console.error(`Error checking ${provider} status:`, error); + res.status(500).json({ authenticated: false, error: error.message }); } - - let stdout = ''; - let stderr = ''; - - childProcess.stdout.on('data', (data) => { - stdout += data.toString(); - }); - - childProcess.stderr.on('data', (data) => { - stderr += data.toString(); - }); - - childProcess.on('close', (code) => { - if (processCompleted) return; - processCompleted = true; - clearTimeout(timeout); - - if (code === 0) { - const emailMatch = stdout.match(/Logged in as ([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})/i); - - if (emailMatch) { - resolve({ - authenticated: true, - email: emailMatch[1], - output: stdout - }); - } else if (stdout.includes('Logged in')) { - resolve({ - authenticated: true, - email: 'Logged in', - output: stdout - }); - } else { - resolve({ - authenticated: false, - email: null, - error: 'Not logged in' - }); - } - } else { - resolve({ - authenticated: false, - email: null, - error: stderr || 'Not logged in' - }); - } - }); - - childProcess.on('error', (err) => { - if (processCompleted) return; - processCompleted = true; - clearTimeout(timeout); - - resolve({ - authenticated: false, - email: null, - error: 'Cursor CLI not found or not installed' - }); - }); }); } -async function checkCodexCredentials() { - try { - const authPath = path.join(os.homedir(), '.codex', 'auth.json'); - const content = await fs.readFile(authPath, 'utf8'); - const auth = JSON.parse(content); - - // Tokens are nested under 'tokens' key - const tokens = auth.tokens || {}; - - // Check for valid tokens (id_token or access_token) - if (tokens.id_token || tokens.access_token) { - // Try to extract email from id_token JWT payload - let email = 'Authenticated'; - if (tokens.id_token) { - try { - // JWT is base64url encoded: header.payload.signature - const parts = tokens.id_token.split('.'); - if (parts.length >= 2) { - // Decode the payload (second part) - const payload = JSON.parse(Buffer.from(parts[1], 'base64url').toString('utf8')); - email = payload.email || payload.user || 'Authenticated'; - } - } catch { - // If JWT decoding fails, use fallback - email = 'Authenticated'; - } - } - - return { - authenticated: true, - email - }; - } - - // Also check for OPENAI_API_KEY as fallback auth method - if (auth.OPENAI_API_KEY) { - return { - authenticated: true, - email: 'API Key Auth' - }; - } - - return { - authenticated: false, - email: null, - error: 'No valid tokens found' - }; - } catch (error) { - if (error.code === 'ENOENT') { - return { - authenticated: false, - email: null, - error: 'Codex not configured' - }; - } - return { - authenticated: false, - email: null, - error: error.message - }; - } -} - -async function checkGeminiCredentials() { - if (process.env.GEMINI_API_KEY && process.env.GEMINI_API_KEY.trim()) { - return { - authenticated: true, - email: 'API Key Auth' - }; - } - - try { - const credsPath = path.join(os.homedir(), '.gemini', 'oauth_creds.json'); - const content = await fs.readFile(credsPath, 'utf8'); - const creds = JSON.parse(content); - - if (creds.access_token) { - let email = 'OAuth Session'; - - try { - // Validate token against Google API - const tokenRes = await fetch(`https://oauth2.googleapis.com/tokeninfo?access_token=${creds.access_token}`); - if (tokenRes.ok) { - const tokenInfo = await tokenRes.json(); - if (tokenInfo.email) { - email = tokenInfo.email; - } - } else if (!creds.refresh_token) { - // Token invalid and no refresh token available - return { - authenticated: false, - email: null, - error: 'Access token invalid and no refresh token found' - }; - } else { - // Token might be expired but we have a refresh token, so CLI will refresh it - try { - const accPath = path.join(os.homedir(), '.gemini', 'google_accounts.json'); - const accContent = await fs.readFile(accPath, 'utf8'); - const accounts = JSON.parse(accContent); - if (accounts.active) { - email = accounts.active; - } - } catch (e) { } - } - } catch (e) { - // Network error, fallback to checking local accounts file - try { - const accPath = path.join(os.homedir(), '.gemini', 'google_accounts.json'); - const accContent = await fs.readFile(accPath, 'utf8'); - const accounts = JSON.parse(accContent); - if (accounts.active) { - email = accounts.active; - } - } catch (err) { } - } - - return { - authenticated: true, - email: email - }; - } - - return { - authenticated: false, - email: null, - error: 'No valid tokens found in oauth_creds' - }; - } catch (error) { - return { - authenticated: false, - email: null, - error: 'Gemini CLI not configured' - }; - } -} - export default router; diff --git a/src/components/chat/view/subcomponents/ChatComposer.tsx b/src/components/chat/view/subcomponents/ChatComposer.tsx index e6da236d..69e8fc3c 100644 --- a/src/components/chat/view/subcomponents/ChatComposer.tsx +++ b/src/components/chat/view/subcomponents/ChatComposer.tsx @@ -160,6 +160,9 @@ export default function ChatComposer({ (r) => r.toolName === 'AskUserQuestion' ); + // Hide the thinking/status bar while any permission request is pending + const hasPendingPermissions = pendingPermissionRequests.length > 0; + // On mobile, when input is focused, float the input box at the bottom const mobileFloatingClass = isInputFocused ? 'max-sm:fixed max-sm:bottom-0 max-sm:left-0 max-sm:right-0 max-sm:z-50 max-sm:bg-background max-sm:shadow-[0_-4px_20px_rgba(0,0,0,0.15)]' @@ -167,7 +170,7 @@ export default function ChatComposer({ return (
- {!hasQuestionPanel && ( + {!hasPendingPermissions && (
Date: Thu, 16 Apr 2026 10:33:45 +0000 Subject: [PATCH 08/10] chore(release): v1.29.4 --- CHANGELOG.md | 19 +++++++++++++++++++ package-lock.json | 4 ++-- package.json | 4 ++-- 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ad8eeebf..5890928b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,25 @@ All notable changes to CloudCLI UI will be documented in this file. +## [1.29.4](https://github.com/siteboon/claudecodeui/compare/v1.29.3...v1.29.4) (2026-04-16) + +### New Features + +* deleting from sidebar will now ask whether to remove all data as well ([e9c7a50](https://github.com/siteboon/claudecodeui/commit/e9c7a5041c31a6f7b2032f06abe19c52d3d4cd8c)) + +### Bug Fixes + +* pass pathToClaudeCodeExecutable to SDK when CLAUDE_CLI_PATH is set ([4c106a5](https://github.com/siteboon/claudecodeui/commit/4c106a5083d90989bbeedaefdbb68f5b3fa6fd58)), closes [#468](https://github.com/siteboon/claudecodeui/issues/468) + +### Refactoring + +* remove the sqlite3 dependency ([2895208](https://github.com/siteboon/claudecodeui/commit/289520814cf3ca36403056739ef22021f78c6033)) +* **server:** extract URL detection and color utils from index.js ([#657](https://github.com/siteboon/claudecodeui/issues/657)) ([63e996b](https://github.com/siteboon/claudecodeui/commit/63e996bb77cfa97b1f55f6bdccc50161a75a3eee)) + +### Maintenance + +* upgrade commit lint to 20.5.0 ([0948601](https://github.com/siteboon/claudecodeui/commit/09486016e67d97358c228ebc6eb4502ccb0012e4)) + ## [1.29.3](https://github.com/siteboon/claudecodeui/compare/v1.29.2...v1.29.3) (2026-04-15) ### Bug Fixes diff --git a/package-lock.json b/package-lock.json index 2bb0c748..043b2d8d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@cloudcli-ai/cloudcli", - "version": "1.29.3", + "version": "1.29.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@cloudcli-ai/cloudcli", - "version": "1.29.3", + "version": "1.29.4", "hasInstallScript": true, "license": "AGPL-3.0-or-later", "dependencies": { diff --git a/package.json b/package.json index 457d132b..12a1e36f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@cloudcli-ai/cloudcli", - "version": "1.29.3", + "version": "1.29.4", "description": "A web-based UI for Claude Code CLI", "type": "module", "main": "dist-server/server/index.js", @@ -117,7 +117,7 @@ "rehype-raw": "^7.0.0", "remark-gfm": "^4.0.0", "remark-math": "^6.0.0", -"tailwind-merge": "^3.3.1", + "tailwind-merge": "^3.3.1", "web-push": "^3.6.7", "ws": "^8.14.2" }, From 6a13e1773b145049ade512aa6e5cac21c2e5c4de Mon Sep 17 00:00:00 2001 From: simosmik Date: Thu, 16 Apr 2026 10:52:55 +0000 Subject: [PATCH 09/10] fix: update node-pty to latest version --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 043b2d8d..4043f34e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -49,7 +49,7 @@ "mime-types": "^3.0.1", "multer": "^2.0.1", "node-fetch": "^2.7.0", - "node-pty": "^1.1.0-beta34", + "node-pty": "^1.2.0-beta.12", "react": "^18.2.0", "react-dom": "^18.2.0", "react-dropzone": "^14.2.3", @@ -12245,9 +12245,9 @@ } }, "node_modules/node-pty": { - "version": "1.1.0", - "resolved": "https://registry.npmmirror.com/node-pty/-/node-pty-1.1.0.tgz", - "integrity": "sha512-20JqtutY6JPXTUnL0ij1uad7Qe1baT46lyolh2sSENDd4sTzKZ4nmAFkeAARDKwmlLjPx6XKRlwRUxwjOy+lUg==", + "version": "1.2.0-beta.12", + "resolved": "https://registry.npmjs.org/node-pty/-/node-pty-1.2.0-beta.12.tgz", + "integrity": "sha512-uExTCG/4VmSJa4+TjxFwPXv8BfacmfFEBL6JpxCMDghcwqzvD0yTcGmZ1fKOK6HY33tp0CelLblqTECJizc+Yw==", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 12a1e36f..722cc39e 100644 --- a/package.json +++ b/package.json @@ -104,7 +104,7 @@ "mime-types": "^3.0.1", "multer": "^2.0.1", "node-fetch": "^2.7.0", - "node-pty": "^1.1.0-beta34", + "node-pty": "^1.2.0-beta.12", "react": "^18.2.0", "react-dom": "^18.2.0", "react-dropzone": "^14.2.3", From 25b00b58de907142408da292b9640dcdf7746242 Mon Sep 17 00:00:00 2001 From: viper151 Date: Thu, 16 Apr 2026 11:02:31 +0000 Subject: [PATCH 10/10] chore(release): v1.29.5 --- CHANGELOG.md | 6 ++++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5890928b..1f81abd9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,12 @@ All notable changes to CloudCLI UI will be documented in this file. +## [1.29.5](https://github.com/siteboon/claudecodeui/compare/v1.29.4...v1.29.5) (2026-04-16) + +### Bug Fixes + +* update node-pty to latest version ([6a13e17](https://github.com/siteboon/claudecodeui/commit/6a13e1773b145049ade512aa6e5cac21c2e5c4de)) + ## [1.29.4](https://github.com/siteboon/claudecodeui/compare/v1.29.3...v1.29.4) (2026-04-16) ### New Features diff --git a/package-lock.json b/package-lock.json index 4043f34e..7ea371ab 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@cloudcli-ai/cloudcli", - "version": "1.29.4", + "version": "1.29.5", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@cloudcli-ai/cloudcli", - "version": "1.29.4", + "version": "1.29.5", "hasInstallScript": true, "license": "AGPL-3.0-or-later", "dependencies": { diff --git a/package.json b/package.json index 722cc39e..5be1fd99 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@cloudcli-ai/cloudcli", - "version": "1.29.4", + "version": "1.29.5", "description": "A web-based UI for Claude Code CLI", "type": "module", "main": "dist-server/server/index.js",