diff --git a/apps/frontend/src/main/env-utils.ts b/apps/frontend/src/main/env-utils.ts index 3cb143dc..8fbee2c0 100644 --- a/apps/frontend/src/main/env-utils.ts +++ b/apps/frontend/src/main/env-utils.ts @@ -16,10 +16,25 @@ import { promises as fsPromises } from 'fs'; import { execFileSync, execFile } from 'child_process'; import { promisify } from 'util'; import { getSentryEnvForSubprocess } from './sentry'; -import { isWindows, isUnix, getPathDelimiter } from './platform'; +import { isWindows, isUnix, getPathDelimiter, getNpmCommand } from './platform'; const execFileAsync = promisify(execFile); +/** + * Windows npm global fallback path + * + * On Windows, npm global packages are installed in %APPDATA%\npm by default. + * This constant provides the fallback path construction for when the npm + * command itself is not in PATH (e.g., packaged Electron apps launched from GUI). + * + * Uses process.env.APPDATA for enterprise environments with redirected profiles, + * falling back to the default home directory location. + */ +const WINDOWS_NPM_FALLBACK_PATH = (): string => { + const appDataPath = process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming'); + return path.join(appDataPath, 'npm'); +}; + /** * Check if a path exists asynchronously (non-blocking) * @@ -54,8 +69,8 @@ let npmGlobalPrefixCachePromise: Promise | null = null; */ function getNpmGlobalPrefix(): string | null { try { - // On Windows, use npm.cmd for proper command resolution - const npmCommand = isWindows() ? 'npm.cmd' : 'npm'; + // Use platform module helper for npm command name + const npmCommand = getNpmCommand(); // Use --location=global to bypass workspace context and avoid ENOWORKSPACES error const rawPrefix = execFileSync(npmCommand, ['config', 'get', 'prefix', '--location=global'], { @@ -80,7 +95,16 @@ function getNpmGlobalPrefix(): string | null { const normalizedPath = path.normalize(binPath); return fs.existsSync(normalizedPath) ? normalizedPath : null; - } catch { + } catch (error) { + // Fallback for Windows: try default npm global location when npm.cmd is not in PATH + // This happens when the packaged app launches from GUI without full shell environment + if (isWindows()) { + const defaultNpmPath = WINDOWS_NPM_FALLBACK_PATH(); + if (fs.existsSync(defaultNpmPath)) { + console.warn('[env-utils] npm command not found, using default npm path:', defaultNpmPath); + return defaultNpmPath; + } + } return null; } } @@ -321,7 +345,8 @@ async function getNpmGlobalPrefixAsync(): Promise { // Start the async fetch npmGlobalPrefixCachePromise = (async () => { try { - const npmCommand = isWindows() ? 'npm.cmd' : 'npm'; + // Use platform module helper for npm command name + const npmCommand = getNpmCommand(); const { stdout } = await execFileAsync(npmCommand, ['config', 'get', 'prefix', '--location=global'], { encoding: 'utf-8', @@ -345,6 +370,16 @@ async function getNpmGlobalPrefixAsync(): Promise { npmGlobalPrefixCache = await existsAsync(normalizedPath) ? normalizedPath : null; return npmGlobalPrefixCache; } catch (error) { + // Fallback for Windows: try default npm global location when npm.cmd is not in PATH + // This happens when the packaged app launches from GUI without full shell environment + if (isWindows()) { + const defaultNpmPath = WINDOWS_NPM_FALLBACK_PATH(); + if (await existsAsync(defaultNpmPath)) { + console.warn('[env-utils] npm command not found, using default npm path:', defaultNpmPath); + npmGlobalPrefixCache = defaultNpmPath; + return defaultNpmPath; + } + } console.warn(`[env-utils] Failed to get npm global prefix: ${error}`); npmGlobalPrefixCache = null; return null; diff --git a/apps/frontend/src/renderer/components/ClaudeCodeStatusBadge.tsx b/apps/frontend/src/renderer/components/ClaudeCodeStatusBadge.tsx index a46010d6..8fd34ee3 100644 --- a/apps/frontend/src/renderer/components/ClaudeCodeStatusBadge.tsx +++ b/apps/frontend/src/renderer/components/ClaudeCodeStatusBadge.tsx @@ -642,12 +642,18 @@ export function ClaudeCodeStatusBadge({ className }: ClaudeCodeStatusBadgeProps) "navigation:claudeCode.updateWarningDescription", "Updating will close all running Claude Code sessions. Any unsaved work in those sessions may be lost. Make sure to save your work before proceeding." )} + + {t( + "navigation:claudeCode.updateWarningTerminalNote", + "A terminal window will open to run the installation command. Please wait for the installation to complete before continuing." + )} + {t("common:cancel", "Cancel")} - {t("navigation:claudeCode.updateAnyway", "Update Anyway")} + {t("navigation:claudeCode.updateAnyway", "Open Terminal & Update")} @@ -667,6 +673,12 @@ export function ClaudeCodeStatusBadge({ className }: ClaudeCodeStatusBadgeProps) "navigation:claudeCode.rollbackWarningDescription", "Switching versions will close all running Claude Code sessions. Any unsaved work in those sessions may be lost. Make sure to save your work before proceeding." )} + + {t( + "navigation:claudeCode.rollbackWarningTerminalNote", + "A terminal window will open to run the installation command. Please wait for the installation to complete before continuing." + )} + @@ -674,7 +686,7 @@ export function ClaudeCodeStatusBadge({ className }: ClaudeCodeStatusBadgeProps) {t("common:cancel", "Cancel")} - {t("navigation:claudeCode.switchAnyway", "Switch Anyway")} + {t("navigation:claudeCode.switchAnyway", "Open Terminal & Switch")} diff --git a/apps/frontend/src/shared/i18n/locales/en/navigation.json b/apps/frontend/src/shared/i18n/locales/en/navigation.json index c5409543..204f9a9b 100644 --- a/apps/frontend/src/shared/i18n/locales/en/navigation.json +++ b/apps/frontend/src/shared/i18n/locales/en/navigation.json @@ -58,7 +58,8 @@ "viewChangelogAriaLabel": "View Claude Code Changelog (opens in new window)", "updateWarningTitle": "Update Claude Code?", "updateWarningDescription": "Updating will close all running Claude Code sessions. Any unsaved work in those sessions may be lost. Make sure to save your work before proceeding.", - "updateAnyway": "Update Anyway", + "updateWarningTerminalNote": "A terminal window will open to run the installation command. Please wait for the installation to complete before continuing.", + "updateAnyway": "Open Terminal & Update", "switchVersion": "Switch Version", "selectVersion": "Select version", "loadingVersions": "Loading versions...", @@ -66,7 +67,8 @@ "installingVersion": "Installing version {{version}}...", "rollbackWarningTitle": "Switch to version {{version}}?", "rollbackWarningDescription": "Switching versions will close all running Claude Code sessions. Any unsaved work in those sessions may be lost. Make sure to save your work before proceeding.", - "switchAnyway": "Switch Anyway", + "rollbackWarningTerminalNote": "A terminal window will open to run the installation command. Please wait for the installation to complete before continuing.", + "switchAnyway": "Open Terminal & Switch", "currentVersion": "Current", "switchInstallation": "Switch Installation", "selectInstallation": "Select installation", diff --git a/apps/frontend/src/shared/i18n/locales/fr/navigation.json b/apps/frontend/src/shared/i18n/locales/fr/navigation.json index 19646a2f..c40ee36c 100644 --- a/apps/frontend/src/shared/i18n/locales/fr/navigation.json +++ b/apps/frontend/src/shared/i18n/locales/fr/navigation.json @@ -58,7 +58,8 @@ "viewChangelogAriaLabel": "Voir le journal des modifications Claude Code (s'ouvre dans une nouvelle fenêtre)", "updateWarningTitle": "Mettre à jour Claude Code ?", "updateWarningDescription": "La mise à jour fermera toutes les sessions Claude Code en cours. Tout travail non sauvegardé dans ces sessions pourrait être perdu. Assurez-vous de sauvegarder votre travail avant de continuer.", - "updateAnyway": "Mettre à jour quand même", + "updateWarningTerminalNote": "Une fenêtre de terminal s'ouvrira pour exécuter la commande d'installation. Veuillez attendre la fin de l'installation avant de continuer.", + "updateAnyway": "Ouvrir le terminal et mettre à jour", "switchVersion": "Changer de version", "selectVersion": "Sélectionner une version", "loadingVersions": "Chargement des versions...", @@ -66,7 +67,8 @@ "installingVersion": "Installation de la version {{version}}...", "rollbackWarningTitle": "Passer à la version {{version}} ?", "rollbackWarningDescription": "Le changement de version fermera toutes les sessions Claude Code en cours. Tout travail non sauvegardé dans ces sessions pourrait être perdu. Assurez-vous de sauvegarder votre travail avant de continuer.", - "switchAnyway": "Changer quand même", + "rollbackWarningTerminalNote": "Une fenêtre de terminal s'ouvrira pour exécuter la commande d'installation. Veuillez attendre la fin de l'installation avant de continuer.", + "switchAnyway": "Ouvrir le terminal et changer", "currentVersion": "Actuelle", "switchInstallation": "Changer d'installation", "selectInstallation": "Sélectionner une installation", diff --git a/package-lock.json b/package-lock.json index 63585b13..9e9495ea 100644 --- a/package-lock.json +++ b/package-lock.json @@ -244,6 +244,7 @@ "integrity": "sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/generator": "^7.28.6", @@ -808,6 +809,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=18" }, @@ -851,6 +853,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=18" } @@ -890,6 +893,7 @@ "resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz", "integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==", "license": "MIT", + "peer": true, "dependencies": { "@dnd-kit/accessibility": "^3.1.1", "@dnd-kit/utilities": "^3.2.2", @@ -1308,7 +1312,6 @@ "dev": true, "license": "BSD-2-Clause", "optional": true, - "peer": true, "dependencies": { "cross-dirname": "^0.1.0", "debug": "^4.3.4", @@ -1330,7 +1333,6 @@ "dev": true, "license": "MIT", "optional": true, - "peer": true, "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", @@ -1347,7 +1349,6 @@ "dev": true, "license": "MIT", "optional": true, - "peer": true, "dependencies": { "universalify": "^2.0.0" }, @@ -1362,7 +1363,6 @@ "dev": true, "license": "MIT", "optional": true, - "peer": true, "engines": { "node": ">= 10.0.0" } @@ -2242,6 +2242,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", "license": "Apache-2.0", + "peer": true, "engines": { "node": ">=8.0.0" } @@ -2263,6 +2264,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/context-async-hooks/-/context-async-hooks-2.4.0.tgz", "integrity": "sha512-jn0phJ+hU7ZuvaoZE/8/Euw3gvHJrn2yi+kXrymwObEPVPjtwCmkvXDRQCWli+fCTTF/aSOtXaLr7CLIvv3LQg==", "license": "Apache-2.0", + "peer": true, "engines": { "node": "^18.19.0 || >=20.6.0" }, @@ -2275,6 +2277,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.4.0.tgz", "integrity": "sha512-KtcyFHssTn5ZgDu6SXmUznS80OFs/wN7y6MyFRRcKU6TOw8hNcGxKvt8hsdaLJfhzUszNSjURetq5Qpkad14Gw==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, @@ -2290,6 +2293,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.208.0.tgz", "integrity": "sha512-Eju0L4qWcQS+oXxi6pgh7zvE2byogAkcsVv0OjHF/97iOz1N/aKE6etSGowYkie+YA1uo6DNwdSxaaNnLvcRlA==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@opentelemetry/api-logs": "0.208.0", "import-in-the-middle": "^2.0.0", @@ -2692,6 +2696,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.4.0.tgz", "integrity": "sha512-RWvGLj2lMDZd7M/5tjkI/2VHMpXebLgPKvBUd9LRasEWR2xAynDwEYZuLvY9P2NGG73HF07jbbgWX2C9oavcQg==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@opentelemetry/core": "2.4.0", "@opentelemetry/semantic-conventions": "^1.29.0" @@ -2708,6 +2713,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.4.0.tgz", "integrity": "sha512-WH0xXkz/OHORDLKqaxcUZS0X+t1s7gGlumr2ebiEgNZQl2b0upK2cdoD0tatf7l8iP74woGJ/Kmxe82jdvcWRw==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@opentelemetry/core": "2.4.0", "@opentelemetry/resources": "2.4.0", @@ -2725,6 +2731,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.39.0.tgz", "integrity": "sha512-R5R9tb2AXs2IRLNKLBJDynhkfmx7mX0vi8NkhZb3gUkPWHn6HXk5J8iQ/dql0U3ApfWym4kXXmBDRGO+oeOfjg==", "license": "Apache-2.0", + "peer": true, "engines": { "node": ">=14" } @@ -4919,6 +4926,7 @@ "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", @@ -5217,6 +5225,7 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.8.tgz", "integrity": "sha512-3MbSL37jEchWZz2p2mjntRZtPt837ij10ApxKfgmXCTuHWagYg7iA5bqPw6C8BMPfwidlvfPI/fxOc42HLhcyg==", "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -5227,6 +5236,7 @@ "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", "devOptional": true, "license": "MIT", + "peer": true, "peerDependencies": { "@types/react": "^19.2.0" } @@ -5492,6 +5502,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -5524,6 +5535,7 @@ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -5965,6 +5977,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -6663,8 +6676,7 @@ "integrity": "sha512-+R08/oI0nl3vfPcqftZRpytksBXDzOUveBq/NBVx0sUp1axwzPQrKinNx5yd5sxPu8j1wIy8AfnVQ+5eFdha6Q==", "dev": true, "license": "MIT", - "optional": true, - "peer": true + "optional": true }, "node_modules/cross-env": { "version": "10.1.0", @@ -6981,6 +6993,7 @@ "integrity": "sha512-ce4Ogns4VMeisIuCSK0C62umG0lFy012jd8LMZ6w/veHUeX4fqfDrGe+HTWALAEwK6JwKP+dhPvizhArSOsFbg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "app-builder-lib": "26.4.0", "builder-util": "26.3.4", @@ -7137,6 +7150,7 @@ "dev": true, "hasInstallScript": true, "license": "MIT", + "peer": true, "dependencies": { "@electron/get": "^2.0.0", "@types/node": "^22.7.7", @@ -7385,7 +7399,6 @@ "dev": true, "hasInstallScript": true, "license": "MIT", - "peer": true, "dependencies": { "@electron/asar": "^3.2.1", "debug": "^4.1.1", @@ -7406,7 +7419,6 @@ "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "graceful-fs": "^4.1.2", "jsonfile": "^4.0.0", @@ -8416,6 +8428,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "@babel/runtime": "^7.28.4" }, @@ -8742,6 +8755,7 @@ "integrity": "sha512-mjzqwWRD9Y1J1KUi7W97Gja1bwOOM5Ug0EZ6UDK3xS7j7mndrkwozHtSblfomlzyB4NepioNt+B2sOSzczVgtQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@acemir/cssom": "^0.9.28", "@asamuzakjp/dom-selector": "^6.7.6", @@ -11257,6 +11271,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -11332,7 +11347,6 @@ "dev": true, "license": "MIT", "optional": true, - "peer": true, "dependencies": { "commander": "^9.4.0" }, @@ -11350,7 +11364,6 @@ "dev": true, "license": "MIT", "optional": true, - "peer": true, "engines": { "node": "^12.20.0 || >=14" } @@ -11477,6 +11490,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz", "integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -11486,6 +11500,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz", "integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==", "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -11888,7 +11903,6 @@ "deprecated": "Rimraf versions prior to v4 are no longer supported", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "glob": "^7.1.3" }, @@ -12467,7 +12481,8 @@ "version": "4.1.18", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.18.tgz", "integrity": "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/tapable": { "version": "2.3.0", @@ -12570,7 +12585,6 @@ "integrity": "sha512-yYrrsWnrXMcdsnu/7YMYAofM1ktpL5By7vZhf15CrXijWWrEYZks5AXBudalfSWJLlnen/QUJUB5aoB0kqZUGA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "mkdirp": "^0.5.1", "rimraf": "~2.6.2" @@ -12634,7 +12648,6 @@ "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "minimist": "^1.2.6" }, @@ -12726,6 +12739,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -12884,6 +12898,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "devOptional": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -13190,6 +13205,7 @@ "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", @@ -13782,6 +13798,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -14223,6 +14240,7 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.5.tgz", "integrity": "sha512-k7Nwx6vuWx1IJ9Bjuf4Zt1PEllcwe7cls3VNzm4CQ1/hgtFUK2bRNG3rvnpPUhFjmqJKAKtjV576KnUkHocg/g==", "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" }