Merge branch 'develop' into feat-gitlab-parity

This commit is contained in:
André Mikalsen
2026-03-18 14:39:58 +01:00
committed by GitHub
15 changed files with 1237 additions and 1045 deletions
+6 -6
View File
@@ -42,12 +42,12 @@
<!-- BETA_DOWNLOADS -->
| Platform | Download |
|----------|----------|
| **Windows** | [Auto-Claude-2.8.0-beta.5-win32-x64.exe](https://github.com/AndyMik90/Auto-Claude/releases/download/v2.8.0-beta.5/Auto-Claude-2.8.0-beta.5-win32-x64.exe) |
| **macOS (Apple Silicon)** | [Auto-Claude-2.8.0-beta.5-darwin-arm64.dmg](https://github.com/AndyMik90/Auto-Claude/releases/download/v2.8.0-beta.5/Auto-Claude-2.8.0-beta.5-darwin-arm64.dmg) |
| **macOS (Intel)** | [Auto-Claude-2.8.0-beta.5-darwin-x64.dmg](https://github.com/AndyMik90/Auto-Claude/releases/download/v2.8.0-beta.5/Auto-Claude-2.8.0-beta.5-darwin-x64.dmg) |
| **Linux** | [Auto-Claude-2.8.0-beta.5-linux-x86_64.AppImage](https://github.com/AndyMik90/Auto-Claude/releases/download/v2.8.0-beta.5/Auto-Claude-2.8.0-beta.5-linux-x86_64.AppImage) |
| **Linux (Debian)** | [Auto-Claude-2.8.0-beta.5-linux-amd64.deb](https://github.com/AndyMik90/Auto-Claude/releases/download/v2.8.0-beta.5/Auto-Claude-2.8.0-beta.5-linux-amd64.deb) |
| **Linux (Flatpak)** | [Auto-Claude-2.8.0-beta.5-linux-x86_64.flatpak](https://github.com/AndyMik90/Auto-Claude/releases/download/v2.8.0-beta.5/Auto-Claude-2.8.0-beta.5-linux-x86_64.flatpak) |
| **Windows** | [Aperant-2.8.0-beta.5-win32-x64.exe](https://github.com/AndyMik90/Auto-Claude/releases/download/v2.8.0-beta.5/Aperant-2.8.0-beta.5-win32-x64.exe) |
| **macOS (Apple Silicon)** | [Aperant-2.8.0-beta.5-darwin-arm64.dmg](https://github.com/AndyMik90/Auto-Claude/releases/download/v2.8.0-beta.5/Aperant-2.8.0-beta.5-darwin-arm64.dmg) |
| **macOS (Intel)** | [Aperant-2.8.0-beta.5-darwin-x64.dmg](https://github.com/AndyMik90/Auto-Claude/releases/download/v2.8.0-beta.5/Aperant-2.8.0-beta.5-darwin-x64.dmg) |
| **Linux** | [Aperant-2.8.0-beta.5-linux-x86_64.AppImage](https://github.com/AndyMik90/Auto-Claude/releases/download/v2.8.0-beta.5/Aperant-2.8.0-beta.5-linux-x86_64.AppImage) |
| **Linux (Debian)** | [Aperant-2.8.0-beta.5-linux-amd64.deb](https://github.com/AndyMik90/Auto-Claude/releases/download/v2.8.0-beta.5/Aperant-2.8.0-beta.5-linux-amd64.deb) |
| **Linux (Flatpak)** | [Aperant-2.8.0-beta.5-linux-x86_64.flatpak](https://github.com/AndyMik90/Auto-Claude/releases/download/v2.8.0-beta.5/Aperant-2.8.0-beta.5-linux-x86_64.flatpak) |
<!-- BETA_DOWNLOADS_END -->
> All releases include SHA256 checksums and VirusTotal scan results for security verification.
+48 -48
View File
@@ -50,27 +50,27 @@
"typecheck": "tsc --noEmit --incremental"
},
"dependencies": {
"@ai-sdk/amazon-bedrock": "^4.0.61",
"@ai-sdk/anthropic": "^3.0.45",
"@ai-sdk/azure": "^3.0.31",
"@ai-sdk/google": "^3.0.29",
"@ai-sdk/groq": "^3.0.24",
"@ai-sdk/mcp": "^1.0.21",
"@ai-sdk/mistral": "^2.0.28",
"@ai-sdk/openai": "^3.0.30",
"@ai-sdk/openai-compatible": "^2.0.30",
"@ai-sdk/xai": "^3.0.57",
"@anthropic-ai/sdk": "^0.71.2",
"@ai-sdk/amazon-bedrock": "^4.0.77",
"@ai-sdk/anthropic": "^3.0.58",
"@ai-sdk/azure": "^3.0.42",
"@ai-sdk/google": "^3.0.43",
"@ai-sdk/groq": "^3.0.29",
"@ai-sdk/mcp": "^1.0.25",
"@ai-sdk/mistral": "^3.0.24",
"@ai-sdk/openai": "^3.0.41",
"@ai-sdk/openai-compatible": "^2.0.35",
"@ai-sdk/xai": "^3.0.67",
"@anthropic-ai/sdk": "^0.78.0",
"@dnd-kit/core": "^6.3.1",
"@dnd-kit/sortable": "^10.0.0",
"@dnd-kit/utilities": "^3.2.2",
"@libsql/client": "^0.17.0",
"@lydell/node-pty": "^1.1.0",
"@modelcontextprotocol/sdk": "^1.26.0",
"@openrouter/ai-sdk-provider": "^2.2.3",
"@modelcontextprotocol/sdk": "^1.27.1",
"@openrouter/ai-sdk-provider": "^2.3.1",
"@radix-ui/react-alert-dialog": "^1.1.15",
"@radix-ui/react-checkbox": "^1.1.4",
"@radix-ui/react-collapsible": "^1.1.3",
"@radix-ui/react-checkbox": "^1.3.3",
"@radix-ui/react-collapsible": "^1.1.12",
"@radix-ui/react-dialog": "^1.1.15",
"@radix-ui/react-dropdown-menu": "^2.1.16",
"@radix-ui/react-popover": "^1.1.15",
@@ -84,78 +84,78 @@
"@radix-ui/react-tabs": "^1.1.13",
"@radix-ui/react-toast": "^1.2.15",
"@radix-ui/react-tooltip": "^1.2.8",
"@sentry/electron": "^7.5.0",
"@sentry/electron": "^7.10.0",
"@tailwindcss/typography": "^0.5.19",
"@tanstack/react-virtual": "^3.13.13",
"@tanstack/react-virtual": "^3.13.22",
"@tavily/core": "^0.7.2",
"@xterm/addon-fit": "^0.11.0",
"@xterm/addon-serialize": "^0.14.0",
"@xterm/addon-web-links": "^0.12.0",
"@xterm/addon-webgl": "^0.19.0",
"@xterm/xterm": "^6.0.0",
"ai": "^6.0.91",
"ai": "^6.0.116",
"chokidar": "^5.0.0",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"dotenv": "^17.2.3",
"dotenv": "^17.3.1",
"electron-log": "^5.4.3",
"electron-updater": "^6.6.2",
"i18next": "^25.7.3",
"lucide-react": "^0.562.0",
"minimatch": "^10.1.1",
"motion": "^12.23.26",
"electron-updater": "^6.8.3",
"i18next": "^25.8.18",
"lucide-react": "^0.577.0",
"minimatch": "^10.2.4",
"motion": "^12.36.0",
"proper-lockfile": "^4.1.2",
"react": "^19.2.3",
"react-dom": "^19.2.3",
"react-i18next": "^16.5.0",
"react": "^19.2.4",
"react-dom": "^19.2.4",
"react-i18next": "^16.5.8",
"react-markdown": "^10.1.0",
"rehype-raw": "^7.0.0",
"rehype-sanitize": "^6.0.0",
"remark-gfm": "^4.0.1",
"semver": "^7.7.3",
"tailwind-merge": "^3.4.0",
"semver": "^7.7.4",
"tailwind-merge": "^3.5.0",
"uuid": "^13.0.0",
"web-tree-sitter": "^0.26.5",
"xstate": "^5.26.0",
"zod": "^4.2.1",
"zustand": "^5.0.9"
"web-tree-sitter": "^0.26.7",
"xstate": "^5.28.0",
"zod": "^4.3.6",
"zustand": "^5.0.11"
},
"devDependencies": {
"@biomejs/biome": "2.3.11",
"@biomejs/biome": "2.4.7",
"@electron-toolkit/preload": "^3.0.2",
"@electron-toolkit/utils": "^4.0.0",
"@electron/rebuild": "^4.0.2",
"@playwright/test": "^1.52.0",
"@tailwindcss/postcss": "^4.1.17",
"@testing-library/dom": "^10.0.0",
"@electron/rebuild": "^4.0.3",
"@playwright/test": "^1.58.2",
"@tailwindcss/postcss": "^4.2.1",
"@testing-library/dom": "^10.4.1",
"@testing-library/jest-dom": "^6.9.1",
"@testing-library/react": "^16.1.0",
"@testing-library/react": "^16.3.2",
"@types/minimatch": "^6.0.0",
"@types/node": "^25.0.0",
"@types/react": "^19.2.7",
"@types/node": "^25.5.0",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"@types/semver": "^7.7.1",
"@types/uuid": "^11.0.0",
"@vitejs/plugin-react": "^5.1.2",
"@vitest/coverage-v8": "^4.1.0",
"autoprefixer": "^10.4.22",
"autoprefixer": "^10.4.27",
"cross-env": "^10.1.0",
"electron": "40.0.0",
"electron-builder": "^26.4.0",
"electron-builder": "^26.8.1",
"electron-vite": "^5.0.0",
"husky": "^9.1.7",
"jsdom": "^27.3.0",
"lint-staged": "^16.2.7",
"postcss": "^8.5.6",
"tailwindcss": "^4.1.17",
"lint-staged": "^16.4.0",
"postcss": "^8.5.8",
"tailwindcss": "^4.2.1",
"typescript": "^5.9.3",
"vite": "^7.2.7",
"vitest": "^4.0.16"
"vitest": "^4.1.0"
},
"overrides": {
"electron-builder-squirrel-windows": "^26.0.12",
"dmg-builder": "^26.0.12",
"@electron/rebuild": "4.0.2"
"@electron/rebuild": "4.0.3"
},
"build": {
"appId": "com.aperant.app",
@@ -67,8 +67,8 @@ vi.mock('../claude-profile/profile-utils', () => ({
getEmailFromConfigDir: vi.fn(),
}));
vi.mock('./output-parser', () => ({}));
vi.mock('./session-handler', () => ({}));
vi.mock('../terminal/output-parser', () => ({}));
vi.mock('../terminal/session-handler', () => ({}));
vi.mock('./pty-manager', () => ({
writeToPty: vi.fn(),
@@ -933,6 +933,20 @@ export function registerSettingsHandlers(
try {
const settings = readSettingsFile() ?? {};
const accounts: ProviderAccount[] = (settings.providerAccounts as ProviderAccount[] | undefined) ?? [];
// Prevent duplicate: same email + provider already registered
if (account.email) {
const duplicate = accounts.find(
(a) => a.provider === account.provider && a.email?.toLowerCase() === account.email!.toLowerCase()
);
if (duplicate) {
return {
success: false,
error: `DUPLICATE_EMAIL:${duplicate.name}`,
};
}
}
const now = Date.now();
const newAccount: ProviderAccount = {
...account,
@@ -94,9 +94,6 @@ export interface TerminalAPI {
submitOAuthCode: (terminalId: string, code: string) => Promise<IPCResult>;
onTerminalClaudeBusy: (callback: (id: string, isBusy: boolean) => void) => () => void;
onTerminalClaudeExit: (callback: (id: string) => void) => () => void;
onTerminalOnboardingComplete: (
callback: (info: { terminalId: string; profileId?: string; detectedAt: string }) => void
) => () => void;
onTerminalPendingResume: (callback: (id: string, sessionId?: string) => void) => () => void;
onTerminalProfileChanged: (callback: (event: TerminalProfileChangedEvent) => void) => () => void;
@@ -395,21 +392,6 @@ export const createTerminalAPI = (): TerminalAPI => ({
};
},
onTerminalOnboardingComplete: (
callback: (info: { terminalId: string; profileId?: string; detectedAt: string }) => void
): (() => void) => {
const handler = (
_event: Electron.IpcRendererEvent,
info: { terminalId: string; profileId?: string; detectedAt: string }
): void => {
callback(info);
};
ipcRenderer.on(IPC_CHANNELS.TERMINAL_ONBOARDING_COMPLETE, handler);
return () => {
ipcRenderer.removeListener(IPC_CHANNELS.TERMINAL_ONBOARDING_COMPLETE, handler);
};
},
onTerminalPendingResume: (
callback: (id: string, sessionId?: string) => void
): (() => void) => {
@@ -110,6 +110,20 @@ export function AddAccountDialog({
}
}, [open, editAccount, provider, billingModelOverride]);
// Parse DUPLICATE_EMAIL error from backend and show user-friendly toast
const handleDuplicateEmailError = useCallback((error: string): boolean => {
if (error.startsWith('DUPLICATE_EMAIL:')) {
const existingName = error.slice('DUPLICATE_EMAIL:'.length);
toast({
variant: 'destructive',
title: t('providers.dialog.toast.error'),
description: t('providers.dialog.toast.duplicateEmail', { existingName }),
});
return true;
}
return false;
}, [toast, t]);
const isOAuthOnly = (provider === 'anthropic' || provider === 'openai') && authType === 'oauth';
const isCodexOAuth = provider === 'openai' && authType === 'oauth';
@@ -190,10 +204,16 @@ export function AddAccountDialog({
: t('providers.dialog.toast.added'),
description: name.trim(),
});
} else if (result.error && !handleDuplicateEmailError(result.error)) {
toast({
variant: 'destructive',
title: t('providers.dialog.toast.error'),
description: result.error,
});
}
};
autoSave();
}, [oauthStatus, isCodexOAuth, accountSaved, name, provider, oauthProfileId, isEditing, editAccount, oauthEmail, addProviderAccount, updateProviderAccount, toast, t, refreshUsageData]);
}, [oauthStatus, isCodexOAuth, accountSaved, name, provider, oauthProfileId, isEditing, editAccount, oauthEmail, addProviderAccount, updateProviderAccount, handleDuplicateEmailError, toast, t, refreshUsageData]);
const canSave = () => {
if (!name.trim()) return false;
@@ -264,8 +284,14 @@ export function AddAccountDialog({
description: name.trim(),
});
await refreshUsageData();
onOpenChange(false);
} else if (saveResult.error && !handleDuplicateEmailError(saveResult.error)) {
toast({
variant: 'destructive',
title: t('providers.dialog.toast.error'),
description: saveResult.error,
});
}
onOpenChange(false);
}, 800);
} else {
setOauthStatus('error');
@@ -318,7 +344,7 @@ export function AddAccountDialog({
setOauthStatus('error');
setOauthError(err instanceof Error ? err.message : 'Unexpected error');
}
}, [name, t, toast, isCodexOAuth, isEditing, editAccount, provider, addProviderAccount, updateProviderAccount, onOpenChange, refreshUsageData]);
}, [name, t, toast, isCodexOAuth, isEditing, editAccount, provider, addProviderAccount, updateProviderAccount, handleDuplicateEmailError, onOpenChange, refreshUsageData]);
const handleFallbackTerminal = useCallback(async () => {
if (!name.trim()) {
@@ -342,7 +368,7 @@ export function AddAccountDialog({
createdAt: new Date(),
});
if (!profileResult.success || !profileResult.data) {
toast({ variant: 'destructive', title: 'Failed to create profile' });
toast({ variant: 'destructive', title: t('providers.dialog.toast.createProfileFailed') });
return;
}
profileId = profileResult.data.id;
@@ -352,7 +378,7 @@ export function AddAccountDialog({
// Get terminal config for embedded AuthTerminal
const authResult = await window.electronAPI.authenticateClaudeProfile(profileId);
if (!authResult.success || !authResult.data) {
toast({ variant: 'destructive', title: authResult.error ?? 'Failed to prepare terminal' });
toast({ variant: 'destructive', title: authResult.error ?? t('providers.dialog.toast.authPrepareFailed') });
return;
}
@@ -362,7 +388,7 @@ export function AddAccountDialog({
} catch (err) {
toast({
variant: 'destructive',
title: err instanceof Error ? err.message : 'Unexpected error',
title: err instanceof Error ? err.message : t('providers.dialog.toast.unexpectedError'),
});
}
}, [name, oauthProfileId, t, toast]);
@@ -421,7 +447,7 @@ export function AddAccountDialog({
description: name.trim(),
});
onOpenChange(false);
} else {
} else if (result.error && !handleDuplicateEmailError(result.error)) {
toast({
variant: 'destructive',
title: t('providers.dialog.toast.error'),
@@ -104,7 +104,6 @@ export const terminalMock = {
onTerminalAuthCreated: () => () => {},
onTerminalClaudeBusy: () => () => {},
onTerminalClaudeExit: () => () => {},
onTerminalOnboardingComplete: () => () => {},
onTerminalPendingResume: () => () => {},
onTerminalProfileChanged: () => () => {},
onTerminalOAuthCodeNeeded: () => () => {},
-1
View File
@@ -110,7 +110,6 @@ export const IPC_CHANNELS = {
TERMINAL_OAUTH_CODE_SUBMIT: 'terminal:oauthCodeSubmit', // User submitted OAuth code to send to terminal
TERMINAL_CLAUDE_BUSY: 'terminal:claudeBusy', // Claude Code busy state (for visual indicator)
TERMINAL_CLAUDE_EXIT: 'terminal:claudeExit', // Claude Code exited (returned to shell)
TERMINAL_ONBOARDING_COMPLETE: 'terminal:onboardingComplete', // Claude onboarding complete (ready for input after login)
TERMINAL_PROFILE_CHANGED: 'terminal:profileChanged', // Profile changed, terminals need refresh (main -> renderer)
// Claude profile management (multi-account support)
@@ -674,11 +674,9 @@
"authFailed": "Authentication failed",
"connecting": "Connecting...",
"authenticate": "Authenticate: {{profileName}}",
"completeSetup": "Complete setup: {{profileName}}",
"authenticatedAs": "Authenticated as {{email}}",
"authenticated": "Authenticated!",
"authError": "Authentication Error",
"onboardingMessage": "Token received! Complete the Claude Code setup below - this window will close automatically when done.",
"successMessage": "Authentication successful! Closing..."
},
"profileCreated": {
@@ -810,7 +810,11 @@
"toast": {
"added": "Account added",
"updated": "Account updated",
"error": "Failed to save account"
"error": "Failed to save account",
"duplicateEmail": "This email is already registered as \"{{existingName}}\"",
"createProfileFailed": "Failed to create profile",
"authPrepareFailed": "Failed to prepare terminal",
"unexpectedError": "Unexpected error"
}
},
"toast": {
@@ -674,11 +674,9 @@
"authFailed": "Échec de l'authentification",
"connecting": "Connexion...",
"authenticate": "Authentifier : {{profileName}}",
"completeSetup": "Terminer la configuration : {{profileName}}",
"authenticatedAs": "Authentifié en tant que {{email}}",
"authenticated": "Authentifié !",
"authError": "Erreur d'authentification",
"onboardingMessage": "Jeton reçu ! Terminez la configuration de Claude Code ci-dessous - cette fenêtre se fermera automatiquement une fois terminé.",
"successMessage": "Authentification réussie ! Fermeture..."
},
"profileCreated": {
@@ -810,7 +810,11 @@
"toast": {
"added": "Compte ajouté",
"updated": "Compte mis à jour",
"error": "Échec de l'enregistrement du compte"
"error": "Échec de l'enregistrement du compte",
"duplicateEmail": "Cet e-mail est déjà enregistré sous \"{{existingName}}\"",
"createProfileFailed": "Échec de la création du profil",
"authPrepareFailed": "Échec de la préparation du terminal",
"unexpectedError": "Erreur inattendue"
}
},
"toast": {
-6
View File
@@ -311,12 +311,6 @@ export interface ElectronAPILegacy {
onTerminalClaudeBusy: (callback: (id: string, isBusy: boolean) => void) => () => void;
/** Listen for Claude exit (user closed Claude within terminal, returned to shell) */
onTerminalClaudeExit: (callback: (id: string) => void) => () => void;
/** Listen for onboarding complete (Claude shows ready state after login/onboarding) */
onTerminalOnboardingComplete: (callback: (info: {
terminalId: string;
profileId?: string;
detectedAt: string;
}) => void) => () => void;
/** Listen for pending Claude resume notifications (for deferred resume on tab activation) */
onTerminalPendingResume: (callback: (id: string, sessionId?: string) => void) => () => void;
/** Listen for profile change events - terminals need to be recreated with new profile env vars */
+1123 -949
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -45,6 +45,6 @@
"lucide-react": "^0.562.0"
},
"overrides": {
"@electron/rebuild": "4.0.2"
"@electron/rebuild": "4.0.3"
}
}