diff --git a/.gitignore b/.gitignore index 6ef17d3f..7d8bc51a 100755 --- a/.gitignore +++ b/.gitignore @@ -138,4 +138,4 @@ tasks/ !src/i18n/locales/de/tasks.json # Git worktrees -.worktrees/ \ No newline at end of file +.worktrees/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 149443d3..1f81abd9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,41 @@ 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 + +* 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 + +* **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/eslint.config.js b/eslint.config.js index 5645f2e8..3ef25d92 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -3,7 +3,9 @@ import tseslint from "typescript-eslint"; import react from "eslint-plugin-react"; import reactHooks from "eslint-plugin-react-hooks"; import reactRefresh from "eslint-plugin-react-refresh"; -import importX from "eslint-plugin-import-x"; +import { createNodeResolver, importX } from "eslint-plugin-import-x"; +import { createTypeScriptImportResolver } from "eslint-import-resolver-typescript"; +import boundaries from "eslint-plugin-boundaries"; import tailwindcss from "eslint-plugin-tailwindcss"; import unusedImports from "eslint-plugin-unused-imports"; import globals from "globals"; @@ -82,7 +84,7 @@ export default tseslint.config( "sibling", "index", ], - "newlines-between": "never", + "newlines-between": "always", }, ], @@ -98,5 +100,131 @@ export default tseslint.config( "no-control-regex": "off", "no-useless-escape": "off", }, + }, + { + files: ["server/**/*.{js,ts}"], // apply this block only to backend source files + ignores: ["server/**/*.d.ts"], // skip generated declaration files in backend linting + plugins: { + boundaries, // enforce backend architecture boundaries (module-to-module contracts) + "import-x": importX, // keep import hygiene rules (duplicates, unresolved paths, etc.) + "unused-imports": unusedImports, // remove dead imports/variables from backend files + }, + languageOptions: { + parser: tseslint.parser, // parse both JS and TS syntax in backend files + parserOptions: { + ecmaVersion: "latest", // support modern ECMAScript syntax in backend code + sourceType: "module", // treat backend files as ESM modules + }, + globals: { + ...globals.node, // expose Node.js globals such as process, Buffer, and __dirname equivalents + }, + }, + settings: { + "boundaries/include": ["server/**/*.{js,ts}"], // only analyze dependency boundaries inside backend files + "import/resolver": { + // boundaries resolves imports through eslint-module-utils, which reads the classic + // import/resolver setting instead of import-x/resolver-next. + typescript: { + project: ["server/tsconfig.json"], // resolve backend aliases using the canonical backend tsconfig + alwaysTryTypes: true, // keep normal TS package/type resolution working alongside aliases + }, + node: { + extensions: [".mjs", ".cjs", ".js", ".json", ".node", ".ts", ".tsx"], // preserve Node-style fallback resolution for plain files + }, + }, + "import-x/resolver-next": [ + // ESLint's import plugin does not read tsconfig path aliases on its own. + // This resolver teaches import-x how to understand the backend-only "@/*" + // mapping defined in server/tsconfig.json, which fixes false no-unresolved errors in editors. + createTypeScriptImportResolver({ + project: ["server/tsconfig.json"], // point the resolver at the canonical backend tsconfig instead of the frontend one + alwaysTryTypes: true, // keep standard TypeScript package resolution working while backend aliases are enabled + }), + // Keep Node-style resolution available for normal package imports and plain relative JS files. + // The TypeScript resolver handles aliases, while the Node resolver preserves the expected fallback behavior. + createNodeResolver({ + extensions: [".mjs", ".cjs", ".js", ".json", ".node", ".ts", ".tsx"], + }), + ], + "boundaries/elements": [ + { + type: "backend-shared-types", // shared backend type contract that modules may consume without creating runtime coupling + pattern: ["server/shared/types.{js,ts}"], // support the current shared types path + mode: "file", // treat the types file itself as the boundary element instead of the whole folder + }, + { + type: "backend-module", // logical element name used by boundaries rules below + pattern: "server/modules/*", // each direct folder in server/modules is treated as one module boundary + mode: "folder", // classify dependencies at folder-module level (not per individual file) + capture: ["moduleName"], // capture the module folder name for messages/debugging/template use + }, + ], + }, + rules: { + // --- Unused imports/vars (backend) --- + "unused-imports/no-unused-imports": "warn", // warn when imports are not used so they can be cleaned up + "unused-imports/no-unused-vars": "off", // keep backend signal focused on dead imports instead of local unused variables + + // --- Import hygiene (backend) --- + "import-x/no-duplicates": "warn", // prevent duplicate import lines from the same module + "import-x/order": [ + "warn", // keep backend import grouping/order consistent with the frontend config + { + groups: [ + "builtin", // Node built-ins such as fs, path, and url come first + "external", // third-party packages come after built-ins + "internal", // aliased internal imports such as @/... come next + "parent", // ../ imports come after aliased internal imports + "sibling", // ./foo imports come after parent imports + "index", // bare ./ imports stay last + ], + "newlines-between": "always", // require a blank line between import groups in backend files too + }, + ], + "import-x/no-unresolved": "error", // fail when an import path cannot be resolved + "import-x/no-useless-path-segments": "warn", // prefer cleaner paths (remove redundant ./ and ../ segments) + "import-x/no-absolute-path": "error", // disallow absolute filesystem imports in backend files + + // --- General safety/style (backend) --- + eqeqeq: ["warn", "always", { null: "ignore" }], // avoid accidental coercion while still allowing x == null checks + + // --- Architecture boundaries (backend modules) --- + "boundaries/dependencies": [ + "error", // treat architecture violations as lint errors + { + default: "allow", // allow normal imports unless a rule below explicitly disallows them + checkInternals: false, // do not apply these cross-module rules to imports inside the same module + rules: [ + { + from: { type: "backend-module" }, // modules may depend on the shared types contract only as erased type-only imports + to: { type: "backend-shared-types" }, + disallow: { + dependency: { kind: ["value", "typeof"] }, + }, // block runtime imports so shared types stay a compile-time contract instead of a hidden shared module + message: + "Backend modules may only use `import type` when importing from server/shared/types.ts (or server/types.ts).", + }, + { + to: { type: "backend-module" }, // when importing anything that belongs to another backend module + disallow: { to: { internalPath: "**" } }, // block all direct/deep imports into module internals by default + message: + "Cross-module imports must go through that module's barrel file (server/modules//index.ts or index.js).", // explicit error message for architecture violations + }, + { + to: { type: "backend-module" }, // same target scope as the disallow rule above + allow: { + to: { + internalPath: [ + "index", // allow extensionless barrel imports resolved as module root index + "index.{js,mjs,cjs,ts,tsx}", // allow explicit index.* barrel file imports + ], + }, + }, // re-allow only public module entry points (barrel files) + }, + ], + }, + ], + "boundaries/no-unknown": "error", // fail fast if boundaries cannot classify a dependency, which prevents silent rule bypasses + }, } ); diff --git a/package-lock.json b/package-lock.json index 21bee5b6..7ea371ab 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@cloudcli-ai/cloudcli", - "version": "1.29.2", + "version": "1.29.5", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@cloudcli-ai/cloudcli", - "version": "1.29.2", + "version": "1.29.5", "hasInstallScript": true, "license": "AGPL-3.0-or-later", "dependencies": { @@ -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", @@ -62,18 +62,16 @@ "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" }, "bin": { - "cloudcli": "server/cli.js" + "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", @@ -84,6 +82,8 @@ "autoprefixer": "^10.4.16", "concurrently": "^8.2.2", "eslint": "^9.39.3", + "eslint-import-resolver-typescript": "^4.4.4", + "eslint-plugin-boundaries": "^6.0.2", "eslint-plugin-import-x": "^4.16.1", "eslint-plugin-react": "^7.37.5", "eslint-plugin-react-hooks": "^7.0.1", @@ -98,6 +98,8 @@ "release-it": "^19.0.5", "sharp": "^0.34.2", "tailwindcss": "^3.4.0", + "tsc-alias": "^1.8.16", + "tsx": "^4.21.0", "typescript": "^5.9.3", "typescript-eslint": "^8.56.1", "vite": "^7.0.4" @@ -443,6 +445,23 @@ "node": ">=6.9.0" } }, + "node_modules/@boundaries/elements": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@boundaries/elements/-/elements-2.0.1.tgz", + "integrity": "sha512-sAWO3D8PFP6pBXdxxW93SQi/KQqqhE2AAHo3AgWfdtJXwO6bfK6/wUN81XnOZk0qRC6vHzUEKhjwVD9dtDWvxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-import-resolver-node": "0.3.9", + "eslint-module-utils": "2.12.1", + "handlebars": "4.7.9", + "is-core-module": "2.16.1", + "micromatch": "4.0.8" + }, + "engines": { + "node": ">=18.18" + } + }, "node_modules/@codemirror/autocomplete": { "version": "6.18.6", "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.18.6.tgz", @@ -633,17 +652,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" }, @@ -655,13 +674,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": { @@ -669,13 +688,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": { @@ -707,13 +726,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", @@ -735,13 +754,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": { @@ -749,13 +768,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": { @@ -776,32 +795,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", @@ -823,13 +842,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" }, @@ -838,15 +857,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" }, @@ -855,14 +874,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", @@ -873,16 +892,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" @@ -912,9 +931,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": { @@ -926,14 +945,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": { @@ -941,7 +960,7 @@ }, "peerDependencies": { "conventional-commits-filter": "^5.0.0", - "conventional-commits-parser": "^6.1.0" + "conventional-commits-parser": "^6.4.0" }, "peerDependenciesMeta": { "conventional-commits-filter": { @@ -1679,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", @@ -2850,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", @@ -3658,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", @@ -4613,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", @@ -4725,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", @@ -4815,6 +4754,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/array.prototype.findlast": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", @@ -5679,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" @@ -5903,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", @@ -5964,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": { @@ -6027,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", @@ -6157,9 +6089,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": { @@ -6262,13 +6194,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" @@ -6347,19 +6279,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", @@ -6603,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", @@ -6673,6 +6585,19 @@ "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", "license": "Apache-2.0" }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/dlv": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", @@ -6823,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" @@ -6846,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": { @@ -7217,6 +7142,112 @@ } } }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", + "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^3.2.7", + "is-core-module": "^2.13.0", + "resolve": "^1.22.4" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-import-resolver-typescript": { + "version": "4.4.4", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-4.4.4.tgz", + "integrity": "sha512-1iM2zeBvrYmUNTj2vSC/90JTHDth+dfOfiNKkxApWRsTJYNrc8rOdxxIf5vazX+BiAXTeOT0UvWpGI/7qIWQOw==", + "dev": true, + "license": "ISC", + "dependencies": { + "debug": "^4.4.1", + "eslint-import-context": "^0.1.8", + "get-tsconfig": "^4.10.1", + "is-bun-module": "^2.0.0", + "stable-hash-x": "^0.2.0", + "tinyglobby": "^0.2.14", + "unrs-resolver": "^1.7.11" + }, + "engines": { + "node": "^16.17.0 || >=18.6.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-import-resolver-typescript" + }, + "peerDependencies": { + "eslint": "*", + "eslint-plugin-import": "*", + "eslint-plugin-import-x": "*" + }, + "peerDependenciesMeta": { + "eslint-plugin-import": { + "optional": true + }, + "eslint-plugin-import-x": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.1.tgz", + "integrity": "sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^3.2.7" + }, + "engines": { + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-boundaries": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-boundaries/-/eslint-plugin-boundaries-6.0.2.tgz", + "integrity": "sha512-wSHgiYeMEbziP91lH0UQ9oslgF2djG1x+LV9z/qO19ggMKZaCB8pKIGePHAY91eLF4EAgpsxQk8MRSFGRPfPzw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@boundaries/elements": "2.0.1", + "chalk": "4.1.2", + "eslint-import-resolver-node": "0.3.9", + "eslint-module-utils": "2.12.1", + "handlebars": "4.7.9", + "micromatch": "4.0.8" + }, + "engines": { + "node": ">=18.18" + }, + "peerDependencies": { + "eslint": ">=6.0.0" + } + }, "node_modules/eslint-plugin-import-x": { "version": "4.16.1", "resolved": "https://registry.npmjs.org/eslint-plugin-import-x/-/eslint-plugin-import-x-4.16.1.tgz", @@ -8046,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" @@ -8058,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" @@ -8070,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", @@ -8142,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", @@ -8373,35 +8327,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": { @@ -8519,6 +8458,27 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -8535,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": { @@ -8554,9 +8514,9 @@ } }, "node_modules/handlebars": { - "version": "4.7.8", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", - "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", + "version": "4.7.9", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.9.tgz", + "integrity": "sha512-4E71E0rpOaQuJR2A3xDZ+GM1HyWYv1clR58tC8emQNeQe3RH7MAzSbat+V0wG78LQBo6m6bzSG/L4pBuCsgnUQ==", "dev": true, "license": "MIT", "dependencies": { @@ -8655,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", @@ -8980,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": { @@ -9036,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", @@ -9218,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" @@ -9228,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", @@ -9317,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" @@ -9446,6 +9370,29 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-bun-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-bun-module/-/is-bun-module-2.0.0.tgz", + "integrity": "sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.7.1" + } + }, + "node_modules/is-bun-module/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/is-callable": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", @@ -9656,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": { @@ -11848,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" @@ -11861,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" @@ -11874,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" @@ -11894,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" @@ -11907,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" @@ -11927,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" @@ -11940,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", @@ -11960,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" @@ -11972,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": { @@ -12026,6 +11976,20 @@ "node": "^18.17.0 || >=20.5.0" } }, + "node_modules/mylas": { + "version": "2.1.14", + "resolved": "https://registry.npmjs.org/mylas/-/mylas-2.1.14.tgz", + "integrity": "sha512-BzQguy9W9NJgoVn2mRWzbFrFWWztGCcng2QI9+41frfk+Athwgx3qhqhvStz7ExeUUu7Kzw427sNzHpEZNINog==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/raouldeheer" + } + }, "node_modules/mz": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", @@ -12281,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": { @@ -12395,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", @@ -12753,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" @@ -12936,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", @@ -12989,6 +12926,16 @@ "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", "license": "MIT" }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/pathe": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", @@ -13051,6 +12998,19 @@ "pathe": "^2.0.3" } }, + "node_modules/plimit-lit": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/plimit-lit/-/plimit-lit-1.6.1.tgz", + "integrity": "sha512-B7+VDyb8Tl6oMJT9oSO2CW8XC/T4UcJGrwOVoNGwOQsQYhlpfajmrMj5xeejqaASq3V/EqThyOeATEOMuSEXiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "queue-lit": "^1.5.1" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/possible-typed-array-names": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", @@ -13278,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", @@ -13412,6 +13365,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/queue-lit": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/queue-lit/-/queue-lit-1.5.2.tgz", + "integrity": "sha512-tLc36IOPeMAubu8BkW8YDBV+WyIgKlYU7zUNs0J5Vk9skSZ4JfGlPOqplP0aHdfv7HL0B2Pg6nwiq60Qc6M2Hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -14184,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" @@ -14207,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", @@ -14556,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", @@ -15173,6 +15066,16 @@ "is-arrayish": "^0.3.1" } }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/slice-ansi": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-8.0.0.tgz", @@ -15223,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", @@ -15234,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", @@ -15331,411 +15234,12 @@ "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", "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", @@ -16247,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", @@ -16292,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" @@ -16301,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" @@ -16310,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" @@ -16322,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": { @@ -16479,12 +15988,599 @@ "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", "license": "Apache-2.0" }, + "node_modules/tsc-alias": { + "version": "1.8.16", + "resolved": "https://registry.npmjs.org/tsc-alias/-/tsc-alias-1.8.16.tgz", + "integrity": "sha512-QjCyu55NFyRSBAl6+MTFwplpFcnm2Pq01rR/uxfqJoLMm6X3O14KEGtaSDZpJYaE1bJBGDjD0eSuiIWPe2T58g==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^3.5.3", + "commander": "^9.0.0", + "get-tsconfig": "^4.10.0", + "globby": "^11.0.4", + "mylas": "^2.1.9", + "normalize-path": "^3.0.0", + "plimit-lit": "^1.2.6" + }, + "bin": { + "tsc-alias": "dist/bin/index.js" + }, + "engines": { + "node": ">=16.20.2" + } + }, + "node_modules/tsc-alias/node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/tsc-alias/node_modules/commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || >=14" + } + }, + "node_modules/tsc-alias/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/tsc-alias/node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "license": "0BSD" }, + "node_modules/tsx": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", + "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.27.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/tsx/node_modules/@esbuild/aix-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", + "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz", + "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz", + "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz", + "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/darwin-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz", + "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/darwin-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz", + "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz", + "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/freebsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz", + "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz", + "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz", + "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz", + "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-loong64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz", + "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-mips64el": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz", + "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz", + "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-riscv64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz", + "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-s390x": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz", + "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz", + "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz", + "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/netbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz", + "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz", + "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/openbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz", + "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz", + "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/sunos-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz", + "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz", + "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz", + "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz", + "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/esbuild": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz", + "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.7", + "@esbuild/android-arm": "0.27.7", + "@esbuild/android-arm64": "0.27.7", + "@esbuild/android-x64": "0.27.7", + "@esbuild/darwin-arm64": "0.27.7", + "@esbuild/darwin-x64": "0.27.7", + "@esbuild/freebsd-arm64": "0.27.7", + "@esbuild/freebsd-x64": "0.27.7", + "@esbuild/linux-arm": "0.27.7", + "@esbuild/linux-arm64": "0.27.7", + "@esbuild/linux-ia32": "0.27.7", + "@esbuild/linux-loong64": "0.27.7", + "@esbuild/linux-mips64el": "0.27.7", + "@esbuild/linux-ppc64": "0.27.7", + "@esbuild/linux-riscv64": "0.27.7", + "@esbuild/linux-s390x": "0.27.7", + "@esbuild/linux-x64": "0.27.7", + "@esbuild/netbsd-arm64": "0.27.7", + "@esbuild/netbsd-x64": "0.27.7", + "@esbuild/openbsd-arm64": "0.27.7", + "@esbuild/openbsd-x64": "0.27.7", + "@esbuild/openharmony-arm64": "0.27.7", + "@esbuild/sunos-x64": "0.27.7", + "@esbuild/win32-arm64": "0.27.7", + "@esbuild/win32-ia32": "0.27.7", + "@esbuild/win32-x64": "0.27.7" + } + }, "node_modules/tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", @@ -17365,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 2a80d5a3..5be1fd99 100644 --- a/package.json +++ b/package.json @@ -1,16 +1,17 @@ { "name": "@cloudcli-ai/cloudcli", - "version": "1.29.2", + "version": "1.29.5", "description": "A web-based UI for Claude Code CLI", "type": "module", - "main": "server/index.js", + "main": "dist-server/server/index.js", "bin": { - "cloudcli": "server/cli.js" + "cloudcli": "dist-server/server/cli.js" }, "files": [ "server/", "shared/", "dist/", + "dist-server/", "scripts/", "README.md" ], @@ -23,14 +24,19 @@ "url": "https://github.com/siteboon/claudecodeui/issues" }, "scripts": { - "dev": "concurrently --kill-others \"npm run server\" \"npm run client\"", - "server": "node server/index.js", + "dev": "concurrently --kill-others \"npm run server:dev\" \"npm run client\"", + "server": "node dist-server/server/index.js", + "server:dev": "tsx --tsconfig server/tsconfig.json server/index.js", + "server:dev-watch": "tsx watch --tsconfig server/tsconfig.json server/index.js", "client": "vite", - "build": "vite build", + "build": "npm run build:client && npm run build:server", + "build:client": "vite build", + "prebuild:server": "node -e \"require('node:fs').rmSync('dist-server', { recursive: true, force: true })\"", + "build:server": "tsc -p server/tsconfig.json && tsc-alias -p server/tsconfig.json", "preview": "vite preview", - "typecheck": "tsc --noEmit -p tsconfig.json", - "lint": "eslint src/", - "lint:fix": "eslint src/ --fix", + "typecheck": "tsc --noEmit -p tsconfig.json && tsc --noEmit -p server/tsconfig.json", + "lint": "eslint src/ server/", + "lint:fix": "eslint src/ server/ --fix", "start": "npm run build && npm run server", "release": "./release.sh", "prepublishOnly": "npm run build", @@ -98,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", @@ -111,15 +117,13 @@ "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" }, "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", @@ -130,6 +134,8 @@ "autoprefixer": "^10.4.16", "concurrently": "^8.2.2", "eslint": "^9.39.3", + "eslint-import-resolver-typescript": "^4.4.4", + "eslint-plugin-boundaries": "^6.0.2", "eslint-plugin-import-x": "^4.16.1", "eslint-plugin-react": "^7.37.5", "eslint-plugin-react-hooks": "^7.0.1", @@ -144,11 +150,14 @@ "release-it": "^19.0.5", "sharp": "^0.34.2", "tailwindcss": "^3.4.0", + "tsc-alias": "^1.8.16", + "tsx": "^4.21.0", "typescript": "^5.9.3", "typescript-eslint": "^8.56.1", "vite": "^7.0.4" }, "lint-staged": { - "src/**/*.{ts,tsx,js,jsx}": "eslint" + "src/**/*.{ts,tsx,js,jsx}": "eslint", + "server/**/*.{js,ts}": "eslint" } } diff --git a/redirect-package/bin.js b/redirect-package/bin.js index 2f31c0c6..fc33eef1 100644 --- a/redirect-package/bin.js +++ b/redirect-package/bin.js @@ -1,2 +1,2 @@ #!/usr/bin/env node -import('@cloudcli-ai/cloudcli/server/cli.js'); +import('@cloudcli-ai/cloudcli/dist-server/server/cli.js'); diff --git a/server/claude-sdk.js b/server/claude-sdk.js index a4f33cf6..557da267 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/index.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') { @@ -148,6 +149,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; @@ -701,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', @@ -710,8 +721,6 @@ async function queryClaudeSDK(command, options = {}, ws) { sessionName: sessionSummary, error }); - - throw error; } } diff --git a/server/cli.js b/server/cli.js index 3c0d1d4f..7551afc4 100755 --- a/server/cli.js +++ b/server/cli.js @@ -16,11 +16,12 @@ import fs from 'fs'; import path from 'path'; import os from 'os'; -import { fileURLToPath } from 'url'; -import { dirname } from 'path'; +import { findAppRoot, getModuleDir } from './utils/runtime-paths.js'; -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); +const __dirname = getModuleDir(import.meta.url); +// The CLI is compiled into dist-server/server, but it still needs to read the top-level +// package.json and .env file. Resolving the app root once keeps those lookups stable. +const APP_ROOT = findAppRoot(__dirname); // ANSI color codes for terminal output const colors = { @@ -50,13 +51,16 @@ const c = { }; // Load package.json for version info -const packageJsonPath = path.join(__dirname, '../package.json'); +const packageJsonPath = path.join(APP_ROOT, 'package.json'); const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')); +// Match the runtime fallback in load-env.js so "cloudcli status" reports the same default +// database location that the backend will actually use when no DATABASE_PATH is configured. +const DEFAULT_DATABASE_PATH = path.join(os.homedir(), '.cloudcli', 'auth.db'); // Load environment variables from .env file if it exists function loadEnvFile() { try { - const envPath = path.join(__dirname, '../.env'); + const envPath = path.join(APP_ROOT, '.env'); const envFile = fs.readFileSync(envPath, 'utf8'); envFile.split('\n').forEach(line => { const trimmedLine = line.trim(); @@ -75,12 +79,12 @@ function loadEnvFile() { // Get the database path (same logic as db.js) function getDatabasePath() { loadEnvFile(); - return process.env.DATABASE_PATH || path.join(__dirname, 'database', 'auth.db'); + return process.env.DATABASE_PATH || DEFAULT_DATABASE_PATH; } // Get the installation directory function getInstallDir() { - return path.join(__dirname, '..'); + return APP_ROOT; } // Show status command @@ -124,7 +128,7 @@ function showStatus() { console.log(` Status: ${projectsExists ? c.ok('[OK] Exists') : c.warn('[WARN] Not found')}`); // Config file location - const envFilePath = path.join(__dirname, '../.env'); + const envFilePath = path.join(APP_ROOT, '.env'); const envExists = fs.existsSync(envFilePath); console.log(`\n${c.info('[INFO]')} Configuration File:`); console.log(` ${c.dim(envFilePath)}`); diff --git a/server/cursor-cli.js b/server/cursor-cli.js index c5107d1a..5bd1ae61 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/index.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/database/db.js b/server/database/db.js index 9ab0ad78..f43c894a 100644 --- a/server/database/db.js +++ b/server/database/db.js @@ -2,11 +2,21 @@ import Database from 'better-sqlite3'; import path from 'path'; import fs from 'fs'; import crypto from 'crypto'; -import { fileURLToPath } from 'url'; -import { dirname } from 'path'; +import { findAppRoot, getModuleDir } from '../utils/runtime-paths.js'; +import { + APP_CONFIG_TABLE_SQL, + USER_NOTIFICATION_PREFERENCES_TABLE_SQL, + VAPID_KEYS_TABLE_SQL, + PUSH_SUBSCRIPTIONS_TABLE_SQL, + SESSION_NAMES_TABLE_SQL, + SESSION_NAMES_LOOKUP_INDEX_SQL, + DATABASE_SCHEMA_SQL +} from './schema.js'; -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); +const __dirname = getModuleDir(import.meta.url); +// The compiled backend lives under dist-server/server/database, but the install root we log +// should still point at the project/app root. Resolving it here avoids build-layout drift. +const APP_ROOT = findAppRoot(__dirname); // ANSI color codes for terminal output const colors = { @@ -24,7 +34,6 @@ const c = { // Use DATABASE_PATH environment variable if set, otherwise use default location const DB_PATH = process.env.DATABASE_PATH || path.join(__dirname, 'auth.db'); -const INIT_SQL_PATH = path.join(__dirname, 'init.sql'); // Ensure database directory exists if custom path is provided if (process.env.DATABASE_PATH) { @@ -62,14 +71,10 @@ const db = new Database(DB_PATH); // app_config must exist before any other module imports (auth.js reads the JWT secret at load time). // runMigrations() also creates this table, but it runs too late for existing installations // where auth.js is imported before initializeDatabase() is called. -db.exec(`CREATE TABLE IF NOT EXISTS app_config ( - key TEXT PRIMARY KEY, - value TEXT NOT NULL, - created_at DATETIME DEFAULT CURRENT_TIMESTAMP -)`); +db.exec(APP_CONFIG_TABLE_SQL); // Show app installation path prominently -const appInstallPath = path.join(__dirname, '../..'); +const appInstallPath = APP_ROOT; console.log(''); console.log(c.dim('═'.repeat(60))); console.log(`${c.info('[INFO]')} App Installation: ${c.bright(appInstallPath)}`); @@ -100,53 +105,12 @@ const runMigrations = () => { db.exec('ALTER TABLE users ADD COLUMN has_completed_onboarding BOOLEAN DEFAULT 0'); } - db.exec(` - CREATE TABLE IF NOT EXISTS user_notification_preferences ( - user_id INTEGER PRIMARY KEY, - preferences_json TEXT NOT NULL, - updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, - FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE - ) - `); - - db.exec(` - CREATE TABLE IF NOT EXISTS vapid_keys ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - public_key TEXT NOT NULL, - private_key TEXT NOT NULL, - created_at DATETIME DEFAULT CURRENT_TIMESTAMP - ) - `); - - db.exec(` - CREATE TABLE IF NOT EXISTS push_subscriptions ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - user_id INTEGER NOT NULL, - endpoint TEXT NOT NULL UNIQUE, - keys_p256dh TEXT NOT NULL, - keys_auth TEXT NOT NULL, - created_at DATETIME DEFAULT CURRENT_TIMESTAMP, - FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE - ) - `); - // Create app_config table if it doesn't exist (for existing installations) - db.exec(`CREATE TABLE IF NOT EXISTS app_config ( - key TEXT PRIMARY KEY, - value TEXT NOT NULL, - created_at DATETIME DEFAULT CURRENT_TIMESTAMP - )`); - - // Create session_names table if it doesn't exist (for existing installations) - db.exec(`CREATE TABLE IF NOT EXISTS session_names ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - session_id TEXT NOT NULL, - provider TEXT NOT NULL DEFAULT 'claude', - custom_name TEXT NOT NULL, - created_at DATETIME DEFAULT CURRENT_TIMESTAMP, - updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, - UNIQUE(session_id, provider) - )`); - db.exec('CREATE INDEX IF NOT EXISTS idx_session_names_lookup ON session_names(session_id, provider)'); + db.exec(USER_NOTIFICATION_PREFERENCES_TABLE_SQL); + db.exec(VAPID_KEYS_TABLE_SQL); + db.exec(PUSH_SUBSCRIPTIONS_TABLE_SQL); + db.exec(APP_CONFIG_TABLE_SQL); + db.exec(SESSION_NAMES_TABLE_SQL); + db.exec(SESSION_NAMES_LOOKUP_INDEX_SQL); console.log('Database migrations completed successfully'); } catch (error) { @@ -158,8 +122,7 @@ const runMigrations = () => { // Initialize database with schema const initializeDatabase = async () => { try { - const initSQL = fs.readFileSync(INIT_SQL_PATH, 'utf8'); - db.exec(initSQL); + db.exec(DATABASE_SCHEMA_SQL); console.log('Database initialized successfully'); runMigrations(); } catch (error) { diff --git a/server/database/init.sql b/server/database/init.sql deleted file mode 100644 index 98351516..00000000 --- a/server/database/init.sql +++ /dev/null @@ -1,99 +0,0 @@ --- Initialize authentication database -PRAGMA foreign_keys = ON; - --- Users table (single user system) -CREATE TABLE IF NOT EXISTS users ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - username TEXT UNIQUE NOT NULL, - password_hash TEXT NOT NULL, - created_at DATETIME DEFAULT CURRENT_TIMESTAMP, - last_login DATETIME, - is_active BOOLEAN DEFAULT 1, - git_name TEXT, - git_email TEXT, - has_completed_onboarding BOOLEAN DEFAULT 0 -); - --- Indexes for performance -CREATE INDEX IF NOT EXISTS idx_users_username ON users(username); -CREATE INDEX IF NOT EXISTS idx_users_active ON users(is_active); - --- API Keys table for external API access -CREATE TABLE IF NOT EXISTS api_keys ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - user_id INTEGER NOT NULL, - key_name TEXT NOT NULL, - api_key TEXT UNIQUE NOT NULL, - created_at DATETIME DEFAULT CURRENT_TIMESTAMP, - last_used DATETIME, - is_active BOOLEAN DEFAULT 1, - FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE -); - -CREATE INDEX IF NOT EXISTS idx_api_keys_key ON api_keys(api_key); -CREATE INDEX IF NOT EXISTS idx_api_keys_user_id ON api_keys(user_id); -CREATE INDEX IF NOT EXISTS idx_api_keys_active ON api_keys(is_active); - --- User credentials table for storing various tokens/credentials (GitHub, GitLab, etc.) -CREATE TABLE IF NOT EXISTS user_credentials ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - user_id INTEGER NOT NULL, - credential_name TEXT NOT NULL, - credential_type TEXT NOT NULL, -- 'github_token', 'gitlab_token', 'bitbucket_token', etc. - credential_value TEXT NOT NULL, - description TEXT, - created_at DATETIME DEFAULT CURRENT_TIMESTAMP, - is_active BOOLEAN DEFAULT 1, - FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE -); - -CREATE INDEX IF NOT EXISTS idx_user_credentials_user_id ON user_credentials(user_id); -CREATE INDEX IF NOT EXISTS idx_user_credentials_type ON user_credentials(credential_type); -CREATE INDEX IF NOT EXISTS idx_user_credentials_active ON user_credentials(is_active); - --- User notification preferences (backend-owned, provider-agnostic) -CREATE TABLE IF NOT EXISTS user_notification_preferences ( - user_id INTEGER PRIMARY KEY, - preferences_json TEXT NOT NULL, - updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, - FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE -); - --- VAPID key pair for Web Push notifications -CREATE TABLE IF NOT EXISTS vapid_keys ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - public_key TEXT NOT NULL, - private_key TEXT NOT NULL, - created_at DATETIME DEFAULT CURRENT_TIMESTAMP -); - --- Browser push subscriptions -CREATE TABLE IF NOT EXISTS push_subscriptions ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - user_id INTEGER NOT NULL, - endpoint TEXT NOT NULL UNIQUE, - keys_p256dh TEXT NOT NULL, - keys_auth TEXT NOT NULL, - created_at DATETIME DEFAULT CURRENT_TIMESTAMP, - FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE -); - --- Session custom names (provider-agnostic display name overrides) -CREATE TABLE IF NOT EXISTS session_names ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - session_id TEXT NOT NULL, - provider TEXT NOT NULL DEFAULT 'claude', - custom_name TEXT NOT NULL, - created_at DATETIME DEFAULT CURRENT_TIMESTAMP, - updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, - UNIQUE(session_id, provider) -); - -CREATE INDEX IF NOT EXISTS idx_session_names_lookup ON session_names(session_id, provider); - --- App configuration table (auto-generated secrets, settings, etc.) -CREATE TABLE IF NOT EXISTS app_config ( - key TEXT PRIMARY KEY, - value TEXT NOT NULL, - created_at DATETIME DEFAULT CURRENT_TIMESTAMP -); diff --git a/server/database/schema.js b/server/database/schema.js new file mode 100644 index 00000000..21c1b8eb --- /dev/null +++ b/server/database/schema.js @@ -0,0 +1,102 @@ +export const APP_CONFIG_TABLE_SQL = `CREATE TABLE IF NOT EXISTS app_config ( + key TEXT PRIMARY KEY, + value TEXT NOT NULL, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP +);`; + +export const USER_NOTIFICATION_PREFERENCES_TABLE_SQL = `CREATE TABLE IF NOT EXISTS user_notification_preferences ( + user_id INTEGER PRIMARY KEY, + preferences_json TEXT NOT NULL, + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE +);`; + +export const VAPID_KEYS_TABLE_SQL = `CREATE TABLE IF NOT EXISTS vapid_keys ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + public_key TEXT NOT NULL, + private_key TEXT NOT NULL, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP +);`; + +export const PUSH_SUBSCRIPTIONS_TABLE_SQL = `CREATE TABLE IF NOT EXISTS push_subscriptions ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER NOT NULL, + endpoint TEXT NOT NULL UNIQUE, + keys_p256dh TEXT NOT NULL, + keys_auth TEXT NOT NULL, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE +);`; + +export const SESSION_NAMES_TABLE_SQL = `CREATE TABLE IF NOT EXISTS session_names ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + session_id TEXT NOT NULL, + provider TEXT NOT NULL DEFAULT 'claude', + custom_name TEXT NOT NULL, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, + UNIQUE(session_id, provider) +);`; + +export const SESSION_NAMES_LOOKUP_INDEX_SQL = `CREATE INDEX IF NOT EXISTS idx_session_names_lookup ON session_names(session_id, provider);`; + +export const DATABASE_SCHEMA_SQL = `PRAGMA foreign_keys = ON; + +CREATE TABLE IF NOT EXISTS users ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + username TEXT UNIQUE NOT NULL, + password_hash TEXT NOT NULL, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + last_login DATETIME, + is_active BOOLEAN DEFAULT 1, + git_name TEXT, + git_email TEXT, + has_completed_onboarding BOOLEAN DEFAULT 0 +); + +CREATE INDEX IF NOT EXISTS idx_users_username ON users(username); +CREATE INDEX IF NOT EXISTS idx_users_active ON users(is_active); + +CREATE TABLE IF NOT EXISTS api_keys ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER NOT NULL, + key_name TEXT NOT NULL, + api_key TEXT UNIQUE NOT NULL, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + last_used DATETIME, + is_active BOOLEAN DEFAULT 1, + FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE +); + +CREATE INDEX IF NOT EXISTS idx_api_keys_key ON api_keys(api_key); +CREATE INDEX IF NOT EXISTS idx_api_keys_user_id ON api_keys(user_id); +CREATE INDEX IF NOT EXISTS idx_api_keys_active ON api_keys(is_active); + +CREATE TABLE IF NOT EXISTS user_credentials ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER NOT NULL, + credential_name TEXT NOT NULL, + credential_type TEXT NOT NULL, + credential_value TEXT NOT NULL, + description TEXT, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + is_active BOOLEAN DEFAULT 1, + FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE +); + +CREATE INDEX IF NOT EXISTS idx_user_credentials_user_id ON user_credentials(user_id); +CREATE INDEX IF NOT EXISTS idx_user_credentials_type ON user_credentials(credential_type); +CREATE INDEX IF NOT EXISTS idx_user_credentials_active ON user_credentials(is_active); + +${USER_NOTIFICATION_PREFERENCES_TABLE_SQL} + +${VAPID_KEYS_TABLE_SQL} + +${PUSH_SUBSCRIPTIONS_TABLE_SQL} + +${SESSION_NAMES_TABLE_SQL} + +${SESSION_NAMES_LOOKUP_INDEX_SQL} + +${APP_CONFIG_TABLE_SQL} +`; 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/index.js b/server/index.js index fa9ecc15..86ded977 100755 --- a/server/index.js +++ b/server/index.js @@ -3,13 +3,13 @@ import './load-env.js'; import fs from 'fs'; import path from 'path'; -import { fileURLToPath } from 'url'; -import { dirname } from 'path'; +import { findAppRoot, getModuleDir } from './utils/runtime-paths.js'; -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); - -const installMode = fs.existsSync(path.join(__dirname, '..', '.git')) ? 'git' : 'npm'; +const __dirname = getModuleDir(import.meta.url); +// The server source runs from /server, while the compiled output runs from /dist-server/server. +// Resolving the app root once keeps every repo-level lookup below aligned across both layouts. +const APP_ROOT = findAppRoot(__dirname); +const installMode = fs.existsSync(path.join(APP_ROOT, '.git')) ? 'git' : 'npm'; import { c } from './utils/colors.js'; @@ -326,11 +326,11 @@ app.use('/api/sessions', authenticateToken, messagesRoutes); app.use('/api/agent', agentRoutes); // Serve public files (like api-docs.html) -app.use(express.static(path.join(__dirname, '../public'))); +app.use(express.static(path.join(APP_ROOT, 'public'))); // Static files served after API routes // Add cache control: HTML files should not be cached, but assets can be cached -app.use(express.static(path.join(__dirname, '../dist'), { +app.use(express.static(path.join(APP_ROOT, 'dist'), { setHeaders: (res, filePath) => { if (filePath.endsWith('.html')) { // Prevent HTML caching to avoid service worker issues after builds @@ -352,7 +352,7 @@ app.use(express.static(path.join(__dirname, '../dist'), { app.post('/api/system/update', authenticateToken, async (req, res) => { try { // Get the project root directory (parent of server directory) - const projectRoot = path.join(__dirname, '..'); + const projectRoot = APP_ROOT; console.log('Starting system update from directory:', projectRoot); @@ -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 }); @@ -2194,7 +2197,7 @@ app.get('*', (req, res) => { // Only serve index.html for HTML routes, not for static assets // Static assets should already be handled by express.static middleware above - const indexPath = path.join(__dirname, '../dist/index.html'); + const indexPath = path.join(APP_ROOT, 'dist', 'index.html'); // Check if dist/index.html exists (production build available) if (fs.existsSync(indexPath)) { @@ -2309,7 +2312,7 @@ async function startServer() { configureWebPush(); // Check if running in production mode (dist folder exists) - const distIndexPath = path.join(__dirname, '../dist/index.html'); + const distIndexPath = path.join(APP_ROOT, 'dist', 'index.html'); const isProduction = fs.existsSync(distIndexPath); // Log Claude implementation mode @@ -2323,7 +2326,7 @@ async function startServer() { console.log(`${c.info('[INFO]')} To run in development mode with hot-module replacement, go to http://${DISPLAY_HOST}:${VITE_PORT}`); server.listen(SERVER_PORT, HOST, async () => { - const appInstallPath = path.join(__dirname, '..'); + const appInstallPath = APP_ROOT; console.log(''); console.log(c.dim('═'.repeat(63))); diff --git a/server/load-env.js b/server/load-env.js index ad9ccbb7..2c889534 100644 --- a/server/load-env.js +++ b/server/load-env.js @@ -2,14 +2,15 @@ import fs from 'fs'; import os from 'os'; import path from 'path'; -import { fileURLToPath } from 'url'; -import { dirname } from 'path'; +import { findAppRoot, getModuleDir } from './utils/runtime-paths.js'; -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); +const __dirname = getModuleDir(import.meta.url); +// Resolve the repo/app root via the nearest /server folder so this file keeps finding the +// same top-level .env file from both /server/load-env.js and /dist-server/server/load-env.js. +const APP_ROOT = findAppRoot(__dirname); try { - const envPath = path.join(__dirname, '../.env'); + const envPath = path.join(APP_ROOT, '.env'); const envFile = fs.readFileSync(envPath, 'utf8'); envFile.split('\n').forEach(line => { const trimmedLine = line.trim(); @@ -24,6 +25,10 @@ try { console.log('No .env file found or error reading it:', e.message); } +// Keep the default database in a stable user-level location so rebuilding dist-server +// never changes where the backend stores auth.db when DATABASE_PATH is not set explicitly. +const DEFAULT_DATABASE_PATH = path.join(os.homedir(), '.cloudcli', 'auth.db'); + if (!process.env.DATABASE_PATH) { - process.env.DATABASE_PATH = path.join(os.homedir(), '.cloudcli', 'auth.db'); + process.env.DATABASE_PATH = DEFAULT_DATABASE_PATH; } diff --git a/server/openai-codex.js b/server/openai-codex.js index c08f90d1..b053d4de 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/index.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/projects.js b/server/projects.js index d8ccaeb7..67184af0 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'; @@ -1164,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 { @@ -1175,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; } } @@ -1305,16 +1307,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 +1332,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/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/adapter.js b/server/providers/cursor/adapter.js index 582daacb..51772d40 100644 --- a/server/providers/cursor/adapter.js +++ b/server/providers/cursor/adapter.js @@ -11,6 +11,125 @@ import { createNormalizedMessage } from '../types.js'; const PROVIDER = 'cursor'; /** +<<<<<<< HEAD +======= + * Load raw blobs from Cursor's SQLite store.db, parse the DAG structure, + * and return sorted message blobs in chronological order. + * @param {string} sessionId + * @param {string} projectPath - Absolute project path (used to compute cwdId hash) + * @returns {Promise>} + */ +async function loadCursorBlobs(sessionId, projectPath) { + // 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 = new Database(storeDbPath, { readonly: true, fileMustExist: true }); + + try { + const allBlobs = db.prepare('SELECT rowid, id, data FROM blobs').all(); + + const blobMap = new Map(); + const parentRefs = new Map(); + const childRefs = new Map(); + const jsonBlobs = []; + + for (const blob of allBlobs) { + blobMap.set(blob.id, blob); + + if (blob.data && blob.data[0] === 0x7B) { + try { + const parsed = JSON.parse(blob.data.toString('utf8')); + jsonBlobs.push({ ...blob, parsed }); + } catch { + // skip unparseable blobs + } + } else if (blob.data) { + const parents = []; + let i = 0; + while (i < blob.data.length - 33) { + if (blob.data[i] === 0x0A && blob.data[i + 1] === 0x20) { + const parentHash = blob.data.slice(i + 2, i + 34).toString('hex'); + if (blobMap.has(parentHash)) { + parents.push(parentHash); + } + i += 34; + } else { + i++; + } + } + if (parents.length > 0) { + parentRefs.set(blob.id, parents); + for (const parentId of parents) { + if (!childRefs.has(parentId)) childRefs.set(parentId, []); + childRefs.get(parentId).push(blob.id); + } + } + } + } + + // Topological sort (DFS) + const visited = new Set(); + const sorted = []; + function visit(nodeId) { + if (visited.has(nodeId)) return; + visited.add(nodeId); + for (const pid of (parentRefs.get(nodeId) || [])) visit(pid); + const b = blobMap.get(nodeId); + if (b) sorted.push(b); + } + for (const blob of allBlobs) { + if (!parentRefs.has(blob.id)) visit(blob.id); + } + for (const blob of allBlobs) visit(blob.id); + + // Order JSON blobs by DAG appearance + const messageOrder = new Map(); + let orderIndex = 0; + for (const blob of sorted) { + if (blob.data && blob.data[0] !== 0x7B) { + for (const jb of jsonBlobs) { + try { + const idBytes = Buffer.from(jb.id, 'hex'); + if (blob.data.includes(idBytes) && !messageOrder.has(jb.id)) { + messageOrder.set(jb.id, orderIndex++); + } + } catch { /* skip */ } + } + } + } + + const sortedJsonBlobs = jsonBlobs.sort((a, b) => { + const oa = messageOrder.get(a.id) ?? Number.MAX_SAFE_INTEGER; + const ob = messageOrder.get(b.id) ?? Number.MAX_SAFE_INTEGER; + return oa !== ob ? oa - ob : a.rowid - b.rowid; + }); + + const messages = []; + for (let idx = 0; idx < sortedJsonBlobs.length; idx++) { + const blob = sortedJsonBlobs[idx]; + const parsed = blob.parsed; + if (!parsed) continue; + const role = parsed?.role || parsed?.message?.role; + if (role === 'system') continue; + messages.push({ + id: blob.id, + sequence: idx + 1, + rowid: blob.rowid, + content: parsed, + }); + } + + return messages; + } finally { + db.close(); + } +} + +/** +>>>>>>> refactor/split-server-index * Normalize a realtime NDJSON event from Cursor CLI into NormalizedMessage(s). * History uses normalizeCursorBlobs (SQLite DAG), this handles streaming NDJSON. * @param {object|string} raw - A parsed NDJSON event or a raw text line diff --git a/server/providers/cursor/sessions.js b/server/providers/cursor/sessions.js index f6a7f933..30980c32 100644 --- a/server/providers/cursor/sessions.js +++ b/server/providers/cursor/sessions.js @@ -21,21 +21,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(); @@ -130,7 +125,7 @@ async function loadCursorBlobs(sessionId, projectPath) { return messages; } finally { - await db.close(); + db.close(); } } 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 be545078..a30fd430 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/index.js'; import { codexAdapter } from './codex/index.js'; import { geminiAdapter } from './gemini/index.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/index.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/server/routes/commands.js b/server/routes/commands.js index 388a8f76..4ce3c4c0 100644 --- a/server/routes/commands.js +++ b/server/routes/commands.js @@ -1,13 +1,15 @@ import express from 'express'; import { promises as fs } from 'fs'; import path from 'path'; -import { fileURLToPath } from 'url'; import os from 'os'; import { CLAUDE_MODELS, CURSOR_MODELS, CODEX_MODELS } from '../../shared/modelConstants.js'; import { parseFrontmatter } from '../utils/frontmatter.js'; +import { findAppRoot, getModuleDir } from '../utils/runtime-paths.js'; -const __filename = fileURLToPath(import.meta.url); -const __dirname = path.dirname(__filename); +const __dirname = getModuleDir(import.meta.url); +// This route reads the top-level package.json for the status command, so it needs the real +// app root even after compilation moves the route file under dist-server/server/routes. +const APP_ROOT = findAppRoot(__dirname); const router = express.Router(); @@ -291,7 +293,7 @@ Custom commands can be created in: '/status': async (args, context) => { // Read version from package.json - const packageJsonPath = path.join(path.dirname(__dirname), '..', 'package.json'); + const packageJsonPath = path.join(APP_ROOT, 'package.json'); let version = 'unknown'; let packageName = 'claude-code-ui'; 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) { diff --git a/server/tsconfig.json b/server/tsconfig.json new file mode 100644 index 00000000..59a363ed --- /dev/null +++ b/server/tsconfig.json @@ -0,0 +1,33 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "lib": ["ES2022"], + "baseUrl": ".", + "paths": { + // In the backend config, "@" maps to the /server directory itself. + "@/*": ["*"] + }, + // The backend is still mostly JavaScript today, so allowJs lets us add a real + // TypeScript build without forcing a large rename before the tooling is usable. + "allowJs": true, + // Keep the migration incremental: existing JS keeps building, while any new TS files + // still go through the normal TypeScript pipeline and strict checks. + "checkJs": false, + "strict": true, + "noEmitOnError": true, + // The backend build emits both /server and /shared into dist-server, so rootDir must + // stay one level above this file even though the config itself now lives in /server. + "rootDir": "..", + "outDir": "../dist-server", + "sourceMap": true, + "resolveJsonModule": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "skipLibCheck": true, + "types": ["node"] + }, + "include": ["./**/*.js", "./**/*.ts", "../shared/**/*.js", "../shared/**/*.ts"], + "exclude": ["../dist", "../dist-server", "../node_modules", "../src"] +} diff --git a/server/utils/runtime-paths.js b/server/utils/runtime-paths.js new file mode 100644 index 00000000..92137187 --- /dev/null +++ b/server/utils/runtime-paths.js @@ -0,0 +1,37 @@ +import path from 'path'; +import { fileURLToPath } from 'url'; + +export function getModuleDir(importMetaUrl) { + return path.dirname(fileURLToPath(importMetaUrl)); +} + +export function findServerRoot(startDir) { + // Source files live under /server, while compiled files live under /dist-server/server. + // Walking up to the nearest "server" folder gives every backend module one stable anchor + // that works in both layouts instead of relying on fragile "../.." assumptions. + let currentDir = startDir; + + while (path.basename(currentDir) !== 'server') { + const parentDir = path.dirname(currentDir); + + if (parentDir === currentDir) { + throw new Error(`Could not resolve the backend server root from "${startDir}".`); + } + + currentDir = parentDir; + } + + return currentDir; +} + +export function findAppRoot(startDir) { + const serverRoot = findServerRoot(startDir); + const parentOfServerRoot = path.dirname(serverRoot); + + // Source files live at /server, while compiled files live at /dist-server/server. + // When the nearest server folder sits inside dist-server we need to hop one extra level up + // so repo-level files still resolve from the real app root instead of the build directory. + return path.basename(parentOfServerRoot) === 'dist-server' + ? path.dirname(parentOfServerRoot) + : parentOfServerRoot; +} diff --git a/src/components/chat/hooks/useChatComposerState.ts b/src/components/chat/hooks/useChatComposerState.ts index 858faff9..15f4b63f 100644 --- a/src/components/chat/hooks/useChatComposerState.ts +++ b/src/components/chat/hooks/useChatComposerState.ts @@ -19,7 +19,7 @@ import type { PendingPermissionRequest, PermissionMode, } from '../types/types'; -import type { Project, ProjectSession, SessionProvider } from '../../../types/app'; +import type { Project, ProjectSession, LLMProvider } from '../../../types/app'; import { escapeRegExp } from '../utils/chatFormatting'; import { useFileMentions } from './useFileMentions'; import { type SlashCommand, useSlashCommands } from './useSlashCommands'; @@ -33,7 +33,7 @@ interface UseChatComposerStateArgs { selectedProject: Project | null; selectedSession: ProjectSession | null; currentSessionId: string | null; - provider: SessionProvider; + provider: LLMProvider; permissionMode: PermissionMode | string; cyclePermissionMode: () => void; cursorModel: string; diff --git a/src/components/chat/hooks/useChatProviderState.ts b/src/components/chat/hooks/useChatProviderState.ts index 9d48ce3d..6d39d22a 100644 --- a/src/components/chat/hooks/useChatProviderState.ts +++ b/src/components/chat/hooks/useChatProviderState.ts @@ -2,7 +2,7 @@ import { useCallback, useEffect, useRef, useState } from 'react'; import { authenticatedFetch } from '../../../utils/api'; import { CLAUDE_MODELS, CODEX_MODELS, CURSOR_MODELS, GEMINI_MODELS } from '../../../../shared/modelConstants'; import type { PendingPermissionRequest, PermissionMode } from '../types/types'; -import type { ProjectSession, SessionProvider } from '../../../types/app'; +import type { ProjectSession, LLMProvider } from '../../../types/app'; interface UseChatProviderStateArgs { selectedSession: ProjectSession | null; @@ -11,8 +11,8 @@ interface UseChatProviderStateArgs { export function useChatProviderState({ selectedSession }: UseChatProviderStateArgs) { const [permissionMode, setPermissionMode] = useState('default'); const [pendingPermissionRequests, setPendingPermissionRequests] = useState([]); - const [provider, setProvider] = useState(() => { - return (localStorage.getItem('selected-provider') as SessionProvider) || 'claude'; + const [provider, setProvider] = useState(() => { + return (localStorage.getItem('selected-provider') as LLMProvider) || 'claude'; }); const [cursorModel, setCursorModel] = useState(() => { return localStorage.getItem('cursor-model') || CURSOR_MODELS.DEFAULT; diff --git a/src/components/chat/hooks/useChatRealtimeHandlers.ts b/src/components/chat/hooks/useChatRealtimeHandlers.ts index 6d734730..73d1a5e7 100644 --- a/src/components/chat/hooks/useChatRealtimeHandlers.ts +++ b/src/components/chat/hooks/useChatRealtimeHandlers.ts @@ -1,7 +1,7 @@ import { useEffect, useRef } from 'react'; import type { Dispatch, MutableRefObject, SetStateAction } from 'react'; import type { PendingPermissionRequest } from '../types/types'; -import type { Project, ProjectSession, SessionProvider } from '../../../types/app'; +import type { Project, ProjectSession, LLMProvider } from '../../../types/app'; import type { SessionStore, NormalizedMessage } from '../../../stores/useSessionStore'; type PendingViewSession = { @@ -48,7 +48,7 @@ type LatestChatMessage = { interface UseChatRealtimeHandlersArgs { latestMessage: LatestChatMessage | null; - provider: SessionProvider; + provider: LLMProvider; selectedProject: Project | null; selectedSession: ProjectSession | null; currentSessionId: string | null; diff --git a/src/components/chat/hooks/useChatSessionState.ts b/src/components/chat/hooks/useChatSessionState.ts index e952ee1a..b551060a 100644 --- a/src/components/chat/hooks/useChatSessionState.ts +++ b/src/components/chat/hooks/useChatSessionState.ts @@ -2,7 +2,7 @@ import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } fr import type { MutableRefObject } from 'react'; import { authenticatedFetch } from '../../../utils/api'; import type { ChatMessage, Provider } from '../types/types'; -import type { Project, ProjectSession, SessionProvider } from '../../../types/app'; +import type { Project, ProjectSession, LLMProvider } from '../../../types/app'; import { createCachedDiffCalculator, type DiffCalculator } from '../utils/messageTransforms'; import { normalizedToChatMessages } from './useChatMessages'; import type { SessionStore, NormalizedMessage } from '../../../stores/useSessionStore'; @@ -40,7 +40,7 @@ interface ScrollRestoreState { function chatMessageToNormalized( msg: ChatMessage, sessionId: string, - provider: SessionProvider, + provider: LLMProvider, ): NormalizedMessage | null { const id = `local_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`; const ts = msg.timestamp instanceof Date @@ -151,7 +151,7 @@ export function useChatSessionState({ // When a real session ID arrives and we have a pending user message, flush it to the store const prevActiveSessionRef = useRef(null); if (activeSessionId && activeSessionId !== prevActiveSessionRef.current && pendingUserMessage) { - const prov = (localStorage.getItem('selected-provider') as SessionProvider) || 'claude'; + const prov = (localStorage.getItem('selected-provider') as LLMProvider) || 'claude'; const normalized = chatMessageToNormalized(pendingUserMessage, activeSessionId, prov); if (normalized) { sessionStore.appendRealtime(activeSessionId, normalized); @@ -189,7 +189,7 @@ export function useChatSessionState({ setPendingUserMessage(msg); return; } - const prov = (localStorage.getItem('selected-provider') as SessionProvider) || 'claude'; + const prov = (localStorage.getItem('selected-provider') as LLMProvider) || 'claude'; const normalized = chatMessageToNormalized(msg, activeSessionId, prov); if (normalized) { sessionStore.appendRealtime(activeSessionId, normalized); @@ -240,7 +240,7 @@ export function useChatSessionState({ try { const slot = await sessionStore.fetchMore(selectedSession.id, { - provider: sessionProvider as SessionProvider, + provider: sessionProvider as LLMProvider, projectName: selectedProject.name, projectPath: selectedProject.fullPath || selectedProject.path || '', limit: MESSAGES_PER_PAGE, @@ -374,7 +374,7 @@ export function useChatSessionState({ // Fetch from server → store updates → chatMessages re-derives automatically setIsLoadingSessionMessages(true); sessionStore.fetchFromServer(selectedSession.id, { - provider: (selectedSession.__provider || provider) as SessionProvider, + provider: (selectedSession.__provider || provider) as LLMProvider, projectName: selectedProject.name, projectPath: selectedProject.fullPath || selectedProject.path || '', limit: MESSAGES_PER_PAGE, @@ -410,7 +410,7 @@ export function useChatSessionState({ // Skip store refresh during active streaming if (!isLoading) { await sessionStore.refreshFromServer(selectedSession.id, { - provider: (selectedSession.__provider || provider) as SessionProvider, + provider: (selectedSession.__provider || provider) as LLMProvider, projectName: selectedProject.name, projectPath: selectedProject.fullPath || selectedProject.path || '', }); @@ -468,7 +468,7 @@ export function useChatSessionState({ try { // Load all messages into the store for search navigation const slot = await sessionStore.fetchFromServer(selectedSession.id, { - provider: sessionProvider as SessionProvider, + provider: sessionProvider as LLMProvider, projectName: selectedProject.name, projectPath: selectedProject.fullPath || selectedProject.path || '', limit: null, @@ -655,7 +655,7 @@ export function useChatSessionState({ try { const slot = await sessionStore.fetchFromServer(requestSessionId, { - provider: sessionProvider as SessionProvider, + provider: sessionProvider as LLMProvider, projectName: selectedProject.name, projectPath: selectedProject.fullPath || selectedProject.path || '', limit: null, diff --git a/src/components/chat/types/types.ts b/src/components/chat/types/types.ts index 66d50741..900028c0 100644 --- a/src/components/chat/types/types.ts +++ b/src/components/chat/types/types.ts @@ -1,6 +1,6 @@ -import type { Project, ProjectSession, SessionProvider } from '../../../types/app'; +import type { Project, ProjectSession, LLMProvider } from '../../../types/app'; -export type Provider = SessionProvider; +export type Provider = LLMProvider; export type PermissionMode = 'default' | 'acceptEdits' | 'bypassPermissions' | 'plan'; diff --git a/src/components/chat/view/ChatInterface.tsx b/src/components/chat/view/ChatInterface.tsx index 19483f64..44709502 100644 --- a/src/components/chat/view/ChatInterface.tsx +++ b/src/components/chat/view/ChatInterface.tsx @@ -3,7 +3,7 @@ import { useTranslation } from 'react-i18next'; import { useTasksSettings } from '../../../contexts/TasksSettingsContext'; import { QuickSettingsPanel } from '../../quick-settings-panel'; import type { ChatInterfaceProps, Provider } from '../types/types'; -import type { SessionProvider } from '../../../types/app'; +import type { LLMProvider } from '../../../types/app'; import { useChatProviderState } from '../hooks/useChatProviderState'; import { useChatSessionState } from '../hooks/useChatSessionState'; import { useChatRealtimeHandlers } from '../hooks/useChatRealtimeHandlers'; @@ -206,9 +206,9 @@ function ChatInterface({ // so missed streaming events are shown. Also reset isLoading. const handleWebSocketReconnect = useCallback(async () => { if (!selectedProject || !selectedSession) return; - const providerVal = (localStorage.getItem('selected-provider') as SessionProvider) || 'claude'; + const providerVal = (localStorage.getItem('selected-provider') as LLMProvider) || 'claude'; await sessionStore.refreshFromServer(selectedSession.id, { - provider: (selectedSession.__provider || providerVal) as SessionProvider, + provider: (selectedSession.__provider || providerVal) as LLMProvider, projectName: selectedProject.name, projectPath: selectedProject.fullPath || selectedProject.path || '', }); 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 && (
void; + provider: LLMProvider; + setProvider: (provider: LLMProvider) => void; textareaRef: RefObject; claudeModel: string; setClaudeModel: (model: string) => void; diff --git a/src/components/chat/view/subcomponents/ProviderSelectionEmptyState.tsx b/src/components/chat/view/subcomponents/ProviderSelectionEmptyState.tsx index 792b12c7..9eaf690e 100644 --- a/src/components/chat/view/subcomponents/ProviderSelectionEmptyState.tsx +++ b/src/components/chat/view/subcomponents/ProviderSelectionEmptyState.tsx @@ -8,14 +8,14 @@ import { CODEX_MODELS, GEMINI_MODELS, } from "../../../../../shared/modelConstants"; -import type { ProjectSession, SessionProvider } from "../../../../types/app"; +import type { ProjectSession, LLMProvider } from "../../../../types/app"; import { NextTaskBanner } from "../../../task-master"; type ProviderSelectionEmptyStateProps = { selectedSession: ProjectSession | null; currentSessionId: string | null; - provider: SessionProvider; - setProvider: (next: SessionProvider) => void; + provider: LLMProvider; + setProvider: (next: LLMProvider) => void; textareaRef: React.RefObject; claudeModel: string; setClaudeModel: (model: string) => void; @@ -32,7 +32,7 @@ type ProviderSelectionEmptyStateProps = { }; type ProviderDef = { - id: SessionProvider; + id: LLMProvider; name: string; infoKey: string; accent: string; @@ -75,7 +75,7 @@ const PROVIDERS: ProviderDef[] = [ }, ]; -function getModelConfig(p: SessionProvider) { +function getModelConfig(p: LLMProvider) { if (p === "claude") return CLAUDE_MODELS; if (p === "codex") return CODEX_MODELS; if (p === "gemini") return GEMINI_MODELS; @@ -83,7 +83,7 @@ function getModelConfig(p: SessionProvider) { } function getModelValue( - p: SessionProvider, + p: LLMProvider, c: string, cu: string, co: string, @@ -119,7 +119,7 @@ export default function ProviderSelectionEmptyState({ defaultValue: "Start the next task", }); - const selectProvider = (next: SessionProvider) => { + const selectProvider = (next: LLMProvider) => { setProvider(next); localStorage.setItem("selected-provider", next); setTimeout(() => textareaRef.current?.focus(), 100); diff --git a/src/components/llm-logo-provider/SessionProviderLogo.tsx b/src/components/llm-logo-provider/SessionProviderLogo.tsx index 1eaef523..53c02291 100644 --- a/src/components/llm-logo-provider/SessionProviderLogo.tsx +++ b/src/components/llm-logo-provider/SessionProviderLogo.tsx @@ -1,11 +1,11 @@ -import type { SessionProvider } from '../../types/app'; +import type { LLMProvider } from '../../types/app'; import ClaudeLogo from './ClaudeLogo'; import CodexLogo from './CodexLogo'; import CursorLogo from './CursorLogo'; import GeminiLogo from './GeminiLogo'; type SessionProviderLogoProps = { - provider?: SessionProvider | string | null; + provider?: LLMProvider | string | null; className?: string; }; diff --git a/src/components/onboarding/view/Onboarding.tsx b/src/components/onboarding/view/Onboarding.tsx index 58627c4c..6d9a69ca 100644 --- a/src/components/onboarding/view/Onboarding.tsx +++ b/src/components/onboarding/view/Onboarding.tsx @@ -1,17 +1,15 @@ import { Check, ChevronLeft, ChevronRight, Loader2 } from 'lucide-react'; import { useCallback, useEffect, useRef, useState } from 'react'; +import type { LLMProvider } from '../../../types/app'; import { authenticatedFetch } from '../../../utils/api'; +import { useProviderAuthStatus } from '../../provider-auth/hooks/useProviderAuthStatus'; import ProviderLoginModal from '../../provider-auth/view/ProviderLoginModal'; import AgentConnectionsStep from './subcomponents/AgentConnectionsStep'; import GitConfigurationStep from './subcomponents/GitConfigurationStep'; import OnboardingStepProgress from './subcomponents/OnboardingStepProgress'; -import type { CliProvider, ProviderStatusMap } from './types'; import { - cliProviders, - createInitialProviderStatuses, gitEmailPattern, readErrorMessageFromResponse, - selectedProject, } from './utils'; type OnboardingProps = { @@ -24,59 +22,14 @@ export default function Onboarding({ onComplete }: OnboardingProps) { const [gitEmail, setGitEmail] = useState(''); const [isSubmitting, setIsSubmitting] = useState(false); const [errorMessage, setErrorMessage] = useState(''); - const [activeLoginProvider, setActiveLoginProvider] = useState(null); - const [providerStatuses, setProviderStatuses] = useState(createInitialProviderStatuses); + const [activeLoginProvider, setActiveLoginProvider] = useState(null); + const { + providerAuthStatus, + checkProviderAuthStatus, + refreshProviderAuthStatuses, + } = useProviderAuthStatus(); - const previousActiveLoginProviderRef = useRef(undefined); - - const checkProviderAuthStatus = useCallback(async (provider: CliProvider) => { - try { - const response = await authenticatedFetch(`/api/cli/${provider}/status`); - if (!response.ok) { - setProviderStatuses((previous) => ({ - ...previous, - [provider]: { - authenticated: false, - email: null, - loading: false, - error: 'Failed to check authentication status', - }, - })); - return; - } - - const payload = (await response.json()) as { - authenticated?: boolean; - email?: string | null; - error?: string | null; - }; - - setProviderStatuses((previous) => ({ - ...previous, - [provider]: { - authenticated: Boolean(payload.authenticated), - email: payload.email ?? null, - loading: false, - error: payload.error ?? null, - }, - })); - } catch (caughtError) { - console.error(`Error checking ${provider} auth status:`, caughtError); - setProviderStatuses((previous) => ({ - ...previous, - [provider]: { - authenticated: false, - email: null, - loading: false, - error: caughtError instanceof Error ? caughtError.message : 'Unknown error', - }, - })); - } - }, []); - - const refreshAllProviderStatuses = useCallback(async () => { - await Promise.all(cliProviders.map((provider) => checkProviderAuthStatus(provider))); - }, [checkProviderAuthStatus]); + const previousActiveLoginProviderRef = useRef(undefined); const loadGitConfig = useCallback(async () => { try { @@ -99,23 +52,24 @@ export default function Onboarding({ onComplete }: OnboardingProps) { useEffect(() => { void loadGitConfig(); - void refreshAllProviderStatuses(); - }, [loadGitConfig, refreshAllProviderStatuses]); + void refreshProviderAuthStatuses(); + }, [loadGitConfig, refreshProviderAuthStatuses]); useEffect(() => { const previousProvider = previousActiveLoginProviderRef.current; previousActiveLoginProviderRef.current = activeLoginProvider; - const isInitialMount = previousProvider === undefined; - const didCloseModal = previousProvider !== null && activeLoginProvider === null; + const didCloseModal = previousProvider !== undefined + && previousProvider !== null + && activeLoginProvider === null; - // Refresh statuses once on mount and again after the login modal is closed. - if (isInitialMount || didCloseModal) { - void refreshAllProviderStatuses(); + // Refresh statuses after the login modal is closed. + if (didCloseModal) { + void refreshProviderAuthStatuses(); } - }, [activeLoginProvider, refreshAllProviderStatuses]); + }, [activeLoginProvider, refreshProviderAuthStatuses]); - const handleProviderLoginOpen = (provider: CliProvider) => { + const handleProviderLoginOpen = (provider: LLMProvider) => { setActiveLoginProvider(provider); }; @@ -209,7 +163,7 @@ export default function Onboarding({ onComplete }: OnboardingProps) { /> ) : ( )} @@ -279,7 +233,6 @@ export default function Onboarding({ onComplete }: OnboardingProps) { isOpen={Boolean(activeLoginProvider)} onClose={() => setActiveLoginProvider(null)} provider={activeLoginProvider} - project={selectedProject} onComplete={handleLoginComplete} /> )} diff --git a/src/components/onboarding/view/subcomponents/AgentConnectionCard.tsx b/src/components/onboarding/view/subcomponents/AgentConnectionCard.tsx index edf2ef0e..3737ad54 100644 --- a/src/components/onboarding/view/subcomponents/AgentConnectionCard.tsx +++ b/src/components/onboarding/view/subcomponents/AgentConnectionCard.tsx @@ -1,9 +1,10 @@ import { Check } from 'lucide-react'; import SessionProviderLogo from '../../../llm-logo-provider/SessionProviderLogo'; -import type { CliProvider, ProviderAuthStatus } from '../types'; +import type { LLMProvider } from '../../../../types/app'; +import type { ProviderAuthStatus } from '../../../provider-auth/types'; type AgentConnectionCardProps = { - provider: CliProvider; + provider: LLMProvider; title: string; status: ProviderAuthStatus; connectedClassName: string; diff --git a/src/components/onboarding/view/subcomponents/AgentConnectionsStep.tsx b/src/components/onboarding/view/subcomponents/AgentConnectionsStep.tsx index 5bca5d33..3dfb25a9 100644 --- a/src/components/onboarding/view/subcomponents/AgentConnectionsStep.tsx +++ b/src/components/onboarding/view/subcomponents/AgentConnectionsStep.tsx @@ -1,9 +1,10 @@ -import type { CliProvider, ProviderStatusMap } from '../types'; +import type { LLMProvider } from '../../../../types/app'; +import type { ProviderAuthStatusMap } from '../../../provider-auth/types'; import AgentConnectionCard from './AgentConnectionCard'; type AgentConnectionsStepProps = { - providerStatuses: ProviderStatusMap; - onOpenProviderLogin: (provider: CliProvider) => void; + providerStatuses: ProviderAuthStatusMap; + onOpenProviderLogin: (provider: LLMProvider) => void; }; const providerCards = [ diff --git a/src/components/onboarding/view/types.ts b/src/components/onboarding/view/types.ts deleted file mode 100644 index 46800813..00000000 --- a/src/components/onboarding/view/types.ts +++ /dev/null @@ -1,12 +0,0 @@ -import type { CliProvider } from '../../provider-auth/types'; - -export type { CliProvider }; - -export type ProviderAuthStatus = { - authenticated: boolean; - email: string | null; - loading: boolean; - error: string | null; -}; - -export type ProviderStatusMap = Record; diff --git a/src/components/onboarding/view/utils.ts b/src/components/onboarding/view/utils.ts index adb3c4bc..0b40ab02 100644 --- a/src/components/onboarding/view/utils.ts +++ b/src/components/onboarding/view/utils.ts @@ -1,24 +1,5 @@ -import { IS_PLATFORM } from '../../../constants/config'; -import type { CliProvider, ProviderStatusMap } from './types'; - -export const cliProviders: CliProvider[] = ['claude', 'cursor', 'codex', 'gemini']; - export const gitEmailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; -export const selectedProject = { - name: 'default', - displayName: 'default', - fullPath: IS_PLATFORM ? '/workspace' : '', - path: IS_PLATFORM ? '/workspace' : '', -}; - -export const createInitialProviderStatuses = (): ProviderStatusMap => ({ - claude: { authenticated: false, email: null, loading: true, error: null }, - cursor: { authenticated: false, email: null, loading: true, error: null }, - codex: { authenticated: false, email: null, loading: true, error: null }, - gemini: { authenticated: false, email: null, loading: true, error: null }, -}); - export const readErrorMessageFromResponse = async (response: Response, fallback: string) => { try { const payload = (await response.json()) as { error?: string }; diff --git a/src/components/provider-auth/hooks/useProviderAuthStatus.ts b/src/components/provider-auth/hooks/useProviderAuthStatus.ts new file mode 100644 index 00000000..83692a2a --- /dev/null +++ b/src/components/provider-auth/hooks/useProviderAuthStatus.ts @@ -0,0 +1,109 @@ +import { useCallback, useState } from 'react'; +import { authenticatedFetch } from '../../../utils/api'; +import type { LLMProvider } from '../../../types/app'; +import { + CLI_AUTH_STATUS_ENDPOINTS, + CLI_PROVIDERS, + createInitialProviderAuthStatusMap, +} from '../types'; +import type { + ProviderAuthStatus, + ProviderAuthStatusMap, +} from '../types'; + +type ProviderAuthStatusPayload = { + authenticated?: boolean; + email?: string | null; + method?: string | null; + error?: string | null; +}; + +const FALLBACK_STATUS_ERROR = 'Failed to check authentication status'; +const FALLBACK_UNKNOWN_ERROR = 'Unknown error'; + +const toErrorMessage = (error: unknown): string => ( + error instanceof Error ? error.message : FALLBACK_UNKNOWN_ERROR +); + +const toProviderAuthStatus = ( + payload: ProviderAuthStatusPayload, + fallbackError: string | null = null, +): ProviderAuthStatus => ({ + authenticated: Boolean(payload.authenticated), + email: payload.email ?? null, + method: payload.method ?? null, + error: payload.error ?? fallbackError, + loading: false, +}); + +type UseProviderAuthStatusOptions = { + initialLoading?: boolean; +}; + +export function useProviderAuthStatus( + { initialLoading = true }: UseProviderAuthStatusOptions = {}, +) { + const [providerAuthStatus, setProviderAuthStatus] = useState(() => ( + createInitialProviderAuthStatusMap(initialLoading) + )); + + const setProviderLoading = useCallback((provider: LLMProvider) => { + setProviderAuthStatus((previous) => ({ + ...previous, + [provider]: { + ...previous[provider], + loading: true, + error: null, + }, + })); + }, []); + + const setProviderStatus = useCallback((provider: LLMProvider, status: ProviderAuthStatus) => { + setProviderAuthStatus((previous) => ({ + ...previous, + [provider]: status, + })); + }, []); + + const checkProviderAuthStatus = useCallback(async (provider: LLMProvider) => { + setProviderLoading(provider); + + try { + const response = await authenticatedFetch(CLI_AUTH_STATUS_ENDPOINTS[provider]); + + if (!response.ok) { + setProviderStatus(provider, { + authenticated: false, + email: null, + method: null, + loading: false, + error: FALLBACK_STATUS_ERROR, + }); + return; + } + + const payload = (await response.json()) as ProviderAuthStatusPayload; + setProviderStatus(provider, toProviderAuthStatus(payload)); + } catch (caughtError) { + console.error(`Error checking ${provider} auth status:`, caughtError); + setProviderStatus(provider, { + authenticated: false, + email: null, + method: null, + loading: false, + error: toErrorMessage(caughtError), + }); + } + }, [setProviderLoading, setProviderStatus]); + + const refreshProviderAuthStatuses = useCallback(async (providers: LLMProvider[] = CLI_PROVIDERS) => { + await Promise.all(providers.map((provider) => checkProviderAuthStatus(provider))); + }, [checkProviderAuthStatus]); + + return { + providerAuthStatus, + setProviderAuthStatus, + checkProviderAuthStatus, + refreshProviderAuthStatuses, + }; +} diff --git a/src/components/provider-auth/types.ts b/src/components/provider-auth/types.ts index e39a9796..bb93199c 100644 --- a/src/components/provider-auth/types.ts +++ b/src/components/provider-auth/types.ts @@ -1 +1,27 @@ -export type CliProvider = 'claude' | 'cursor' | 'codex' | 'gemini'; +import type { LLMProvider } from '../../types/app'; + +export type ProviderAuthStatus = { + authenticated: boolean; + email: string | null; + method: string | null; + error: string | null; + loading: boolean; +}; + +export type ProviderAuthStatusMap = Record; + +export const CLI_PROVIDERS: LLMProvider[] = ['claude', 'cursor', 'codex', 'gemini']; + +export const CLI_AUTH_STATUS_ENDPOINTS: Record = { + claude: '/api/cli/claude/status', + cursor: '/api/cli/cursor/status', + codex: '/api/cli/codex/status', + gemini: '/api/cli/gemini/status', +}; + +export const createInitialProviderAuthStatusMap = (loading = true): ProviderAuthStatusMap => ({ + claude: { authenticated: false, email: null, method: null, error: null, loading }, + cursor: { authenticated: false, email: null, method: null, error: null, loading }, + codex: { authenticated: false, email: null, method: null, error: null, loading }, + gemini: { authenticated: false, email: null, method: null, error: null, loading }, +}); diff --git a/src/components/provider-auth/view/ProviderLoginModal.tsx b/src/components/provider-auth/view/ProviderLoginModal.tsx index 9e80302e..fa1d4407 100644 --- a/src/components/provider-auth/view/ProviderLoginModal.tsx +++ b/src/components/provider-auth/view/ProviderLoginModal.tsx @@ -1,21 +1,12 @@ import { ExternalLink, KeyRound, X } from 'lucide-react'; import StandaloneShell from '../../standalone-shell/view/StandaloneShell'; -import { IS_PLATFORM } from '../../../constants/config'; -import type { CliProvider } from '../types'; - -type LoginModalProject = { - name?: string; - displayName?: string; - fullPath?: string; - path?: string; - [key: string]: unknown; -}; +import { DEFAULT_PROJECT_FOR_EMPTY_SHELL, IS_PLATFORM } from '../../../constants/config'; +import type { LLMProvider } from '../../../types/app'; type ProviderLoginModalProps = { isOpen: boolean; onClose: () => void; - provider?: CliProvider; - project?: LoginModalProject | null; + provider?: LLMProvider; onComplete?: (exitCode: number) => void; customCommand?: string; isAuthenticated?: boolean; @@ -26,7 +17,7 @@ const getProviderCommand = ({ customCommand, isAuthenticated: _isAuthenticated, }: { - provider: CliProvider; + provider: LLMProvider; customCommand?: string; isAuthenticated: boolean; }) => { @@ -49,30 +40,17 @@ const getProviderCommand = ({ return 'gemini status'; }; -const getProviderTitle = (provider: CliProvider) => { +const getProviderTitle = (provider: LLMProvider) => { if (provider === 'claude') return 'Claude CLI Login'; if (provider === 'cursor') return 'Cursor CLI Login'; if (provider === 'codex') return 'Codex CLI Login'; return 'Gemini CLI Configuration'; }; -const normalizeProject = (project?: LoginModalProject | null) => { - const normalizedName = project?.name || 'default'; - const normalizedFullPath = project?.fullPath ?? project?.path ?? (IS_PLATFORM ? '/workspace' : ''); - - return { - name: normalizedName, - displayName: project?.displayName || normalizedName, - fullPath: normalizedFullPath, - path: project?.path ?? normalizedFullPath, - }; -}; - export default function ProviderLoginModal({ isOpen, onClose, provider = 'claude', - project = null, onComplete, customCommand, isAuthenticated = false, @@ -83,7 +61,6 @@ export default function ProviderLoginModal({ const command = getProviderCommand({ provider, customCommand, isAuthenticated }); const title = getProviderTitle(provider); - const shellProject = normalizeProject(project); const handleComplete = (exitCode: number) => { onComplete?.(exitCode); @@ -158,7 +135,7 @@ export default function ProviderLoginModal({
) : ( - + )}
diff --git a/src/components/settings/constants/constants.ts b/src/components/settings/constants/constants.ts index 36f45392..52f16e10 100644 --- a/src/components/settings/constants/constants.ts +++ b/src/components/settings/constants/constants.ts @@ -1,7 +1,6 @@ import type { AgentCategory, AgentProvider, - AuthStatus, ClaudeMcpFormState, CodexMcpFormState, CodeEditorSettingsState, @@ -34,13 +33,6 @@ export const DEFAULT_CODE_EDITOR_SETTINGS: CodeEditorSettingsState = { fontSize: '14', }; -export const DEFAULT_AUTH_STATUS: AuthStatus = { - authenticated: false, - email: null, - loading: true, - error: null, -}; - export const DEFAULT_MCP_TEST_RESULT: McpTestResult = { success: false, message: '', @@ -88,9 +80,3 @@ export const DEFAULT_CURSOR_PERMISSIONS: CursorPermissionsState = { skipPermissions: false, }; -export const AUTH_STATUS_ENDPOINTS: Record = { - claude: '/api/cli/claude/status', - cursor: '/api/cli/cursor/status', - codex: '/api/cli/codex/status', - gemini: '/api/cli/gemini/status', -}; diff --git a/src/components/settings/hooks/useSettingsController.ts b/src/components/settings/hooks/useSettingsController.ts index 293cbceb..09550265 100644 --- a/src/components/settings/hooks/useSettingsController.ts +++ b/src/components/settings/hooks/useSettingsController.ts @@ -1,15 +1,13 @@ import { useCallback, useEffect, useRef, useState } from 'react'; import { useTheme } from '../../../contexts/ThemeContext'; import { authenticatedFetch } from '../../../utils/api'; +import { useProviderAuthStatus } from '../../provider-auth/hooks/useProviderAuthStatus'; import { - AUTH_STATUS_ENDPOINTS, - DEFAULT_AUTH_STATUS, DEFAULT_CODE_EDITOR_SETTINGS, DEFAULT_CURSOR_PERMISSIONS, } from '../constants/constants'; import type { AgentProvider, - AuthStatus, ClaudeMcpFormState, ClaudePermissionsState, CodeEditorSettingsState, @@ -23,7 +21,6 @@ import type { NotificationPreferencesState, ProjectSortOrder, SettingsMainTab, - SettingsProject, } from '../types/types'; type ThemeContextValue = { @@ -34,15 +31,6 @@ type ThemeContextValue = { type UseSettingsControllerArgs = { isOpen: boolean; initialTab: string; - projects: SettingsProject[]; - onClose: () => void; -}; - -type StatusApiResponse = { - authenticated?: boolean; - email?: string | null; - error?: string | null; - method?: string; }; type JsonResult = { @@ -166,20 +154,6 @@ const mapCliServersToMcpServers = (servers: McpCliServer[] = []): McpServer[] => })) ); -const getDefaultProject = (projects: SettingsProject[]): SettingsProject => { - if (projects.length > 0) { - return projects[0]; - } - - const cwd = typeof process !== 'undefined' && process.cwd ? process.cwd() : ''; - return { - name: 'default', - displayName: 'default', - fullPath: cwd, - path: cwd, - }; -}; - const toResponseJson = async (response: Response): Promise => response.json() as Promise; const createEmptyClaudePermissions = (): ClaudePermissionsState => ({ @@ -204,7 +178,7 @@ const createDefaultNotificationPreferences = (): NotificationPreferencesState => }, }); -export function useSettingsController({ isOpen, initialTab, projects, onClose }: UseSettingsControllerArgs) { +export function useSettingsController({ isOpen, initialTab }: UseSettingsControllerArgs) { const { isDarkMode, toggleDarkMode } = useTheme() as ThemeContextValue; const closeTimerRef = useRef(null); @@ -242,64 +216,11 @@ export function useSettingsController({ isOpen, initialTab, projects, onClose }: const [showLoginModal, setShowLoginModal] = useState(false); const [loginProvider, setLoginProvider] = useState(''); - const [selectedProject, setSelectedProject] = useState(null); - - const [claudeAuthStatus, setClaudeAuthStatus] = useState(DEFAULT_AUTH_STATUS); - const [cursorAuthStatus, setCursorAuthStatus] = useState(DEFAULT_AUTH_STATUS); - const [codexAuthStatus, setCodexAuthStatus] = useState(DEFAULT_AUTH_STATUS); - const [geminiAuthStatus, setGeminiAuthStatus] = useState(DEFAULT_AUTH_STATUS); - - const setAuthStatusByProvider = useCallback((provider: AgentProvider, status: AuthStatus) => { - if (provider === 'claude') { - setClaudeAuthStatus(status); - return; - } - - if (provider === 'cursor') { - setCursorAuthStatus(status); - return; - } - - if (provider === 'gemini') { - setGeminiAuthStatus(status); - return; - } - - setCodexAuthStatus(status); - }, []); - - const checkAuthStatus = useCallback(async (provider: AgentProvider) => { - try { - const response = await authenticatedFetch(AUTH_STATUS_ENDPOINTS[provider]); - - if (!response.ok) { - setAuthStatusByProvider(provider, { - authenticated: false, - email: null, - loading: false, - error: 'Failed to check authentication status', - }); - return; - } - - const data = await toResponseJson(response); - setAuthStatusByProvider(provider, { - authenticated: Boolean(data.authenticated), - email: data.email || null, - loading: false, - error: data.error || null, - method: data.method, - }); - } catch (error) { - console.error(`Error checking ${provider} auth status:`, error); - setAuthStatusByProvider(provider, { - authenticated: false, - email: null, - loading: false, - error: getErrorMessage(error), - }); - } - }, [setAuthStatusByProvider]); + const { + providerAuthStatus, + checkProviderAuthStatus, + refreshProviderAuthStatuses, + } = useProviderAuthStatus(); const fetchCursorMcpServers = useCallback(async () => { try { @@ -724,9 +645,8 @@ export function useSettingsController({ isOpen, initialTab, projects, onClose }: const openLoginForProvider = useCallback((provider: AgentProvider) => { setLoginProvider(provider); - setSelectedProject(getDefaultProject(projects)); setShowLoginModal(true); - }, [projects]); + }, []); const handleLoginComplete = useCallback((exitCode: number) => { if (exitCode !== 0 || !loginProvider) { @@ -734,8 +654,8 @@ export function useSettingsController({ isOpen, initialTab, projects, onClose }: } setSaveStatus('success'); - void checkAuthStatus(loginProvider); - }, [checkAuthStatus, loginProvider]); + void checkProviderAuthStatus(loginProvider); + }, [checkProviderAuthStatus, loginProvider]); const saveSettings = useCallback(async () => { setSaveStatus(null); @@ -827,11 +747,8 @@ export function useSettingsController({ isOpen, initialTab, projects, onClose }: setActiveTab(normalizeMainTab(initialTab)); void loadSettings(); - void checkAuthStatus('claude'); - void checkAuthStatus('cursor'); - void checkAuthStatus('codex'); - void checkAuthStatus('gemini'); - }, [checkAuthStatus, initialTab, isOpen, loadSettings]); + void refreshProviderAuthStatuses(); + }, [initialTab, isOpen, loadSettings, refreshProviderAuthStatuses]); useEffect(() => { localStorage.setItem('codeEditorTheme', codeEditorSettings.theme); @@ -935,17 +852,13 @@ export function useSettingsController({ isOpen, initialTab, projects, onClose }: closeCodexMcpForm, submitCodexMcpForm, handleCodexMcpDelete, - claudeAuthStatus, - cursorAuthStatus, - codexAuthStatus, - geminiAuthStatus, + providerAuthStatus, geminiPermissionMode, setGeminiPermissionMode, openLoginForProvider, showLoginModal, setShowLoginModal, loginProvider, - selectedProject, handleLoginComplete, }; } diff --git a/src/components/settings/types/types.ts b/src/components/settings/types/types.ts index e3af730b..235d0b47 100644 --- a/src/components/settings/types/types.ts +++ b/src/components/settings/types/types.ts @@ -1,7 +1,9 @@ import type { Dispatch, SetStateAction } from 'react'; +import type { LLMProvider } from '../../../types/app'; +import type { ProviderAuthStatus } from '../../provider-auth/types'; export type SettingsMainTab = 'agents' | 'appearance' | 'git' | 'api' | 'tasks' | 'notifications' | 'plugins' | 'about'; -export type AgentProvider = 'claude' | 'cursor' | 'codex' | 'gemini'; +export type AgentProvider = LLMProvider; export type AgentCategory = 'account' | 'permissions' | 'mcp'; export type ProjectSortOrder = 'name' | 'date'; export type SaveStatus = 'success' | 'error' | null; @@ -18,13 +20,7 @@ export type SettingsProject = { path?: string; }; -export type AuthStatus = { - authenticated: boolean; - email: string | null; - loading: boolean; - error: string | null; - method?: string; -}; +export type AuthStatus = ProviderAuthStatus; export type KeyValueMap = Record; diff --git a/src/components/settings/view/Settings.tsx b/src/components/settings/view/Settings.tsx index 70d3ea4d..c1977cf2 100644 --- a/src/components/settings/view/Settings.tsx +++ b/src/components/settings/view/Settings.tsx @@ -56,23 +56,17 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'agents' }: Set closeCodexMcpForm, submitCodexMcpForm, handleCodexMcpDelete, - claudeAuthStatus, - cursorAuthStatus, - codexAuthStatus, - geminiAuthStatus, + providerAuthStatus, geminiPermissionMode, setGeminiPermissionMode, openLoginForProvider, showLoginModal, setShowLoginModal, loginProvider, - selectedProject, handleLoginComplete, } = useSettingsController({ isOpen, - initialTab, - projects, - onClose, + initialTab }); const { @@ -105,13 +99,7 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'agents' }: Set return null; } - const isAuthenticated = loginProvider === 'claude' - ? claudeAuthStatus.authenticated - : loginProvider === 'cursor' - ? cursorAuthStatus.authenticated - : loginProvider === 'codex' - ? codexAuthStatus.authenticated - : false; + const isAuthenticated = Boolean(loginProvider && providerAuthStatus[loginProvider].authenticated); return (
@@ -121,7 +109,7 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'agents' }: Set

{t('title')}

{saveStatus === 'success' && ( - {t('saveStatus.success')} + {t('saveStatus.success')} )} +
diff --git a/src/components/sidebar/view/subcomponents/SidebarProjectItem.tsx b/src/components/sidebar/view/subcomponents/SidebarProjectItem.tsx index ae0bdf97..5ac02019 100644 --- a/src/components/sidebar/view/subcomponents/SidebarProjectItem.tsx +++ b/src/components/sidebar/view/subcomponents/SidebarProjectItem.tsx @@ -2,7 +2,7 @@ import { Check, ChevronDown, ChevronRight, Edit3, Folder, FolderOpen, Star, Tras import type { TFunction } from 'i18next'; import { Button } from '../../../../shared/view/ui'; import { cn } from '../../../../lib/utils'; -import type { Project, ProjectSession, SessionProvider } from '../../../../types/app'; +import type { Project, ProjectSession, LLMProvider } from '../../../../types/app'; import type { MCPServerStatus, SessionWithProvider } from '../../types/types'; import { getTaskIndicatorStatus } from '../../utils/utils'; import TaskIndicator from './TaskIndicator'; @@ -38,14 +38,14 @@ type SidebarProjectItemProps = { projectName: string, sessionId: string, sessionTitle: string, - provider: SessionProvider, + provider: LLMProvider, ) => void; onLoadMoreSessions: (project: Project) => void; onNewSession: (project: Project) => void; onEditingSessionNameChange: (value: string) => void; onStartEditingSession: (sessionId: string, initialName: string) => void; onCancelEditingSession: () => void; - onSaveEditingSession: (projectName: string, sessionId: string, summary: string, provider: SessionProvider) => void; + onSaveEditingSession: (projectName: string, sessionId: string, summary: string, provider: LLMProvider) => void; t: TFunction; }; diff --git a/src/components/sidebar/view/subcomponents/SidebarProjectList.tsx b/src/components/sidebar/view/subcomponents/SidebarProjectList.tsx index bd0fdbb3..a755b1da 100644 --- a/src/components/sidebar/view/subcomponents/SidebarProjectList.tsx +++ b/src/components/sidebar/view/subcomponents/SidebarProjectList.tsx @@ -1,6 +1,6 @@ import { useEffect } from 'react'; import type { TFunction } from 'i18next'; -import type { LoadingProgress, Project, ProjectSession, SessionProvider } from '../../../../types/app'; +import type { LoadingProgress, Project, ProjectSession, LLMProvider } from '../../../../types/app'; import type { LoadingSessionsByProject, MCPServerStatus, @@ -42,14 +42,14 @@ export type SidebarProjectListProps = { projectName: string, sessionId: string, sessionTitle: string, - provider: SessionProvider, + provider: LLMProvider, ) => void; onLoadMoreSessions: (project: Project) => void; onNewSession: (project: Project) => void; onEditingSessionNameChange: (value: string) => void; onStartEditingSession: (sessionId: string, initialName: string) => void; onCancelEditingSession: () => void; - onSaveEditingSession: (projectName: string, sessionId: string, summary: string, provider: SessionProvider) => void; + onSaveEditingSession: (projectName: string, sessionId: string, summary: string, provider: LLMProvider) => void; t: TFunction; }; diff --git a/src/components/sidebar/view/subcomponents/SidebarProjectSessions.tsx b/src/components/sidebar/view/subcomponents/SidebarProjectSessions.tsx index a5ceaa06..2f4068e9 100644 --- a/src/components/sidebar/view/subcomponents/SidebarProjectSessions.tsx +++ b/src/components/sidebar/view/subcomponents/SidebarProjectSessions.tsx @@ -1,7 +1,7 @@ import { ChevronDown, Plus } from 'lucide-react'; import type { TFunction } from 'i18next'; import { Button } from '../../../../shared/view/ui'; -import type { Project, ProjectSession, SessionProvider } from '../../../../types/app'; +import type { Project, ProjectSession, LLMProvider } from '../../../../types/app'; import type { SessionWithProvider } from '../../types/types'; import SidebarSessionItem from './SidebarSessionItem'; @@ -18,14 +18,14 @@ type SidebarProjectSessionsProps = { onEditingSessionNameChange: (value: string) => void; onStartEditingSession: (sessionId: string, initialName: string) => void; onCancelEditingSession: () => void; - onSaveEditingSession: (projectName: string, sessionId: string, summary: string, provider: SessionProvider) => void; + onSaveEditingSession: (projectName: string, sessionId: string, summary: string, provider: LLMProvider) => void; onProjectSelect: (project: Project) => void; onSessionSelect: (session: SessionWithProvider, projectName: string) => void; onDeleteSession: ( projectName: string, sessionId: string, sessionTitle: string, - provider: SessionProvider, + provider: LLMProvider, ) => void; onLoadMoreSessions: (project: Project) => void; onNewSession: (project: Project) => void; diff --git a/src/components/sidebar/view/subcomponents/SidebarSessionItem.tsx b/src/components/sidebar/view/subcomponents/SidebarSessionItem.tsx index a0f9b8d9..507478a8 100644 --- a/src/components/sidebar/view/subcomponents/SidebarSessionItem.tsx +++ b/src/components/sidebar/view/subcomponents/SidebarSessionItem.tsx @@ -3,7 +3,7 @@ import type { TFunction } from 'i18next'; import { Badge, Button } from '../../../../shared/view/ui'; import { cn } from '../../../../lib/utils'; import { formatTimeAgo } from '../../../../utils/dateUtils'; -import type { Project, ProjectSession, SessionProvider } from '../../../../types/app'; +import type { Project, ProjectSession, LLMProvider } from '../../../../types/app'; import type { SessionWithProvider } from '../../types/types'; import { createSessionViewModel } from '../../utils/utils'; import SessionProviderLogo from '../../../llm-logo-provider/SessionProviderLogo'; @@ -18,14 +18,14 @@ type SidebarSessionItemProps = { onEditingSessionNameChange: (value: string) => void; onStartEditingSession: (sessionId: string, initialName: string) => void; onCancelEditingSession: () => void; - onSaveEditingSession: (projectName: string, sessionId: string, summary: string, provider: SessionProvider) => void; + onSaveEditingSession: (projectName: string, sessionId: string, summary: string, provider: LLMProvider) => void; onProjectSelect: (project: Project) => void; onSessionSelect: (session: SessionWithProvider, projectName: string) => void; onDeleteSession: ( projectName: string, sessionId: string, sessionTitle: string, - provider: SessionProvider, + provider: LLMProvider, ) => void; t: TFunction; }; diff --git a/src/constants/config.ts b/src/constants/config.ts index 6741c35d..853cfb43 100644 --- a/src/constants/config.ts +++ b/src/constants/config.ts @@ -2,4 +2,16 @@ * Environment Flag: Is Platform * Indicates if the app is running in Platform mode (hosted) or OSS mode (self-hosted) */ -export const IS_PLATFORM = import.meta.env.VITE_IS_PLATFORM === 'true'; \ No newline at end of file +export const IS_PLATFORM = import.meta.env.VITE_IS_PLATFORM === 'true'; + +/** + * For empty shell instances where no project is provided, + * we use a default project object to ensure the shell can still function. + * This prevents errors related to missing project data. + */ +export const DEFAULT_PROJECT_FOR_EMPTY_SHELL = { + name: 'default', + displayName: 'default', + fullPath: IS_PLATFORM ? '/workspace' : '', + path: IS_PLATFORM ? '/workspace' : '', +}; \ No newline at end of file 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/stores/useSessionStore.ts b/src/stores/useSessionStore.ts index 60ace484..5f6446be 100644 --- a/src/stores/useSessionStore.ts +++ b/src/stores/useSessionStore.ts @@ -8,7 +8,7 @@ */ import { useCallback, useMemo, useRef, useState } from 'react'; -import type { SessionProvider } from '../types/app'; +import type { LLMProvider } from '../types/app'; import { authenticatedFetch } from '../utils/api'; // ─── NormalizedMessage (mirrors server/adapters/types.js) ──────────────────── @@ -33,7 +33,7 @@ export interface NormalizedMessage { id: string; sessionId: string; timestamp: string; - provider: SessionProvider; + provider: LLMProvider; kind: MessageKind; // kind-specific fields (flat for simplicity) @@ -169,7 +169,7 @@ export function useSessionStore() { const fetchFromServer = useCallback(async ( sessionId: string, opts: { - provider?: SessionProvider; + provider?: LLMProvider; projectName?: string; projectPath?: string; limit?: number | null; @@ -228,7 +228,7 @@ export function useSessionStore() { const fetchMore = useCallback(async ( sessionId: string, opts: { - provider?: SessionProvider; + provider?: LLMProvider; projectName?: string; projectPath?: string; limit?: number; @@ -303,7 +303,7 @@ export function useSessionStore() { const refreshFromServer = useCallback(async ( sessionId: string, opts: { - provider?: SessionProvider; + provider?: LLMProvider; projectName?: string; projectPath?: string; } = {}, @@ -357,7 +357,7 @@ export function useSessionStore() { * Update or create a streaming message (accumulated text so far). * Uses a well-known ID so subsequent calls replace the same message. */ - const updateStreaming = useCallback((sessionId: string, accumulatedText: string, msgProvider: SessionProvider) => { + const updateStreaming = useCallback((sessionId: string, accumulatedText: string, msgProvider: LLMProvider) => { const slot = getSlot(sessionId); const streamId = `__streaming_${sessionId}`; const msg: NormalizedMessage = { diff --git a/src/types/app.ts b/src/types/app.ts index 9abaac6b..4c2f230d 100644 --- a/src/types/app.ts +++ b/src/types/app.ts @@ -1,4 +1,4 @@ -export type SessionProvider = 'claude' | 'cursor' | 'codex' | 'gemini'; +export type LLMProvider = 'claude' | 'cursor' | 'codex' | 'gemini'; export type AppTab = 'chat' | 'files' | 'shell' | 'git' | 'tasks' | 'preview' | `plugin:${string}`; @@ -12,7 +12,7 @@ export interface ProjectSession { updated_at?: string; lastActivity?: string; messageCount?: number; - __provider?: SessionProvider; + __provider?: LLMProvider; __projectName?: string; [key: string]: unknown; } 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) }); diff --git a/tsconfig.json b/tsconfig.json index 57470821..94c553dd 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,6 +4,13 @@ "useDefineForClassFields": true, "lib": ["ES2020", "DOM", "DOM.Iterable"], "module": "ESNext", + "baseUrl": ".", + "paths": { + // The frontend keeps "@" mapped to /src. + // The backend gets its own "@" mapping in server/tsconfig.json so both sides can use + // the same alias name without sharing one compiler configuration. + "@/*": ["src/*"] + }, "skipLibCheck": true, "moduleResolution": "Bundler", "resolveJsonModule": true, @@ -14,7 +21,8 @@ "forceConsistentCasingInFileNames": true, "allowJs": true, // "checkJs": true, - "types": ["vite/client"] + "types": ["vite/client"], + "ignoreDeprecations": "5.0" }, "include": ["src", "shared", "vite.config.js"] } diff --git a/vite.config.js b/vite.config.js index 88c8e28e..61967cbe 100755 --- a/vite.config.js +++ b/vite.config.js @@ -1,3 +1,4 @@ +import { fileURLToPath, URL } from 'node:url' import { defineConfig, loadEnv } from 'vite' import react from '@vitejs/plugin-react' import { getConnectableHost, normalizeLoopbackHost } from './shared/networkHosts.js' @@ -19,6 +20,11 @@ export default defineConfig(({ mode }) => { return { plugins: [react()], + resolve: { + alias: { + '@': fileURLToPath(new URL('./src', import.meta.url)) + } + }, server: { host, port: parseInt(env.VITE_PORT) || 5173,