feat(frontend): add Claude Code version rollback feature (#983)
* feat(frontend): add Claude Code version rollback feature Add ability for users to switch to any of the last 20 Claude Code CLI versions directly from the Claude Code popup in the sidebar. Changes: - Add IPC channels for fetching available versions and installing specific version - Add backend handlers to fetch versions from npm registry (with 1-hour cache) - Add version selector dropdown in ClaudeCodeStatusBadge component - Add warning dialog before switching versions (warns about closing sessions) - Add i18n support for English and French translations Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: address PR review feedback for Claude Code version rollback - Add validation after semver filtering to handle empty version list - Add error state and UI feedback for installation/version switch failures - Extract magic number 5000ms to VERSION_RECHECK_DELAY_MS constant - Bind Select value prop to selectedVersion state - Normalize version comparison to handle 'v' prefix consistently - Use normalized version comparison in SelectItem disabled check Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -20,7 +20,9 @@ import semver from 'semver';
|
||||
|
||||
// Cache for latest version (avoid hammering npm registry)
|
||||
let cachedLatestVersion: { version: string; timestamp: number } | null = null;
|
||||
let cachedVersionList: { versions: string[]; timestamp: number } | null = null;
|
||||
const CACHE_DURATION_MS = 24 * 60 * 60 * 1000; // 24 hours
|
||||
const VERSION_LIST_CACHE_DURATION_MS = 60 * 60 * 1000; // 1 hour for version list
|
||||
|
||||
/**
|
||||
* Fetch the latest version of Claude Code from npm registry
|
||||
@@ -63,6 +65,74 @@ async function fetchLatestVersion(): Promise<string> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch available versions of Claude Code from npm registry
|
||||
* Returns versions sorted by semver descending (newest first)
|
||||
* Limited to last 20 versions for performance
|
||||
*/
|
||||
async function fetchAvailableVersions(): Promise<string[]> {
|
||||
// Check cache first
|
||||
if (cachedVersionList && Date.now() - cachedVersionList.timestamp < VERSION_LIST_CACHE_DURATION_MS) {
|
||||
return cachedVersionList.versions;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch('https://registry.npmjs.org/@anthropic-ai/claude-code', {
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
signal: AbortSignal.timeout(15000), // 15 second timeout
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
const versions = Object.keys(data.versions || {});
|
||||
|
||||
if (!versions.length) {
|
||||
throw new Error('No versions found in npm registry');
|
||||
}
|
||||
|
||||
// Sort by semver descending (newest first) and take last 20
|
||||
const sortedVersions = versions
|
||||
.filter(v => semver.valid(v)) // Only valid semver versions
|
||||
.sort((a, b) => semver.rcompare(a, b)) // Sort descending
|
||||
.slice(0, 20); // Limit to 20 versions
|
||||
|
||||
// Validate we have versions after filtering
|
||||
if (sortedVersions.length === 0) {
|
||||
throw new Error('No valid semver versions found in npm registry');
|
||||
}
|
||||
|
||||
// Cache the result
|
||||
cachedVersionList = { versions: sortedVersions, timestamp: Date.now() };
|
||||
return sortedVersions;
|
||||
} catch (error) {
|
||||
console.error('[Claude Code] Failed to fetch available versions:', error);
|
||||
// Return cached versions if available, even if expired
|
||||
if (cachedVersionList) {
|
||||
return cachedVersionList.versions;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the platform-specific install command for a specific version of Claude Code
|
||||
* @param version - The version to install (e.g., "1.0.5")
|
||||
*/
|
||||
function getInstallVersionCommand(version: string): string {
|
||||
if (process.platform === 'win32') {
|
||||
// Windows: kill running Claude processes first, then install specific version
|
||||
return `taskkill /IM claude.exe /F 2>nul; claude install --force ${version}`;
|
||||
} else {
|
||||
// macOS/Linux: kill running Claude processes first, then install specific version
|
||||
return `pkill -x claude 2>/dev/null; sleep 1; claude install --force ${version}`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the platform-specific install command for Claude Code
|
||||
* @param isUpdate - If true, Claude is already installed and we just need to update
|
||||
@@ -280,7 +350,7 @@ export async function openTerminalWithCommand(command: string): Promise<void> {
|
||||
'C:\\Program Files\\Git\\git-bash.exe',
|
||||
'C:\\Program Files (x86)\\Git\\git-bash.exe',
|
||||
];
|
||||
let gitBashPath = gitBashPaths.find(p => existsSync(p));
|
||||
const gitBashPath = gitBashPaths.find(p => existsSync(p));
|
||||
if (gitBashPath) {
|
||||
await runWindowsCommand(`"${gitBashPath}" -c "${escapedBashCommand}"`);
|
||||
} else {
|
||||
@@ -621,5 +691,65 @@ export function registerClaudeCodeHandlers(): void {
|
||||
}
|
||||
);
|
||||
|
||||
// Get available Claude Code versions
|
||||
ipcMain.handle(
|
||||
IPC_CHANNELS.CLAUDE_CODE_GET_VERSIONS,
|
||||
async (): Promise<IPCResult<{ versions: string[] }>> => {
|
||||
try {
|
||||
console.log('[Claude Code] Fetching available versions...');
|
||||
const versions = await fetchAvailableVersions();
|
||||
console.log('[Claude Code] Found', versions.length, 'versions');
|
||||
return {
|
||||
success: true,
|
||||
data: { versions },
|
||||
};
|
||||
} catch (error) {
|
||||
const errorMsg = error instanceof Error ? error.message : 'Unknown error';
|
||||
console.error('[Claude Code] Failed to fetch versions:', errorMsg, error);
|
||||
return {
|
||||
success: false,
|
||||
error: `Failed to fetch available versions: ${errorMsg}`,
|
||||
};
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// Install a specific version of Claude Code
|
||||
ipcMain.handle(
|
||||
IPC_CHANNELS.CLAUDE_CODE_INSTALL_VERSION,
|
||||
async (_event, version: string): Promise<IPCResult<{ command: string; version: string }>> => {
|
||||
try {
|
||||
// Validate version format
|
||||
if (!version || typeof version !== 'string') {
|
||||
throw new Error('Invalid version specified');
|
||||
}
|
||||
|
||||
// Basic semver validation
|
||||
if (!semver.valid(version)) {
|
||||
throw new Error(`Invalid version format: ${version}`);
|
||||
}
|
||||
|
||||
console.log('[Claude Code] Installing version:', version);
|
||||
const command = getInstallVersionCommand(version);
|
||||
console.log('[Claude Code] Install command:', command);
|
||||
console.log('[Claude Code] Opening terminal...');
|
||||
await openTerminalWithCommand(command);
|
||||
console.log('[Claude Code] Terminal opened successfully');
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: { command, version },
|
||||
};
|
||||
} catch (error) {
|
||||
const errorMsg = error instanceof Error ? error.message : 'Unknown error';
|
||||
console.error('[Claude Code] Install version failed:', errorMsg, error);
|
||||
return {
|
||||
success: false,
|
||||
error: `Failed to install version: ${errorMsg}`,
|
||||
};
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
console.warn('[IPC] Claude Code handlers registered');
|
||||
}
|
||||
|
||||
@@ -4,10 +4,12 @@
|
||||
* Provides access to Claude Code CLI management:
|
||||
* - Check installed vs latest version
|
||||
* - Install or update Claude Code
|
||||
* - Get available versions for rollback
|
||||
* - Install specific version
|
||||
*/
|
||||
|
||||
import { IPC_CHANNELS } from '../../../shared/constants';
|
||||
import type { ClaudeCodeVersionInfo } from '../../../shared/types/cli';
|
||||
import type { ClaudeCodeVersionInfo, ClaudeCodeVersionList } from '../../../shared/types/cli';
|
||||
import { invokeIpc } from './ipc-utils';
|
||||
|
||||
/**
|
||||
@@ -30,6 +32,27 @@ export interface ClaudeCodeVersionResult {
|
||||
error?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Result of fetching available versions
|
||||
*/
|
||||
export interface ClaudeCodeVersionsResult {
|
||||
success: boolean;
|
||||
data?: ClaudeCodeVersionList;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Result of installing a specific version
|
||||
*/
|
||||
export interface ClaudeCodeInstallVersionResult {
|
||||
success: boolean;
|
||||
data?: {
|
||||
command: string;
|
||||
version: string;
|
||||
};
|
||||
error?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Claude Code API interface exposed to renderer
|
||||
*/
|
||||
@@ -45,6 +68,18 @@ export interface ClaudeCodeAPI {
|
||||
* Opens the user's terminal with the install command
|
||||
*/
|
||||
installClaudeCode: () => Promise<ClaudeCodeInstallResult>;
|
||||
|
||||
/**
|
||||
* Get available Claude Code CLI versions
|
||||
* Returns list of versions sorted newest first
|
||||
*/
|
||||
getClaudeCodeVersions: () => Promise<ClaudeCodeVersionsResult>;
|
||||
|
||||
/**
|
||||
* Install a specific version of Claude Code CLI
|
||||
* Opens the user's terminal with the install command for the specified version
|
||||
*/
|
||||
installClaudeCodeVersion: (version: string) => Promise<ClaudeCodeInstallVersionResult>;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -55,5 +90,11 @@ export const createClaudeCodeAPI = (): ClaudeCodeAPI => ({
|
||||
invokeIpc(IPC_CHANNELS.CLAUDE_CODE_CHECK_VERSION),
|
||||
|
||||
installClaudeCode: (): Promise<ClaudeCodeInstallResult> =>
|
||||
invokeIpc(IPC_CHANNELS.CLAUDE_CODE_INSTALL)
|
||||
invokeIpc(IPC_CHANNELS.CLAUDE_CODE_INSTALL),
|
||||
|
||||
getClaudeCodeVersions: (): Promise<ClaudeCodeVersionsResult> =>
|
||||
invokeIpc(IPC_CHANNELS.CLAUDE_CODE_GET_VERSIONS),
|
||||
|
||||
installClaudeCodeVersion: (version: string): Promise<ClaudeCodeInstallVersionResult> =>
|
||||
invokeIpc(IPC_CHANNELS.CLAUDE_CODE_INSTALL_VERSION, version)
|
||||
});
|
||||
|
||||
@@ -23,6 +23,13 @@ import {
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
} from "./ui/alert-dialog";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "./ui/select";
|
||||
import { cn } from "../lib/utils";
|
||||
import type { ClaudeCodeVersionInfo } from "../../shared/types/cli";
|
||||
|
||||
@@ -34,6 +41,8 @@ type StatusType = "loading" | "installed" | "outdated" | "not-found" | "error";
|
||||
|
||||
// Check every 24 hours
|
||||
const CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000;
|
||||
// Delay before re-checking version after install/update
|
||||
const VERSION_RECHECK_DELAY_MS = 5000;
|
||||
|
||||
/**
|
||||
* Claude Code CLI status badge for the sidebar.
|
||||
@@ -48,6 +57,14 @@ export function ClaudeCodeStatusBadge({ className }: ClaudeCodeStatusBadgeProps)
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [showUpdateWarning, setShowUpdateWarning] = useState(false);
|
||||
|
||||
// Version rollback state
|
||||
const [availableVersions, setAvailableVersions] = useState<string[]>([]);
|
||||
const [isLoadingVersions, setIsLoadingVersions] = useState(false);
|
||||
const [versionsError, setVersionsError] = useState<string | null>(null);
|
||||
const [selectedVersion, setSelectedVersion] = useState<string | null>(null);
|
||||
const [showRollbackWarning, setShowRollbackWarning] = useState(false);
|
||||
const [installError, setInstallError] = useState<string | null>(null);
|
||||
|
||||
// Check Claude Code version
|
||||
const checkVersion = useCallback(async () => {
|
||||
try {
|
||||
@@ -78,6 +95,30 @@ export function ClaudeCodeStatusBadge({ className }: ClaudeCodeStatusBadgeProps)
|
||||
}
|
||||
}, []);
|
||||
|
||||
// Fetch available versions
|
||||
const fetchVersions = useCallback(async () => {
|
||||
if (!window.electronAPI?.getClaudeCodeVersions) {
|
||||
return;
|
||||
}
|
||||
|
||||
setIsLoadingVersions(true);
|
||||
setVersionsError(null);
|
||||
|
||||
try {
|
||||
const result = await window.electronAPI.getClaudeCodeVersions();
|
||||
if (result.success && result.data) {
|
||||
setAvailableVersions(result.data.versions);
|
||||
} else {
|
||||
setVersionsError(result.error || "Failed to load versions");
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Failed to fetch versions:", err);
|
||||
setVersionsError("Failed to load versions");
|
||||
} finally {
|
||||
setIsLoadingVersions(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
// Initial check and periodic re-check
|
||||
useEffect(() => {
|
||||
checkVersion();
|
||||
@@ -90,12 +131,21 @@ export function ClaudeCodeStatusBadge({ className }: ClaudeCodeStatusBadgeProps)
|
||||
return () => clearInterval(interval);
|
||||
}, [checkVersion]);
|
||||
|
||||
// Fetch versions when popover opens and Claude is installed
|
||||
useEffect(() => {
|
||||
if (isOpen && versionInfo?.installed && availableVersions.length === 0) {
|
||||
fetchVersions();
|
||||
}
|
||||
}, [isOpen, versionInfo?.installed, availableVersions.length, fetchVersions]);
|
||||
|
||||
// Perform the actual install/update
|
||||
const performInstall = async () => {
|
||||
setIsInstalling(true);
|
||||
setShowUpdateWarning(false);
|
||||
setInstallError(null);
|
||||
try {
|
||||
if (!window.electronAPI?.installClaudeCode) {
|
||||
setInstallError("Installation not available");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -105,15 +155,51 @@ export function ClaudeCodeStatusBadge({ className }: ClaudeCodeStatusBadgeProps)
|
||||
// Re-check after a delay
|
||||
setTimeout(() => {
|
||||
checkVersion();
|
||||
}, 5000);
|
||||
}, VERSION_RECHECK_DELAY_MS);
|
||||
} else {
|
||||
setInstallError(result.error || "Installation failed");
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Failed to install Claude Code:", err);
|
||||
setInstallError(err instanceof Error ? err.message : "Installation failed");
|
||||
} finally {
|
||||
setIsInstalling(false);
|
||||
}
|
||||
};
|
||||
|
||||
// Perform version rollback/switch
|
||||
const performVersionSwitch = async () => {
|
||||
if (!selectedVersion) return;
|
||||
|
||||
setIsInstalling(true);
|
||||
setShowRollbackWarning(false);
|
||||
setInstallError(null);
|
||||
|
||||
try {
|
||||
if (!window.electronAPI?.installClaudeCodeVersion) {
|
||||
setInstallError("Version switching not available");
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await window.electronAPI.installClaudeCodeVersion(selectedVersion);
|
||||
|
||||
if (result.success) {
|
||||
// Re-check after a delay
|
||||
setTimeout(() => {
|
||||
checkVersion();
|
||||
}, VERSION_RECHECK_DELAY_MS);
|
||||
} else {
|
||||
setInstallError(result.error || "Failed to switch version");
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Failed to switch Claude Code version:", err);
|
||||
setInstallError(err instanceof Error ? err.message : "Failed to switch version");
|
||||
} finally {
|
||||
setIsInstalling(false);
|
||||
setSelectedVersion(null);
|
||||
}
|
||||
};
|
||||
|
||||
// Handle install/update button click
|
||||
const handleInstall = () => {
|
||||
if (status === "outdated") {
|
||||
@@ -125,6 +211,22 @@ export function ClaudeCodeStatusBadge({ className }: ClaudeCodeStatusBadgeProps)
|
||||
}
|
||||
};
|
||||
|
||||
// Normalize version string by removing 'v' prefix for comparison
|
||||
const normalizeVersion = (v: string) => v.replace(/^v/, '');
|
||||
|
||||
// Handle version selection
|
||||
const handleVersionSelect = (version: string) => {
|
||||
// Don't do anything if it's the currently installed version (normalize both for comparison)
|
||||
const normalizedSelected = normalizeVersion(version);
|
||||
const normalizedInstalled = versionInfo?.installed ? normalizeVersion(versionInfo.installed) : '';
|
||||
if (normalizedSelected === normalizedInstalled) {
|
||||
return;
|
||||
}
|
||||
setInstallError(null);
|
||||
setSelectedVersion(version);
|
||||
setShowRollbackWarning(true);
|
||||
};
|
||||
|
||||
// Get status indicator color
|
||||
const getStatusColor = () => {
|
||||
switch (status) {
|
||||
@@ -292,6 +394,60 @@ export function ClaudeCodeStatusBadge({ className }: ClaudeCodeStatusBadgeProps)
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Install/Update error display */}
|
||||
{installError && (
|
||||
<div className="text-xs p-2 bg-destructive/10 text-destructive rounded-md flex items-center gap-2">
|
||||
<AlertTriangle className="h-3 w-3 flex-shrink-0" />
|
||||
<span>{installError}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Version selector - only show when Claude is installed */}
|
||||
{versionInfo?.installed && (
|
||||
<div className="space-y-1.5">
|
||||
<label className="text-xs text-muted-foreground">
|
||||
{t("navigation:claudeCode.switchVersion", "Switch Version")}
|
||||
</label>
|
||||
<Select
|
||||
value={selectedVersion || ""}
|
||||
onValueChange={handleVersionSelect}
|
||||
disabled={isLoadingVersions || isInstalling}
|
||||
>
|
||||
<SelectTrigger className="h-8 text-xs">
|
||||
<SelectValue
|
||||
placeholder={
|
||||
isLoadingVersions
|
||||
? t("navigation:claudeCode.loadingVersions", "Loading versions...")
|
||||
: versionsError
|
||||
? t("navigation:claudeCode.failedToLoadVersions", "Failed to load versions")
|
||||
: t("navigation:claudeCode.selectVersion", "Select version")
|
||||
}
|
||||
/>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{availableVersions.map((version) => {
|
||||
const isCurrentVersion = normalizeVersion(version) === normalizeVersion(versionInfo.installed || '');
|
||||
return (
|
||||
<SelectItem
|
||||
key={version}
|
||||
value={version}
|
||||
className="text-xs"
|
||||
disabled={isCurrentVersion}
|
||||
>
|
||||
<span className="font-mono">{version}</span>
|
||||
{isCurrentVersion && (
|
||||
<span className="ml-2 text-muted-foreground">
|
||||
({t("navigation:claudeCode.currentVersion", "Current")})
|
||||
</span>
|
||||
)}
|
||||
</SelectItem>
|
||||
);
|
||||
})}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Learn more link */}
|
||||
<Button
|
||||
variant="link"
|
||||
@@ -350,6 +506,33 @@ export function ClaudeCodeStatusBadge({ className }: ClaudeCodeStatusBadgeProps)
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
|
||||
{/* Version rollback warning dialog */}
|
||||
<AlertDialog open={showRollbackWarning} onOpenChange={setShowRollbackWarning}>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>
|
||||
{t("navigation:claudeCode.rollbackWarningTitle", "Switch to version {{version}}?", {
|
||||
version: selectedVersion,
|
||||
})}
|
||||
</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
{t(
|
||||
"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."
|
||||
)}
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel onClick={() => setSelectedVersion(null)}>
|
||||
{t("common:cancel", "Cancel")}
|
||||
</AlertDialogCancel>
|
||||
<AlertDialogAction onClick={performVersionSwitch}>
|
||||
{t("navigation:claudeCode.switchAnyway", "Switch Anyway")}
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -250,6 +250,16 @@ const browserMockAPI: ElectronAPI = {
|
||||
success: true,
|
||||
data: { command: 'npm install -g @anthropic-ai/claude-code' }
|
||||
}),
|
||||
getClaudeCodeVersions: async () => ({
|
||||
success: true,
|
||||
data: {
|
||||
versions: ['1.0.5', '1.0.4', '1.0.3', '1.0.2', '1.0.1', '1.0.0']
|
||||
}
|
||||
}),
|
||||
installClaudeCodeVersion: async (version: string) => ({
|
||||
success: true,
|
||||
data: { command: `npm install -g @anthropic-ai/claude-code@${version}`, version }
|
||||
}),
|
||||
|
||||
// Terminal Worktree Operations
|
||||
createTerminalWorktree: async () => ({
|
||||
|
||||
@@ -505,6 +505,8 @@ export const IPC_CHANNELS = {
|
||||
// Claude Code CLI operations
|
||||
CLAUDE_CODE_CHECK_VERSION: 'claudeCode:checkVersion',
|
||||
CLAUDE_CODE_INSTALL: 'claudeCode:install',
|
||||
CLAUDE_CODE_GET_VERSIONS: 'claudeCode:getVersions',
|
||||
CLAUDE_CODE_INSTALL_VERSION: 'claudeCode:installVersion',
|
||||
|
||||
// MCP Server health checks
|
||||
MCP_CHECK_HEALTH: 'mcp:checkHealth', // Quick connectivity check
|
||||
|
||||
@@ -48,6 +48,15 @@
|
||||
"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"
|
||||
"updateAnyway": "Update Anyway",
|
||||
"switchVersion": "Switch Version",
|
||||
"selectVersion": "Select version",
|
||||
"loadingVersions": "Loading versions...",
|
||||
"failedToLoadVersions": "Failed to load versions",
|
||||
"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",
|
||||
"currentVersion": "Current"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,6 +48,15 @@
|
||||
"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"
|
||||
"updateAnyway": "Mettre à jour quand même",
|
||||
"switchVersion": "Changer de version",
|
||||
"selectVersion": "Sélectionner une version",
|
||||
"loadingVersions": "Chargement des versions...",
|
||||
"failedToLoadVersions": "Échec du chargement des versions",
|
||||
"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",
|
||||
"currentVersion": "Actuelle"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,3 +40,12 @@ export interface ClaudeCodeVersionInfo {
|
||||
/** Full detection result with source information */
|
||||
detectionResult: ToolDetectionResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* Available Claude Code CLI versions
|
||||
* Used for version rollback feature
|
||||
*/
|
||||
export interface ClaudeCodeVersionList {
|
||||
/** List of available versions, sorted newest first */
|
||||
versions: string[];
|
||||
}
|
||||
|
||||
@@ -767,6 +767,8 @@ export interface ElectronAPI {
|
||||
// Claude Code CLI operations
|
||||
checkClaudeCodeVersion: () => Promise<IPCResult<import('./cli').ClaudeCodeVersionInfo>>;
|
||||
installClaudeCode: () => Promise<IPCResult<{ command: string }>>;
|
||||
getClaudeCodeVersions: () => Promise<IPCResult<import('./cli').ClaudeCodeVersionList>>;
|
||||
installClaudeCodeVersion: (version: string) => Promise<IPCResult<{ command: string; version: string }>>;
|
||||
|
||||
// Debug operations
|
||||
getDebugInfo: () => Promise<{
|
||||
|
||||
Reference in New Issue
Block a user