feat: complete rebranding to Gemini Nexus with improved UI, proxies and documentation

This commit is contained in:
Gemini Nexus Bot committed 2026-04-22 00:00:56 -03:00
1 parent e524a340cb
commit 433dccbaa9
108 files changed
+1004 -1139

No files matched your search

+4 -4
View File
@@ -4,10 +4,10 @@ export function isProcessRunning() {
return ipc.client.proc.isProcessRunning();
}
export function closeAntigravity() {
return ipc.client.proc.closeAntigravity();
export function closeGeminiNexus() {
return ipc.client.proc.closeGeminiNexus();
}
export function startAntigravity() {
return ipc.client.proc.startAntigravity();
export function startGeminiNexus() {
return ipc.client.proc.startGeminiNexus();
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 390 KiB

+113
View File
@@ -0,0 +1,113 @@
import React, { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useCloudAccounts, useSetAccountProxy } from '@/hooks/useCloudAccounts';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Input } from '@/components/ui/input';
import { isValidProxyUrl } from '@/utils/url';
import { Loader2 } from 'lucide-react';
import { CloudAccount } from '@/types/cloudAccount';
function AccountProxyItem({ account }: { account: CloudAccount }) {
const { t } = useTranslation();
const setAccountProxy = useSetAccountProxy();
const [proxyUrl, setProxyUrl] = useState(account.proxy_url || '');
const [proxySaved, setProxySaved] = useState(false);
return (
<div className="flex items-center justify-between gap-4 py-3 border-b last:border-0">
<div className="flex items-center gap-3 overflow-hidden">
{account.avatar_url ? (
<img
src={account.avatar_url}
alt={account.name || ''}
className="bg-muted h-8 w-8 rounded-full border shrink-0"
/>
) : (
<div className="bg-primary/10 text-primary flex h-8 w-8 items-center justify-center rounded-full border shrink-0">
{account.name?.[0]?.toUpperCase() || 'A'}
</div>
)}
<div className="flex flex-col overflow-hidden">
<span className="truncate text-sm font-semibold">
{account.name || t('cloud.card.unknown')}
</span>
<span className="truncate text-xs text-muted-foreground">{account.email}</span>
</div>
</div>
<div className="flex items-center gap-2">
<Input
value={proxyUrl}
onChange={(e) => {
setProxyUrl(e.target.value);
setProxySaved(false);
}}
onBlur={() => {
const trimmed = proxyUrl.trim();
if (trimmed && !isValidProxyUrl(trimmed)) {
setProxyUrl(account.proxy_url || '');
return;
}
if (trimmed !== (account.proxy_url || '')) {
setAccountProxy.mutate({
accountId: account.id,
proxyUrl: trimmed || null,
});
setProxySaved(true);
setTimeout(() => setProxySaved(false), 2000);
}
}}
placeholder={t('cloud.card.proxyPlaceholder') || 'e.g. http://127.0.0.1:7890'}
className="h-8 w-48 text-sm"
onKeyDown={(e) => {
if (e.key === 'Enter') {
e.currentTarget.blur();
}
}}
/>
<div className="w-16">
{proxySaved && (
<span className="text-[10px] text-green-500 whitespace-nowrap">{t('cloud.card.proxySaved')}</span>
)}
</div>
</div>
</div>
);
}
export function AccountProxySettings() {
const { t } = useTranslation();
const { data: accounts, isLoading } = useCloudAccounts();
if (isLoading) {
return (
<Card>
<CardHeader>
<CardTitle>{t('settings.proxy.accountProxies.title')}</CardTitle>
<CardDescription>{t('settings.proxy.accountProxies.description')}</CardDescription>
</CardHeader>
<CardContent className="flex justify-center p-6">
<Loader2 className="animate-spin text-muted-foreground" />
</CardContent>
</Card>
);
}
if (!accounts || accounts.length === 0) {
return null;
}
return (
<Card>
<CardHeader>
<CardTitle>{t('settings.proxy.accountProxies.title')}</CardTitle>
<CardDescription>{t('settings.proxy.accountProxies.description')}</CardDescription>
</CardHeader>
<CardContent className="flex flex-col">
{accounts.map((account) => (
<AccountProxyItem key={account.id} account={account} />
))}
</CardContent>
</Card>
);
}
+1 -41
View File
@@ -35,9 +35,6 @@ import {
getQuotaStatus,
type QuotaStatus,
} from '@/utils/quota-display';
import { useState } from 'react';
import { useSetAccountProxy } from '@/hooks/useCloudAccounts';
import { isValidProxyUrl } from '@/utils/url';
import { getValidationBlockedStatusLabel } from '@/components/accountValidationStatus';
type ModelQuotaEntry = [string, CloudQuotaModelInfo];
@@ -186,9 +183,6 @@ export function CloudAccountCard({
isProviderCollapsed,
toggleProviderCollapse,
} = useProviderGrouping();
const setAccountProxy = useSetAccountProxy();
const [proxyUrl, setProxyUrl] = useState(account.proxy_url || '');
const [proxySaved, setProxySaved] = useState(false);
const getQuotaTextColorClass = (percentage: number) => {
const quotaStatus = getQuotaStatus(percentage);
@@ -558,45 +552,11 @@ export function CloudAccountCard({
</div>
</CardContent>
<CardFooter className="bg-muted/20 text-muted-foreground justify-between border-t p-2 px-4 text-xs">
<CardFooter className="bg-muted/20 text-muted-foreground justify-center border-t p-2 px-4 text-xs">
<span>
{t('cloud.card.used')}{' '}
{formatDistanceToNow(account.last_used * 1000, { addSuffix: true })}
</span>
<div className="flex items-center gap-2">
<Input
value={proxyUrl}
onChange={(e) => {
setProxyUrl(e.target.value);
setProxySaved(false);
}}
onBlur={() => {
const trimmed = proxyUrl.trim();
if (trimmed && !isValidProxyUrl(trimmed)) {
setProxyUrl(account.proxy_url || '');
return;
}
if (trimmed !== (account.proxy_url || '')) {
setAccountProxy.mutate({
accountId: account.id,
proxyUrl: trimmed || null,
});
setProxySaved(true);
setTimeout(() => setProxySaved(false), 2000);
}
}}
placeholder={t('cloud.card.proxyPlaceholder')}
className="h-6 w-40 text-xs"
onKeyDown={(e) => {
if (e.key === 'Enter') {
e.currentTarget.blur();
}
}}
/>
{proxySaved && (
<span className="text-[10px] text-green-500">{t('cloud.card.proxySaved')}</span>
)}
</div>
</CardFooter>
</Card>
);
+29 -7
View File
@@ -9,7 +9,7 @@ import { Checkbox } from '@/components/ui/checkbox';
import { Badge } from '@/components/ui/badge';
import { Switch } from '@/components/ui/switch';
import { Label } from '@/components/ui/label';
import { Loader2, Search, RotateCcw, Save } from 'lucide-react';
import { Loader2, Search, RotateCcw, Save, Copy, CheckCircle } from 'lucide-react';
import { filter, flatMap, includes, size, sortBy, sumBy, uniq, values } from 'lodash-es';
import type { CloudAccount } from '@/types/cloudAccount';
@@ -44,6 +44,7 @@ export function ModelVisibilitySettings() {
const [modelVisibilityMap, setModelVisibilityMap] = useState<Record<string, boolean>>({});
const [providerGroupingEnabled, setProviderGroupingEnabled] = useState(false);
const [isSavingSettings, setIsSavingSettings] = useState(false);
const [copiedModel, setCopiedModel] = useState<string | null>(null);
// Initialize model visibility and provider groupings from config
useEffect(() => {
@@ -114,6 +115,14 @@ export function ModelVisibilitySettings() {
}));
};
const copyModelId = (modelId: string, e: React.MouseEvent) => {
e.preventDefault();
e.stopPropagation();
navigator.clipboard.writeText(modelId);
setCopiedModel(modelId);
setTimeout(() => setCopiedModel(null), 2000);
};
if (accountsLoading) {
return (
<Card>
@@ -188,12 +197,25 @@ export function ModelVisibilitySettings() {
handleModelVisibilityChange(modelId, checked as boolean)
}
/>
<label
htmlFor={`model-${modelId}`}
className="flex-1 cursor-pointer text-sm font-medium"
>
{modelId}
</label>
<div className="flex flex-1 items-center gap-2">
<label
htmlFor={`model-${modelId}`}
className="cursor-pointer text-sm font-medium"
>
{modelId}
</label>
<button
onClick={(e) => copyModelId(modelId, e)}
className="text-muted-foreground hover:text-foreground transition-colors"
title={t('proxy.copy')}
>
{copiedModel === modelId ? (
<CheckCircle className="h-4 w-4 text-green-500" />
) : (
<Copy className="h-4 w-4" />
)}
</button>
</div>
{!isVisible && (
<Badge variant="secondary" className="text-xs">
{t('settings.modelVisibility.hidden')}
+3 -3
View File
@@ -1,6 +1,6 @@
import React from 'react';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { isProcessRunning, startAntigravity, closeAntigravity } from '@/actions/process';
import { isProcessRunning, startGeminiNexus, closeGeminiNexus } from '@/actions/process';
import { Button } from '@/components/ui/button';
import { cn } from '@/lib/utils';
import { Play, Square, Loader2, Power } from 'lucide-react';
@@ -22,14 +22,14 @@ export const StatusBar: React.FC<StatusBarProps> = ({ isCollapsed = false }) =>
});
const startMutation = useMutation({
mutationFn: startAntigravity,
mutationFn: startGeminiNexus,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['process', 'status'] });
},
});
const stopMutation = useMutation({
mutationFn: closeAntigravity,
mutationFn: closeGeminiNexus,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['process', 'status'] });
},
+1 -1
View File
@@ -23,7 +23,7 @@ function getQuickConfig() {
if (getQuickConfig()) {
Sentry.init({
dsn: process.env.SENTRY_DSN,
release: `antigravity-manager@${app.getVersion()}`,
release: `gemini-nexus@${app.getVersion()}`,
beforeSend(event) {
if (event.exception?.values?.[0]?.value) {
event.exception.values[0].value = event.exception.values[0].value.replace(
+1 -1
View File
@@ -130,7 +130,7 @@ export async function addAccountSnapshot(): Promise<Account> {
const info = getCurrentAccountInfo();
if (!info.isAuthenticated) {
const errorMsg =
'No authenticated account found. Please ensure Antigravity is running and you are logged in.';
'No authenticated account found. Please ensure Gemini Nexus is running and you are logged in.';
logger.error(errorMsg);
throw new Error(errorMsg);
}
+1 -1
View File
@@ -77,7 +77,7 @@ export class AuthServer {
<html>
<body style="font-family: sans-serif; text-align: center; padding-top: 50px;">
<h1>Login Successful</h1>
<p>You can close this window and return to Antigravity Manager.</p>
<p>You can close this window and return to Gemini Nexus.</p>
<script>
setTimeout(() => window.close(), 3000);
</script>
+3 -3
View File
@@ -17,7 +17,7 @@ import {
readCurrentDeviceProfile,
saveGlobalOriginalProfile,
} from '../../ipc/device/handler';
import { getAntigravityDbPaths } from '../../utils/paths';
import { getGeminiNexusDbPaths } from '../../utils/paths';
import { runWithSwitchGuard } from '../../ipc/switchGuard';
import { executeSwitchFlow } from '../../ipc/switchFlow';
import type { DeviceProfile, DeviceProfilesSnapshot } from '../../types/account';
@@ -37,7 +37,7 @@ function notifyTrayUpdate(account: CloudAccount) {
const ACTIVE_OAUTH_CLIENT_KEY_SETTING = 'active_oauth_client_key';
const OAUTH_CLIENT_KEY_BACKFILL_DONE_SETTING = 'oauth_client_key_backfill_v1_done';
const ENTERPRISE_OAUTH_CLIENT_KEY = 'antigravity_enterprise';
const ENTERPRISE_OAUTH_CLIENT_KEY = 'geminiNexus_enterprise';
function isEnterpriseClient(clientKey?: string): boolean {
if (!clientKey) {
@@ -548,7 +548,7 @@ export async function switchCloudAccount(accountId: string): Promise<void> {
processExitTimeoutMs: 10000,
performSwitch: async () => {
// 3. Backup Database (Optimized to avoid race conditions)
const dbPaths = getAntigravityDbPaths();
const dbPaths = getGeminiNexusDbPaths();
for (const dbPath of dbPaths) {
try {
const backupPath = `${dbPath}.backup`;
+2 -2
View File
@@ -86,7 +86,7 @@ export function toSyncLocalAccountORPCError(error: unknown): ORPCError<string, u
normalized.includes('unauthenticated') ||
normalized.includes('unauthorized') ||
normalized.includes('token may be expired') ||
normalized.includes('re-login in antigravity ide')
normalized.includes('re-login in geminiNexus ide')
) {
return new ORPCError('UNAUTHORIZED', { message });
}
@@ -94,7 +94,7 @@ export function toSyncLocalAccountORPCError(error: unknown): ORPCError<string, u
if (
normalized.includes('no cloud account found in ide') ||
normalized.includes('no oauth token found in ide state') ||
normalized.includes('antigravity database not found')
normalized.includes('geminiNexus database not found')
) {
return new ORPCError('BAD_REQUEST', { message });
}
+25 -25
View File
@@ -5,7 +5,7 @@ import { v4 as uuidv4 } from 'uuid';
import { desc, eq } from 'drizzle-orm';
import type { BetterSQLite3Database } from 'drizzle-orm/better-sqlite3';
import { isNumber, isObjectLike, isPlainObject, isString } from 'lodash-es';
import { getCloudAccountsDbPath, getAntigravityDbPaths } from '../../utils/paths';
import { getCloudAccountsDbPath, getGeminiNexusDbPaths } from '../../utils/paths';
import { logger } from '../../utils/logger';
import {
CloudAccount,
@@ -18,7 +18,7 @@ import { ItemTableValueRowSchema, TableInfoRowSchema } from '../../types/db';
import { decryptWithMigration, encrypt, type KeySource } from '../../utils/security';
import { ProtobufUtils } from '../../utils/protobuf';
import { GoogleAPIService } from '../../services/GoogleAPIService';
import { getAntigravityVersion, isNewVersion } from '../../utils/antigravityVersion';
import { getGeminiNexusVersion, isNewVersion } from '../../utils/geminiNexusVersion';
import { parseRow, parseRows } from '../../utils/sqlite';
import { configureDatabase, openDrizzleConnection } from './dbConnection';
import { accounts, itemTable, settings } from './schema';
@@ -1037,9 +1037,9 @@ export class CloudAccountRepo {
apiKey: account.token.access_token,
};
this.upsertItemValue(db, 'antigravityAuthStatus', JSON.stringify(authStatus));
this.upsertItemValue(db, 'antigravityOnboarding', 'true');
db.delete(itemTable).where(eq(itemTable.key, 'google.antigravity')).run();
this.upsertItemValue(db, 'geminiNexusAuthStatus', JSON.stringify(authStatus));
this.upsertItemValue(db, 'geminiNexusOnboarding', 'true');
db.delete(itemTable).where(eq(itemTable.key, 'google.geminiNexus')).run();
}
private static getItemValue(db: DrizzleExecutor, key: string, context: string): string | null {
@@ -1070,8 +1070,8 @@ export class CloudAccountRepo {
const normalizedProjectId = account.token.project_id?.trim();
orm.transaction((tx) => {
this.upsertItemValue(tx, 'antigravityUnifiedStateSync.oauthToken', oauthToken);
this.upsertItemValue(tx, 'antigravityUnifiedStateSync.userStatus', userStatusEntry);
this.upsertItemValue(tx, 'geminiNexusUnifiedStateSync.oauthToken', oauthToken);
this.upsertItemValue(tx, 'geminiNexusUnifiedStateSync.userStatus', userStatusEntry);
if (normalizedProjectId) {
const projectPayload = ProtobufUtils.createStringValuePayload(normalizedProjectId);
const projectEntry = ProtobufUtils.createUnifiedStateEntry(
@@ -1080,13 +1080,13 @@ export class CloudAccountRepo {
);
this.upsertItemValue(
tx,
'antigravityUnifiedStateSync.enterprisePreferences',
'geminiNexusUnifiedStateSync.enterprisePreferences',
projectEntry,
);
} else {
tx
.delete(itemTable)
.where(eq(itemTable.key, 'antigravityUnifiedStateSync.enterprisePreferences'))
.where(eq(itemTable.key, 'geminiNexusUnifiedStateSync.enterprisePreferences'))
.run();
}
this.writeAuthStatusAndCleanup(tx, account);
@@ -1145,8 +1145,8 @@ export class CloudAccountRepo {
private static detectFormatCapability(db: DrizzleExecutor): 'new' | 'old' | null {
const unifiedValue = this.getItemValue(
db,
'antigravityUnifiedStateSync.oauthToken',
'ide.itemTable.antigravityUnifiedStateSync.oauthToken',
'geminiNexusUnifiedStateSync.oauthToken',
'ide.itemTable.geminiNexusUnifiedStateSync.oauthToken',
);
if (unifiedValue) {
return 'new';
@@ -1169,7 +1169,7 @@ export class CloudAccountRepo {
reason: string;
} {
try {
const version = getAntigravityVersion();
const version = getGeminiNexusVersion();
return {
name: isNewVersion(version) ? 'new' : 'old',
reason: `version:${version.shortVersion}`,
@@ -1253,16 +1253,16 @@ export class CloudAccountRepo {
}
static injectCloudToken(account: CloudAccount): void {
const dbPaths = getAntigravityDbPaths();
const dbPaths = getGeminiNexusDbPaths();
const dbPath = dbPaths.find((p) => fs.existsSync(p)) ?? null;
if (!dbPath) {
throw new Error(`Antigravity database not found. Checked paths: ${dbPaths.join(', ')}`);
throw new Error(`Gemini Nexus database not found. Checked paths: ${dbPaths.join(', ')}`);
}
const result = this.injectWithRetry(dbPath, account);
logger.info(
`Successfully injected cloud token and identity for ${account.email} into Antigravity database at ${dbPath} (strategy=${result.strategy}, attempts=${result.attempts}).`,
`Successfully injected cloud token and identity for ${account.email} into Gemini Nexus database at ${dbPath} (strategy=${result.strategy}, attempts=${result.attempts}).`,
);
}
@@ -1312,8 +1312,8 @@ export class CloudAccountRepo {
const enterpriseProjectId = this.readEnterpriseProjectIdFromDb(db);
const unifiedValue = this.getItemValue(
db,
'antigravityUnifiedStateSync.oauthToken',
'ide.itemTable.antigravityUnifiedStateSync.oauthToken',
'geminiNexusUnifiedStateSync.oauthToken',
'ide.itemTable.geminiNexusUnifiedStateSync.oauthToken',
);
let tokenInfo: { accessToken: string; refreshToken: string } | null = null;
@@ -1336,7 +1336,7 @@ export class CloudAccountRepo {
if (!value) {
const errorMsg =
'No cloud account found in IDE. Please login to a Google account in Antigravity IDE first.';
'No cloud account found in IDE. Please login to a Google account in Gemini Nexus IDE first.';
logger.warn(`SyncLocal: ${errorMsg}`);
throw new Error(errorMsg);
}
@@ -1348,7 +1348,7 @@ export class CloudAccountRepo {
if (!tokenInfo) {
const errorMsg =
'No OAuth token found in IDE state. Please login to a Google account in Antigravity IDE first.';
'No OAuth token found in IDE state. Please login to a Google account in Gemini Nexus IDE first.';
logger.warn(`SyncLocal: ${errorMsg}`);
throw new Error(errorMsg);
}
@@ -1387,8 +1387,8 @@ export class CloudAccountRepo {
private static readEnterpriseProjectIdFromDb(db: DrizzleExecutor): string | undefined {
const enterprisePreferencesValue = this.getItemValue(
db,
'antigravityUnifiedStateSync.enterprisePreferences',
'ide.itemTable.antigravityUnifiedStateSync.enterprisePreferences',
'geminiNexusUnifiedStateSync.enterprisePreferences',
'ide.itemTable.geminiNexusUnifiedStateSync.enterprisePreferences',
);
if (!enterprisePreferencesValue) {
return undefined;
@@ -1421,7 +1421,7 @@ export class CloudAccountRepo {
static async syncFromIDE(): Promise<CloudAccount | null> {
// Try all possible database paths
const dbPaths = getAntigravityDbPaths();
const dbPaths = getGeminiNexusDbPaths();
logger.info(`SyncLocal: Checking database paths: ${JSON.stringify(dbPaths)}`);
const dbPath =
@@ -1431,12 +1431,12 @@ export class CloudAccountRepo {
}) ?? null;
if (!dbPath) {
const errorMsg = `Antigravity database not found. Please ensure Antigravity IDE is installed. Checked paths: ${dbPaths.join(', ')}`;
const errorMsg = `Gemini Nexus database not found. Please ensure Gemini Nexus IDE is installed. Checked paths: ${dbPaths.join(', ')}`;
logger.error(errorMsg);
throw new Error(errorMsg);
}
logger.info(`SyncLocal: Using Antigravity database at: ${dbPath}`);
logger.info(`SyncLocal: Using Gemini Nexus database at: ${dbPath}`);
try {
const tokenInfo = this.readTokenInfoWithRetry(dbPath);
@@ -1446,7 +1446,7 @@ export class CloudAccountRepo {
try {
userInfo = await GoogleAPIService.getUserInfo(tokenInfo.accessToken);
} catch (apiError: any) {
const errorMsg = `Failed to validate token with Google API. The token may be expired. Please re-login in Antigravity IDE. Error: ${apiError.message}`;
const errorMsg = `Failed to validate token with Google API. The token may be expired. Please re-login in Gemini Nexus IDE. Error: ${apiError.message}`;
logger.error(`SyncLocal: ${errorMsg}`, apiError);
throw new Error(errorMsg);
}
+17 -17
View File
@@ -6,15 +6,15 @@ import { isString } from 'lodash-es';
import { AccountBackupData, AccountInfo } from '../../types/account';
import { ItemTableValueRowSchema, type ItemTableKey } from '../../types/db';
import { logger } from '../../utils/logger';
import { getAntigravityDbPaths } from '../../utils/paths';
import { getGeminiNexusDbPaths } from '../../utils/paths';
import { parseRow } from '../../utils/sqlite';
import { openDrizzleConnection } from './dbConnection';
import { itemTable } from './schema';
const KEYS_TO_BACKUP: ItemTableKey[] = [
'antigravityAuthStatus',
'geminiNexusAuthStatus',
'jetskiStateSync.agentManagerInitState',
'antigravityUnifiedStateSync.oauthToken',
'geminiNexusUnifiedStateSync.oauthToken',
];
function openIdeDb(dbPath: string, readOnly = false): ReturnType<typeof openDrizzleConnection> {
@@ -31,7 +31,7 @@ function openIdeDb(dbPath: string, readOnly = false): ReturnType<typeof openDriz
*/
export function initDatabase(): void {
try {
const dbPaths = getAntigravityDbPaths();
const dbPaths = getGeminiNexusDbPaths();
if (dbPaths.length === 0) {
return;
}
@@ -86,10 +86,10 @@ function ensureDatabaseExists(dbPath: string): void {
* @returns {ReturnType<typeof openDrizzleConnection>} The database connection.
*/
export function getDatabaseConnection(dbPath?: string): ReturnType<typeof openDrizzleConnection> {
const targetPath = dbPath || getAntigravityDbPaths()[0];
const targetPath = dbPath || getGeminiNexusDbPaths()[0];
if (!targetPath) {
throw new Error('No Antigravity database path found');
throw new Error('No Gemini Nexus database path found');
}
ensureDatabaseExists(targetPath);
@@ -99,7 +99,7 @@ export function getDatabaseConnection(dbPath?: string): ReturnType<typeof openDr
} catch (error: unknown) {
const err = error as { code?: string };
if (err.code === 'SQLITE_BUSY' || err.code === 'SQLITE_LOCKED') {
throw new Error('Database is locked. Please close Antigravity before proceeding.');
throw new Error('Database is locked. Please close Gemini Nexus before proceeding.');
}
throw error;
}
@@ -133,8 +133,8 @@ export function getCurrentAccountInfo(): AccountInfo {
// Query for auth status
const authValue = readItemValue(
orm,
'antigravityAuthStatus',
'ide.itemTable.antigravityAuthStatus',
'geminiNexusAuthStatus',
'ide.itemTable.geminiNexusAuthStatus',
);
let authStatus = null;
if (authValue) {
@@ -160,11 +160,11 @@ export function getCurrentAccountInfo(): AccountInfo {
}
}
// Query for google.antigravity
// Query for google.geminiNexus
const googleValue = readItemValue(
orm,
'google.antigravity',
'ide.itemTable.google.antigravity',
'google.geminiNexus',
'ide.itemTable.google.geminiNexus',
);
let googleState = null;
if (googleValue) {
@@ -175,11 +175,11 @@ export function getCurrentAccountInfo(): AccountInfo {
}
}
// Query for antigravityUserSettings.allUserSettings
// Query for geminiNexusUserSettings.allUserSettings
const settingsValue = readItemValue(
orm,
'antigravityUserSettings.allUserSettings',
'ide.itemTable.antigravityUserSettings.allUserSettings',
'geminiNexusUserSettings.allUserSettings',
'ide.itemTable.geminiNexusUserSettings.allUserSettings',
);
let settingsState = null;
if (settingsValue) {
@@ -273,9 +273,9 @@ export function backupAccount(account: AccountBackupData['account']): AccountBac
* @throws {Error} If the backup data cannot be restored.
*/
export function restoreAccount(backup: AccountBackupData): void {
const dbPaths = getAntigravityDbPaths();
const dbPaths = getGeminiNexusDbPaths();
if (dbPaths.length === 0) {
throw new Error('No Antigravity database paths found');
throw new Error('No Gemini Nexus database paths found');
}
let successCount = 0;
+3 -3
View File
@@ -5,7 +5,7 @@ import { createHash, randomBytes } from 'crypto';
import { isBoolean, isNumber, isObjectLike, isString } from 'lodash-es';
import { type DeviceProfile } from '../../types/account';
import { logger } from '../../utils/logger';
import { getAgentDir, getAntigravityDbPaths, getAntigravityStoragePaths } from '../../utils/paths';
import { getAgentDir, getGeminiNexusDbPaths, getGeminiNexusStoragePaths } from '../../utils/paths';
const GLOBAL_BASELINE_FILE = 'device_original.json';
const SQLITE_RETRY_COUNT = 3;
@@ -508,7 +508,7 @@ export function isIdentityProfileApplyEnabled(): boolean {
}
export function getStoragePath(): string {
const storagePath = resolveExistingPath(getAntigravityStoragePaths());
const storagePath = resolveExistingPath(getGeminiNexusStoragePaths());
if (!storagePath) {
throw new Error('storage_json_not_found');
}
@@ -594,7 +594,7 @@ export function generateDeviceProfile(): DeviceProfile {
}
export function syncStateServiceMachineIdValue(serviceMachineId: string, dbPath?: string): void {
const targetDbPath = dbPath || resolveExistingPath(getAntigravityDbPaths());
const targetDbPath = dbPath || resolveExistingPath(getGeminiNexusDbPaths());
if (!targetDbPath) {
throw new Error('state_vscdb_not_found');
}
+70 -70
View File
@@ -2,7 +2,7 @@ import { exec, execSync, spawn } from 'child_process';
import { promisify } from 'util';
import findProcess, { ProcessInfo } from 'find-process';
import { isNumber } from 'lodash-es';
import { getAntigravityExecutablePath, isWsl } from '../../utils/paths';
import { getGeminiNexusExecutablePath, isWsl } from '../../utils/paths';
import { logger } from '../../utils/logger';
const execAsync = promisify(exec);
@@ -61,24 +61,24 @@ function isPgrepNoMatchError(error: unknown): boolean {
}
const message = error.message.toLowerCase();
const hasPgrep = message.includes('pgrep') && message.includes('antigravity');
const hasPgrep = message.includes('pgrep') && message.includes('geminiNexus');
const code = (error as { code?: number }).code;
return hasPgrep && code === 1;
}
/**
* Checks if the Antigravity process is running.
* Checks if the Gemini Nexus process is running.
* Uses find-process package for robust cross-platform process detection.
* @returns {boolean} True if the Antigravity process is running, false otherwise.
* @returns {boolean} True if the Gemini Nexus process is running, false otherwise.
*/
export async function isProcessRunning(): Promise<boolean> {
try {
const platform = process.platform;
const currentPid = process.pid;
// Use find-process to search for Antigravity processes
// Use find-process to search for Gemini Nexus processes
const allMatches: ProcessInfo[] = [];
const searchNames = ['Antigravity', 'antigravity'];
const searchNames = ['Gemini Nexus', 'geminiNexus'];
if (platform === 'linux') {
searchNames.push('electron');
}
@@ -106,10 +106,10 @@ export async function isProcessRunning(): Promise<boolean> {
const processes = Array.from(processMap.values());
if (processes.length === 0 && sawNoMatch) {
logger.debug('No Antigravity process found (pgrep returned 1)');
logger.debug('No Gemini Nexus process found (pgrep returned 1)');
}
logger.debug(`Found ${processes.length} processes matching 'Antigravity/antigravity'`);
logger.debug(`Found ${processes.length} processes matching 'GeminiNexus/geminiNexus'`);
for (const proc of processes) {
// Skip self
@@ -124,7 +124,7 @@ export async function isProcessRunning(): Promise<boolean> {
if (
name.includes('manager') ||
cmd.includes('manager') ||
cmd.includes('antigravity-manager')
cmd.includes('gemini-nexus')
) {
continue;
}
@@ -135,22 +135,22 @@ export async function isProcessRunning(): Promise<boolean> {
}
if (platform === 'darwin') {
// macOS: Check for Antigravity.app in path
if (cmd.includes('antigravity.app')) {
// macOS: Check for Gemini Nexus.app in path
if (cmd.includes('geminiNexus.app')) {
logger.debug(
`Found Antigravity process: PID=${proc.pid}, name=${name}, cmd=${cmd.substring(0, 100)}`,
`Found Gemini Nexus process: PID=${proc.pid}, name=${name}, cmd=${cmd.substring(0, 100)}`,
);
return true;
}
// Also check if the process name is exactly 'Antigravity' (main process)
if (name === 'antigravity' && !isHelperProcess(name, cmd)) {
logger.debug(`Found Antigravity process: PID=${proc.pid}, name=${name}`);
// Also check if the process name is exactly 'Gemini Nexus' (main process)
if (name === 'geminiNexus' && !isHelperProcess(name, cmd)) {
logger.debug(`Found Gemini Nexus process: PID=${proc.pid}, name=${name}`);
return true;
}
} else if (platform === 'win32') {
// Windows: Check for Antigravity.exe
if (name === 'antigravity.exe' || name === 'antigravity') {
logger.debug(`Found Antigravity process: PID=${proc.pid}, name=${name}`);
// Windows: Check for Gemini Nexus.exe
if (name === 'geminiNexus.exe' || name === 'geminiNexus') {
logger.debug(`Found Gemini Nexus process: PID=${proc.pid}, name=${name}`);
return true;
}
} else {
@@ -159,28 +159,28 @@ export async function isProcessRunning(): Promise<boolean> {
if (nameLower === 'electron') {
// Stricter check for AUR/Electron wrapper:
// Must include antigravity in command line but NOT manager or tools
const isAntigravityApp =
(cmdLower.includes('/antigravity') ||
cmdLower.includes(' antigravity') ||
cmdLower.endsWith('antigravity')) &&
// Must include geminiNexus in command line but NOT manager or tools
const isGeminiNexusApp =
(cmdLower.includes('/geminiNexus') ||
cmdLower.includes(' geminiNexus') ||
cmdLower.endsWith('geminiNexus')) &&
!cmdLower.includes('manager') &&
!cmdLower.includes('tools');
if (isAntigravityApp) {
if (isGeminiNexusApp) {
logger.debug(
`Found Antigravity (AUR/electron) process: PID=${proc.pid}, name=${name}, cmd=${cmd.substring(0, 100)}`,
`Found Gemini Nexus (AUR/electron) process: PID=${proc.pid}, name=${name}, cmd=${cmd.substring(0, 100)}`,
);
return true;
}
}
if (
(name.includes('antigravity') || cmd.includes('/antigravity')) &&
(name.includes('geminiNexus') || cmd.includes('/geminiNexus')) &&
!name.includes('tools')
) {
logger.debug(
`Found Antigravity process: PID=${proc.pid}, name=${name}, cmd=${cmd.substring(0, 100)}`,
`Found Gemini Nexus process: PID=${proc.pid}, name=${name}, cmd=${cmd.substring(0, 100)}`,
);
return true;
}
@@ -195,11 +195,11 @@ export async function isProcessRunning(): Promise<boolean> {
}
/**
* Closes the Antigravity process.
* @returns {boolean} True if the Antigravity process is running, false otherwise.
* Closes the Gemini Nexus process.
* @returns {boolean} True if the Gemini Nexus process is running, false otherwise.
*/
export async function closeAntigravity(): Promise<void> {
logger.info('Closing Antigravity...');
export async function closeGeminiNexus(): Promise<void> {
logger.info('Closing Gemini Nexus...');
const platform = process.platform;
try {
@@ -208,7 +208,7 @@ export async function closeAntigravity(): Promise<void> {
// macOS: Use AppleScript to quit gracefully
try {
logger.info('Attempting graceful exit via AppleScript...');
execSync('osascript -e \'tell application "Antigravity" to quit\'', {
execSync('osascript -e \'tell application "Gemini Nexus" to quit\'', {
stdio: 'ignore',
timeout: 3000,
});
@@ -223,7 +223,7 @@ export async function closeAntigravity(): Promise<void> {
logger.info('Attempting graceful exit via taskkill...');
// /T = Tree (child processes), /IM = Image Name
// We do not wait long here.
execSync('taskkill /IM "Antigravity.exe" /T', {
execSync('taskkill /IM "GeminiNexus.exe" /T', {
stdio: 'ignore',
timeout: 2000,
});
@@ -243,7 +243,7 @@ export async function closeAntigravity(): Promise<void> {
let output = '';
if (platform === 'win32') {
const psCommand = (cmdlet: string) =>
`powershell -NoProfile -Command "${cmdlet} Win32_Process -Filter \\"Name like 'Antigravity%'\\" | Select-Object ProcessId, Name, CommandLine | ConvertTo-Csv -NoTypeInformation"`;
`powershell -NoProfile -Command "${cmdlet} Win32_Process -Filter \\"Name like 'GeminiNexus%'\\" | Select-Object ProcessId, Name, CommandLine | ConvertTo-Csv -NoTypeInformation"`;
try {
output = execSync(psCommand('Get-CimInstance'), {
@@ -304,7 +304,7 @@ export async function closeAntigravity(): Promise<void> {
const pid = parseInt(parts[0]);
if (isNaN(pid)) continue;
const rest = parts.slice(1).join(' ');
if (rest.includes('Antigravity') || rest.includes('antigravity')) {
if (rest.includes('Gemini Nexus') || rest.includes('geminiNexus')) {
processList.push({ pid, name: parts[1], cmd: rest });
}
}
@@ -321,31 +321,31 @@ export async function closeAntigravity(): Promise<void> {
if (p.pid === currentPid) {
return false;
}
// Exclude this electron app (if named Antigravity Manager or antigravity-manager)
if (p.cmd.includes('Antigravity Manager') || p.cmd.includes('antigravity-manager')) {
// Exclude this electron app (if named Gemini Nexus or gemini-nexus)
if (p.cmd.includes('Gemini Nexus') || p.cmd.includes('gemini-nexus')) {
return false;
}
// Match Antigravity (but not manager)
// Match Gemini Nexus (but not manager)
if (platform === 'win32') {
return (
p.cmd.includes('Antigravity.exe') ||
(p.cmd.includes('antigravity') && !p.cmd.includes('manager'))
p.cmd.includes('GeminiNexus.exe') ||
(p.cmd.includes('geminiNexus') && !p.cmd.includes('manager'))
);
} else {
// Explicit !manager check for Linux/macOS to be defensive
return (
(p.cmd.includes('Antigravity') || p.cmd.includes('antigravity')) &&
(p.cmd.includes('Gemini Nexus') || p.cmd.includes('geminiNexus')) &&
!p.cmd.includes('manager')
);
}
});
if (targetProcessList.length === 0) {
logger.info('No Antigravity processes found running.');
logger.info('No Gemini Nexus processes found running.');
return;
}
logger.info(`Found ${targetProcessList.length} remaining Antigravity processes. Killing...`);
logger.info(`Found ${targetProcessList.length} remaining Gemini Nexus processes. Killing...`);
for (const p of targetProcessList) {
try {
@@ -355,13 +355,13 @@ export async function closeAntigravity(): Promise<void> {
}
}
} catch (error) {
logger.error('Error closing Antigravity', error);
logger.error('Error closing Gemini Nexus', error);
// Fallback to simple kill if everything fails
try {
if (platform === 'win32') {
execSync('taskkill /F /IM "Antigravity.exe" /T', { stdio: 'ignore' });
execSync('taskkill /F /IM "GeminiNexus.exe" /T', { stdio: 'ignore' });
} else {
execSync('pkill -9 -f Antigravity', { stdio: 'ignore' });
execSync('pkill -9 -f Gemini Nexus', { stdio: 'ignore' });
}
} catch {
// Ignore
@@ -370,7 +370,7 @@ export async function closeAntigravity(): Promise<void> {
}
/**
* Waits for the Antigravity process to exit.
* Waits for the Gemini Nexus process to exit.
* @param timeoutMs {number} The timeout in milliseconds.
* @returns {Promise<void>} A promise that resolves when the process exits.
*/
@@ -385,7 +385,7 @@ export async function _waitForProcessExit(
}
await new Promise((resolve) => setTimeout(resolve, pollInterval));
}
throw new Error(`Antigravity process did not exit within ${timeoutMs}ms`);
throw new Error(`Gemini Nexus process did not exit within ${timeoutMs}ms`);
}
/**
@@ -418,7 +418,7 @@ async function openUri(uri: string): Promise<boolean> {
}
}
async function waitForAntigravityStartup(
async function waitForGeminiNexusStartup(
timeoutMs = PROCESS_STARTUP_TIMEOUT_MS,
pollIntervalMs = PROCESS_STARTUP_POLL_INTERVAL_MS,
): Promise<boolean> {
@@ -441,15 +441,15 @@ function shouldUseLinuxGpuSafeLaunchArgs(): boolean {
return enableLinuxGpuRaw !== '1' && enableLinuxGpuRaw !== 'true';
}
async function startAntigravityByExecutable(execPath: string): Promise<void> {
async function startGeminiNexusByExecutable(execPath: string): Promise<void> {
if (process.platform === 'darwin') {
await execAsync(`open -a Antigravity`);
await execAsync(`open -a Gemini Nexus`);
return;
}
if (process.platform === 'win32') {
if (!execPath) {
throw new Error('Unable to locate Antigravity executable path');
throw new Error('Unable to locate Gemini Nexus executable path');
}
// Use start command to detach
await execAsync(`start "" "${execPath}"`);
@@ -458,7 +458,7 @@ async function startAntigravityByExecutable(execPath: string): Promise<void> {
if (isWsl()) {
if (!execPath) {
throw new Error('Unable to locate Antigravity executable path');
throw new Error('Unable to locate Gemini Nexus executable path');
}
// In WSL, convert path and use cmd.exe
const winPath = execPath
@@ -470,7 +470,7 @@ async function startAntigravityByExecutable(execPath: string): Promise<void> {
}
if (!execPath) {
throw new Error('Unable to locate Antigravity executable path');
throw new Error('Unable to locate Gemini Nexus executable path');
}
const launchArgs = shouldUseLinuxGpuSafeLaunchArgs() ? [...LINUX_GPU_SAFE_LAUNCH_ARGS] : [];
@@ -486,36 +486,36 @@ async function startAntigravityByExecutable(execPath: string): Promise<void> {
}
/**
* Starts the Antigravity process.
* @param useUri {boolean} Whether to use the URI protocol to start Antigravity.
* Starts the Gemini Nexus process.
* @param useUri {boolean} Whether to use the URI protocol to start Gemini Nexus.
* @returns {Promise<void>} A promise that resolves when the process starts.
*/
export async function startAntigravity(useUri = true): Promise<void> {
logger.info('Starting Antigravity...');
export async function startGeminiNexus(useUri = true): Promise<void> {
logger.info('Starting Gemini Nexus...');
if (await isProcessRunning()) {
logger.info('Antigravity is already running');
logger.info('Gemini Nexus is already running');
return;
}
if (useUri) {
logger.info('Using URI protocol to start...');
const uri = 'antigravity://oauth-success';
const uri = 'geminiNexus://oauth-success';
if (await openUri(uri)) {
logger.info('Antigravity URI launch command sent');
logger.info('Gemini Nexus URI launch command sent');
if (process.platform !== 'linux' || isWsl()) {
return;
}
if (await waitForAntigravityStartup()) {
logger.info('Antigravity process detected after URI launch');
if (await waitForGeminiNexusStartup()) {
logger.info('Gemini Nexus process detected after URI launch');
return;
}
logger.warn(
'URI launch did not keep Antigravity running on Linux. Falling back to executable launch.',
'URI launch did not keep Gemini Nexus running on Linux. Falling back to executable launch.',
);
} else {
logger.warn('URI launch failed, trying executable path...');
@@ -524,22 +524,22 @@ export async function startAntigravity(useUri = true): Promise<void> {
// Fallback to executable path
logger.info('Using executable path to start...');
const execPath = getAntigravityExecutablePath();
const execPath = getGeminiNexusExecutablePath();
try {
await startAntigravityByExecutable(execPath);
logger.info('Antigravity launch command sent');
await startGeminiNexusByExecutable(execPath);
logger.info('Gemini Nexus launch command sent');
if (process.platform === 'linux' && !isWsl()) {
const started = await waitForAntigravityStartup();
const started = await waitForGeminiNexusStartup();
if (!started) {
logger.warn(
'Antigravity launch command completed, but process startup could not be confirmed on Linux.',
'Gemini Nexus launch command completed, but process startup could not be confirmed on Linux.',
);
}
}
} catch (error) {
logger.error('Failed to start Antigravity via executable', error);
logger.error('Failed to start Gemini Nexus via executable', error);
throw error;
}
}
+5 -5
View File
@@ -9,7 +9,7 @@ import { gatewayRouter } from './gateway/router';
import { os } from '@orpc/server';
import { z } from 'zod';
import { isProcessRunning, closeAntigravity, startAntigravity } from './process/handler';
import { isProcessRunning, closeGeminiNexus, startGeminiNexus } from './process/handler';
import { systemHandler } from './system/handler';
import { logger } from '../utils/logger';
@@ -41,11 +41,11 @@ export const router = os.use(logMiddleware).router({
isProcessRunning: os.output(z.boolean()).handler(async () => {
return await isProcessRunning();
}),
closeAntigravity: os.output(z.void()).handler(async () => {
await closeAntigravity();
closeGeminiNexus: os.output(z.void()).handler(async () => {
await closeGeminiNexus();
}),
startAntigravity: os.output(z.void()).handler(async () => {
await startAntigravity();
startGeminiNexus: os.output(z.void()).handler(async () => {
await startGeminiNexus();
}),
}),
+3 -3
View File
@@ -1,6 +1,6 @@
import { type DeviceProfile } from '../types/account';
import { logger } from '../utils/logger';
import { closeAntigravity, startAntigravity, _waitForProcessExit } from './process/handler';
import { closeGeminiNexus, startGeminiNexus, _waitForProcessExit } from './process/handler';
import { applyDeviceProfile } from './device/handler';
import {
type SwitchFailureReason,
@@ -55,7 +55,7 @@ export async function executeSwitchFlow(options: SwitchFlowOptions): Promise<voi
let stage = 'close';
try {
await closeAntigravity();
await closeGeminiNexus();
try {
await _waitForProcessExit(processExitTimeoutMs);
} catch (error) {
@@ -78,7 +78,7 @@ export async function executeSwitchFlow(options: SwitchFlowOptions): Promise<voi
stage = 'switch';
await performSwitch();
stage = 'start';
await startAntigravity();
await startGeminiNexus();
recordSwitchSuccess(scope);
} catch (error) {
const reason = toSwitchFailureReason(stage, error);
+1 -1
View File
@@ -69,7 +69,7 @@ export function initTray(mainWindow: BrowserWindow) {
}
tray = new Tray(icon);
tray.setToolTip('Antigravity Manager');
tray.setToolTip('Gemini Nexus');
tray.on('double-click', () => {
if (globalMainWindow) {
+3 -12
View File
@@ -6,7 +6,6 @@ import {
LayoutDashboard,
Settings,
Network,
Rocket,
ChevronLeft,
ChevronRight,
RefreshCw,
@@ -78,8 +77,8 @@ export const MainLayout: React.FC = () => {
<div className={cn('flex flex-col', isCollapsed ? 'items-center p-4' : 'p-6')}>
<div className="flex items-center gap-2 overflow-hidden whitespace-nowrap">
<div className="bg-primary text-primary-foreground flex h-6 w-6 shrink-0 items-center justify-center rounded">
<Rocket className="h-4 w-4" />
<div className="flex h-6 w-6 shrink-0 items-center justify-center overflow-hidden rounded bg-transparent">
<img src="/src/assets/icon.png" alt="Gemini Nexus Logo" className="h-full w-full object-contain" />
</div>
<div
className={cn(
@@ -87,17 +86,9 @@ export const MainLayout: React.FC = () => {
isCollapsed ? 'w-0 opacity-0' : 'w-auto opacity-100',
)}
>
<h1 className="text-xl font-bold tracking-tight">Antigravity</h1>
<h1 className="text-xl font-bold tracking-tight">Gemini Nexus</h1>
</div>
</div>
<div
className={cn(
'text-muted-foreground mt-1 overflow-hidden text-xs whitespace-nowrap transition-all duration-300',
isCollapsed ? 'h-0 opacity-0' : 'h-auto opacity-100',
)}
>
Manager
</div>
</div>
<nav className="flex-1 space-y-2 px-2">
@@ -397,11 +397,11 @@ function buildSystemInstruction(
system: ClaudeRequest['system'],
): { parts: { text: string }[] } | null {
const assistantIdentityDirective =
'You are Antigravity, a powerful agentic AI coding assistant designed by the Google Deepmind team working on Advanced Agentic Coding.\n' +
'You are Gemini Nexus, a powerful agentic AI coding assistant designed by the Google Deepmind team working on Advanced Agentic Coding.\n' +
'You are pair programming with a USER to solve their coding task. The task may require creating a new codebase, modifying or debugging an existing codebase, or simply answering a question.\n' +
'**Absolute paths only**\n' +
'**Proactiveness**';
const identityMarker = 'You are Antigravity';
const identityMarker = 'You are Gemini Nexus';
const parts: { text: string }[] = [];
@@ -81,7 +81,7 @@ const DYNAMIC_MODEL_FORWARDING_RULES = new Map<string, string>();
export const MODEL_LIST_CREATED_AT = 1770652800;
export const MODEL_LIST_OWNER = 'antigravity';
export const MODEL_LIST_OWNER = 'geminiNexus';
const GEMINI_MODEL_ALIASES: Record<string, string> = {
'gemini-3-pro-image-preview': 'gemini-3-pro-image',
File renamed without changes.
@@ -10,7 +10,7 @@ const QUOTA_API_ENDPOINTS = [
'https://cloudcode-pa.googleapis.com/v1internal:fetchAvailableModels',
] as const;
const CLOUD_CODE_BASE_URL = 'https://cloudcode-pa.googleapis.com';
const USER_AGENT = 'antigravity/1.11.3 Darwin/arm64'; // Keeping the same UA as source
const USER_AGENT = 'geminiNexus/1.11.3 Darwin/arm64'; // Keeping the same UA as source
// Service Class
export class QuotaService {
@@ -41,7 +41,7 @@ export class QuotaService {
{
headers: {
Authorization: `Bearer ${accessToken}`,
'User-Agent': 'antigravity/windows/amd64',
'User-Agent': 'geminiNexus/windows/amd64',
},
},
);
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
+36 -24
View File
@@ -18,7 +18,7 @@ i18n
resources: {
en: {
translation: {
appName: 'Antigravity Manager',
appName: 'Gemini Nexus',
common: {
loading: 'Loading...',
error: 'Error',
@@ -28,8 +28,8 @@ i18n
},
status: {
checking: 'Checking status...',
running: 'Antigravity is running in background',
stopped: 'Antigravity service stopped',
running: 'Gemini Nexus is running in background',
stopped: 'Gemini Nexus service stopped',
},
action: {
stop: 'Stop',
@@ -67,10 +67,10 @@ i18n
},
home: {
title: 'Accounts',
description: 'Manage your Antigravity Google Gemini accounts.',
description: 'Manage your Gemini Nexus Google Gemini accounts.',
noBackups: {
title: 'No backups found',
description: 'Create a backup of your current Antigravity account to get started.',
description: 'Create a backup of your current Gemini Nexus account to get started.',
action: 'Backup Current Account',
},
},
@@ -82,7 +82,7 @@ i18n
models: 'Models',
appearance: {
title: 'Appearance',
description: 'Customize how Antigravity Manager looks on your device.',
description: 'Customize how Gemini Nexus looks on your device.',
},
darkMode: 'Dark Mode',
darkModeDescription: 'Enable dark mode for better viewing at night.',
@@ -152,11 +152,15 @@ i18n
enable: 'Enable Upstream Proxy',
url: 'Proxy URL',
timeout: 'Request Timeout (Seconds)',
accountProxies: {
title: 'Account Proxies',
description: 'Configure specific proxies for individual accounts. These will override the global upstream proxy for that specific account.',
},
},
modelMapping: {
title: 'Model Mapping',
description:
'Map Claude Code models to Antigravity models. Optimize cost and speed by routing requests intelligently.',
'Map Claude Code models to Gemini Nexus models. Optimize cost and speed by routing requests intelligently.',
claudeKeyword: 'Claude Model (Keyword)',
targetGemini: 'Target Gemini Model',
addPlaceholderKey: 'e.g. op-3',
@@ -381,7 +385,7 @@ i18n
pollFailed: 'Failed to poll quota for all accounts',
switched: {
title: 'Account switched!',
description: 'Restarting Antigravity...',
description: 'Restarting Gemini Nexus...',
},
switchFailed: 'Failed to switch account',
deleted: 'Account deleted',
@@ -495,7 +499,7 @@ i18n
},
'zh-CN': {
translation: {
appName: 'Antigravity 管理器',
appName: 'Gemini Nexus 管理器',
common: {
loading: '加载中...',
error: '错误',
@@ -505,8 +509,8 @@ i18n
},
status: {
checking: '正在检查状态...',
running: 'Antigravity 正在后台运行',
stopped: 'Antigravity 服务已停止',
running: 'Gemini Nexus 正在后台运行',
stopped: 'Gemini Nexus 服务已停止',
},
action: {
stop: '停止',
@@ -544,10 +548,10 @@ i18n
},
home: {
title: '账号列表',
description: '管理您的 Antigravity Google Gemini 账号。',
description: '管理您的 Gemini Nexus Google Gemini 账号。',
noBackups: {
title: '未找到备份',
description: '备份您当前的 Antigravity 账号以开始使用。',
description: '备份您当前的 Gemini Nexus 账号以开始使用。',
action: '备份当前账号',
},
},
@@ -559,7 +563,7 @@ i18n
models: '模型',
appearance: {
title: '外观',
description: '自定义 Antigravity 管理器在您设备上的显示方式。',
description: '自定义 Gemini Nexus 管理器在您设备上的显示方式。',
},
darkMode: '深色模式',
darkModeDescription: '启用深色模式以获得更好的夜间观看体验。',
@@ -627,11 +631,15 @@ i18n
enable: '启用上游代理',
url: '代理地址',
timeout: '请求超时 (秒)',
accountProxies: {
title: '独立账号代理',
description: '为各个独立账号配置特定的代理。这些配置将覆盖该账号的全局上游代理。',
},
},
modelMapping: {
title: '模型映射',
description:
'将 Claude Code 模型映射到 Antigravity 模型,智能路由请求以优化成本和速度。',
'将 Claude Code 模型映射到 Gemini Nexus 模型,智能路由请求以优化成本和速度。',
claudeKeyword: 'Claude 模型 (关键词)',
targetGemini: '目标 Gemini 模型',
addPlaceholderKey: '例如 op-3',
@@ -853,7 +861,7 @@ i18n
pollFailed: '轮询所有账号配额失败',
switched: {
title: '账号已切换!',
description: '正在重启 Antigravity...',
description: '正在重启 Gemini Nexus...',
},
switchFailed: '切换账号失败',
deleted: '账号已删除',
@@ -963,7 +971,7 @@ i18n
},
ru: {
translation: {
appName: 'Antigravity Manager',
appName: 'Gemini Nexus',
common: {
loading: 'Загрузка...',
error: 'Ошибка',
@@ -973,8 +981,8 @@ i18n
},
status: {
checking: 'Проверка статуса...',
running: 'Antigravity работает в фоне',
stopped: 'Служба Antigravity остановлена',
running: 'Gemini Nexus работает в фоне',
stopped: 'Служба Gemini Nexus остановлена',
},
action: {
stop: 'Стоп',
@@ -1012,11 +1020,11 @@ i18n
},
home: {
title: 'Аккаунты',
description: 'Управление вашими аккаунтами Antigravity Google Gemini.',
description: 'Управление вашими аккаунтами Gemini Nexus Google Gemini.',
noBackups: {
title: 'Бэкапы не найдены',
description:
'Создайте резервную копию вашего текущего аккаунта Antigravity, чтобы начать.',
'Создайте резервную копию вашего текущего аккаунта Gemini Nexus, чтобы начать.',
action: 'Создать бэкап текущего аккаунта',
},
},
@@ -1028,7 +1036,7 @@ i18n
models: 'Модели',
appearance: {
title: 'Внешний вид',
description: 'Настройте внешний вид Antigravity Manager.',
description: 'Настройте внешний вид Gemini Nexus.',
},
darkMode: 'Темная тема',
darkModeDescription: 'Включить темную тему для комфортной работы ночью.',
@@ -1099,11 +1107,15 @@ i18n
enable: 'Включить прокси',
url: 'URL прокси',
timeout: 'Тайм-аут запроса (сек)',
accountProxies: {
title: 'Прокси аккаунтов',
description: 'Настройка индивидуальных прокси для каждого аккаунта. Эти настройки переопределят глобальный прокси для данного аккаунта.',
},
},
modelMapping: {
title: 'Маппинг моделей',
description:
'Сопоставьте модели Claude Code с моделями Antigravity. Оптимизируйте затраты и скорость, разумно маршрутизируя запросы.',
'Сопоставьте модели Claude Code с моделями Gemini Nexus. Оптимизируйте затраты и скорость, разумно маршрутизируя запросы.',
claudeKeyword: 'Модель Claude (Ключевое слово)',
targetGemini: 'Целевая модель Gemini',
addPlaceholderKey: 'например, op-3',
@@ -1327,7 +1339,7 @@ i18n
pollFailed: 'Не удалось опросить квоты всех аккаунтов',
switched: {
title: 'Аккаунт переключен!',
description: 'Перезапуск Antigravity...',
description: 'Перезапуск Gemini Nexus...',
},
switchFailed: 'Не удалось переключить аккаунт',
deleted: 'Аккаунт удален',
+12 -8
View File
@@ -1,5 +1,5 @@
const vi = {
appName: 'Antigravity Manager',
appName: 'Gemini Nexus',
common: {
loading: 'Đang tải...',
error: 'Lỗi',
@@ -9,8 +9,8 @@ const vi = {
},
status: {
checking: 'Đang kiểm tra trạng thái...',
running: 'Antigravity đang chạy nền',
stopped: 'Dịch vụ Antigravity đã dừng',
running: 'Gemini Nexus đang chạy nền',
stopped: 'Dịch vụ Gemini Nexus đã dừng',
},
action: {
stop: 'Dừng',
@@ -48,10 +48,10 @@ const vi = {
},
home: {
title: 'Tài khoản',
description: 'Quản lý các tài khoản Antigravity Google Gemini.',
description: 'Quản lý các tài khoản Gemini Nexus Google Gemini.',
noBackups: {
title: 'Chưa tìm thấy bản sao lưu',
description: 'Hãy tạo bản sao lưu cho tài khoản Antigravity hiện tại để bắt đầu.',
description: 'Hãy tạo bản sao lưu cho tài khoản Gemini Nexus hiện tại để bắt đầu.',
action: 'Sao lưu tài khoản hiện tại',
},
},
@@ -63,7 +63,7 @@ const vi = {
models: 'Mô hình',
appearance: {
title: 'Giao diện',
description: 'Tùy chỉnh giao diện Antigravity Manager trên thiết bị của bạn.',
description: 'Tùy chỉnh giao diện Gemini Nexus trên thiết bị của bạn.',
},
darkMode: 'Chế độ tối',
darkModeDescription: 'Bật chế độ tối để dễ nhìn hơn vào ban đêm.',
@@ -132,11 +132,15 @@ const vi = {
enable: 'Bật proxy trung gian',
url: 'Địa chỉ proxy',
timeout: 'Thời gian chờ request (giây)',
accountProxies: {
title: 'Proxy tài khoản',
description: 'Cấu hình proxy riêng cho từng tài khoản. Những cấu hình này sẽ ghi đè proxy trung gian chung cho tài khoản đó.',
},
},
modelMapping: {
title: 'Ánh xạ mô hình',
description:
'Ánh xạ các model Claude Code sang model Antigravity để tối ưu chi phí và tốc độ.',
'Ánh xạ các model Claude Code sang model Gemini Nexus để tối ưu chi phí và tốc độ.',
claudeKeyword: 'Từ khóa model Claude',
targetGemini: 'Model Gemini đích',
addPlaceholderKey: 'ví dụ: op-3',
@@ -360,7 +364,7 @@ const vi = {
pollFailed: 'Không thể polling quota của tất cả tài khoản',
switched: {
title: 'Đã chuyển tài khoản!',
description: 'Đang khởi động lại Antigravity...',
description: 'Đang khởi động lại Gemini Nexus...',
},
switchFailed: 'Không thể chuyển tài khoản',
deleted: 'Đã xóa tài khoản',
+4 -4
View File
@@ -55,7 +55,7 @@ app.disableHardwareAcceleration();
if (process.platform === 'linux') {
app.commandLine.appendSwitch('disable-gpu');
app.commandLine.appendSwitch('disable-gpu-compositing');
logger.info('Applied Linux GPU-safe Chromium switches for Antigravity Manager startup');
logger.info('Applied Linux GPU-safe Chromium switches for Gemini Nexus startup');
}
if (squirrelStartup) {
@@ -167,7 +167,7 @@ function showWindowsInstallNoticeIfNeeded() {
const isDev = process.env.NODE_ENV === 'development' || !app.isPackaged;
if (isDev) {
app.setName('Antigravity Manager Dev');
app.setName('Gemini Nexus Dev');
}
const gotSingleInstanceLock = app.requestSingleInstanceLock();
@@ -365,7 +365,7 @@ function checkForUpdates() {
updateElectronApp({
updateSource: {
type: UpdateSourceType.ElectronPublicUpdateService,
repo: 'Draculabo/AntigravityManager',
repo: 'Draculabo/GeminiNexusManager',
},
});
}
@@ -417,7 +417,7 @@ app
// though functionality will be broken.
}
logger.info('Step: Initialize Antigravity DB (WAL Mode)');
logger.info('Step: Initialize Gemini Nexus DB (WAL Mode)');
initDatabase();
})
.then(() => {
+3 -3
View File
@@ -14,12 +14,12 @@ try {
if (process.platform === 'win32') {
appDataPath = path.join(
process.env.APPDATA || path.join(home, 'AppData', 'Roaming'),
'Antigravity',
'Gemini Nexus',
);
} else if (process.platform === 'darwin') {
appDataPath = path.join(home, 'Library', 'Application Support', 'Antigravity');
appDataPath = path.join(home, 'Library', 'Application Support', 'Gemini Nexus');
} else {
appDataPath = path.join(home, '.config', 'Antigravity');
appDataPath = path.join(home, '.config', 'Gemini Nexus');
}
const configPath = path.join(appDataPath, 'gui_config.json');
+96 -37
View File
@@ -6,7 +6,7 @@ import { createFileRoute } from '@tanstack/react-router';
import { useTranslation } from 'react-i18next';
import { useQuery } from '@tanstack/react-query';
import { ipc } from '@/ipc/manager';
import { useEffect, useState, type ReactNode } from 'react';
import { useEffect, useState, useMemo, type ReactNode } from 'react';
import { useAppConfig } from '@/hooks/useAppConfig';
import { ProxyConfig } from '@/types/config';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
@@ -17,7 +17,10 @@ import { Switch } from '@/components/ui/switch';
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectLabel,
SelectSeparator,
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
@@ -25,10 +28,6 @@ import {
Loader2,
Copy,
CheckCircle,
Zap,
Cpu,
Sparkles,
BrainCircuit,
Code,
Terminal,
Eye,
@@ -43,28 +42,17 @@ import {
DialogTitle,
} from '@/components/ui/dialog';
import { useCloudAccounts } from '@/hooks/useCloudAccounts';
import { flatMap, sortBy, uniq } from 'lodash-es';
type ProxyProtocol = 'openai' | 'anthropic';
interface ExampleModel {
id: string;
name: string;
icon: ReactNode;
}
const EXAMPLE_MODELS: ExampleModel[] = [
{ id: 'gemini-3-flash', name: 'Gemini 3 Flash', icon: <Zap size={14} /> },
{ id: 'gemini-3.1-pro-low', name: 'Gemini 3.1 Pro (Low)', icon: <Cpu size={14} /> },
{ id: 'gemini-3.1-pro-high', name: 'Gemini 3.1 Pro (High)', icon: <Cpu size={14} /> },
{
id: 'claude-sonnet-4-6-thinking',
name: 'Claude Sonnet 4.6 (Thinking)',
icon: <Sparkles size={14} />,
},
{
id: 'claude-opus-4-6-thinking',
name: 'Claude Opus 4.6 (Thinking)',
icon: <BrainCircuit size={14} />,
},
const DEFAULT_MODELS = [
'gemini-3-flash',
'gemini-3.1-pro-low',
'gemini-3.1-pro-high',
'claude-sonnet-4-6-thinking',
'claude-opus-4-6-thinking',
];
const ANTHROPIC_ROUTE_OPTIONS = [
@@ -168,10 +156,53 @@ function ProxyPage() {
};
// ===== Usage Examples State =====
const { data: accounts } = useCloudAccounts();
const [selectedProtocol, setSelectedProtocol] = useState<ProxyProtocol>('openai');
const [activeModelTab, setActiveModelTab] = useState('gemini-3.1-pro-high');
const [copied, setCopied] = useState<string | null>(null);
const availableModelIds: string[] = useMemo(() => {
if (!accounts || accounts.length === 0) {
return DEFAULT_MODELS;
}
const modelNames = flatMap(accounts, (account) => {
if (!account.quota?.models) return [];
return Object.keys(account.quota.models);
});
const uniqueModels = sortBy(uniq(modelNames));
return uniqueModels.length > 0 ? uniqueModels : DEFAULT_MODELS;
}, [accounts]);
const groupedModels = useMemo(() => {
const groups: Record<string, string[]> = {
Anthropic: [],
Google: [],
OpenAI: [],
Other: [],
};
availableModelIds.forEach((id) => {
const lower = id.toLowerCase();
if (lower.includes('gemini') || lower.includes('learnlm') || lower.includes('gemma')) {
groups['Google'].push(id);
} else if (lower.includes('claude')) {
groups['Anthropic'].push(id);
} else if (lower.includes('gpt') || lower.includes('o1') || lower.includes('o3') || lower.includes('dall-e')) {
groups['OpenAI'].push(id);
} else {
groups['Other'].push(id);
}
});
return groups;
}, [availableModelIds]);
useEffect(() => {
if (!availableModelIds.includes(activeModelTab) && availableModelIds.length > 0) {
setActiveModelTab(availableModelIds[0]);
}
}, [availableModelIds, activeModelTab]);
// Computed values for examples
const apiKey = proxyConfig?.api_key || 'YOUR_API_KEY';
const baseUrl = `http://localhost:${proxyConfig?.port || 8045}`;
@@ -599,18 +630,46 @@ print(response.choices[0].message.content)`;
</div>
</div>
{/* Model Tabs */}
<div className="flex flex-wrap gap-1 border-b border-gray-200 dark:border-gray-700">
{EXAMPLE_MODELS.map((model) => (
<button
key={model.id}
onClick={() => setActiveModelTab(model.id)}
className={`flex items-center gap-1 rounded-t-lg px-3 py-2 text-xs font-medium whitespace-nowrap transition-colors ${activeModelTab === model.id ? 'border-b-2 border-blue-600 bg-blue-50/50 text-blue-600 dark:border-blue-400 dark:bg-blue-900/10 dark:text-blue-400' : 'text-gray-600 hover:bg-gray-50 dark:text-gray-400 dark:hover:bg-gray-800'}`}
>
{model.icon}
<span>{model.name}</span>
</button>
))}
{/* Model Selector & Copy API ID */}
<div className="flex flex-col gap-4 border-b border-gray-200 pb-4 sm:flex-row sm:items-center sm:justify-between dark:border-gray-700">
<div className="flex items-center gap-3">
<Label className="text-sm font-medium">
Model <span className="text-muted-foreground ml-1 font-normal">({availableModelIds.length} available)</span>
</Label>
<Select value={activeModelTab} onValueChange={setActiveModelTab}>
<SelectTrigger className="h-9 w-[280px] font-mono text-xs">
<SelectValue />
</SelectTrigger>
<SelectContent>
{Object.entries(groupedModels).map(([provider, models]) => {
if (models.length === 0) return null;
return (
<SelectGroup key={provider}>
<SelectLabel className="text-xs font-bold text-muted-foreground">{provider}</SelectLabel>
{models.map((modelId) => (
<SelectItem key={modelId} value={modelId} className="font-mono text-xs">
{modelId}
</SelectItem>
))}
</SelectGroup>
);
})}
</SelectContent>
</Select>
</div>
<Button
variant="outline"
size="sm"
onClick={() => copyToClipboard(activeModelTab, 'model-id')}
className="h-9 text-xs"
>
{copied === 'model-id' ? (
<CheckCircle className="mr-2 h-4 w-4 text-green-500" />
) : (
<Copy className="mr-2 h-4 w-4" />
)}
Copy API Name
</Button>
</div>
{/* cURL Example */}
+3
View File
@@ -23,6 +23,7 @@ import { ModelVisibilitySettings } from '@/components/ModelVisibilitySettings';
import { useEffect, useState } from 'react';
import { ProxyConfig } from '@/types/config';
import { openLogDirectory } from '@/actions/system';
import { AccountProxySettings } from '@/components/AccountProxySettings';
function SettingsPage() {
const { theme, setTheme } = useTheme();
@@ -380,6 +381,8 @@ function SettingsPage() {
</div>
</CardContent>
</Card>
<AccountProxySettings />
</TabsContent>
</Tabs>
</div>
@@ -2,7 +2,7 @@ import { Injectable, Logger } from '@nestjs/common';
import axios, { AxiosProxyConfig, AxiosRequestConfig, AxiosResponse } from 'axios';
import { isEmpty, isFunction, isNil, isObjectLike, isString } from 'lodash-es';
import { GeminiRequest, GeminiResponse } from '../interfaces/request-interfaces';
import { GeminiInternalRequest } from '../../../../lib/antigravity/types';
import { GeminiInternalRequest } from '../../../../lib/geminiNexus/types';
import { getServerConfig } from '../../../server-config';
import { resolveRequestUserAgent } from '../request-user-agent';
import { UpstreamRequestError } from './upstream-error';
@@ -18,7 +18,7 @@ import { ProxyGuard } from './proxy.guard';
import { ProxyService } from './proxy.service';
import { GeminiRequest, GeminiResponse } from './interfaces/request-interfaces';
import { getServerConfig } from '../../server-config';
import { getAllDynamicModels } from '../../../lib/antigravity/ModelMapping';
import { getAllDynamicModels } from '../../../lib/geminiNexus/ModelMapping';
import { TokenManagerService } from './token-manager.service';
type GeminiModelMetadata = {
+1 -1
View File
@@ -28,7 +28,7 @@ import {
getAllDynamicModels,
MODEL_LIST_CREATED_AT,
MODEL_LIST_OWNER,
} from '../../../lib/antigravity/ModelMapping';
} from '../../../lib/geminiNexus/ModelMapping';
import { getServerConfig } from '../../server-config';
import { TokenManagerService } from './token-manager.service';
+9 -9
View File
@@ -4,18 +4,18 @@ import { TokenManagerService } from './token-manager.service';
import { GeminiClient } from './clients/gemini.client';
import { v4 as uuidv4 } from 'uuid';
import { Observable } from 'rxjs';
import { transformClaudeRequestIn } from '../../../lib/antigravity/ClaudeRequestMapper';
import { transformResponse } from '../../../lib/antigravity/ClaudeResponseMapper';
import { StreamingState, PartProcessor } from '../../../lib/antigravity/ClaudeStreamingMapper';
import { transformClaudeRequestIn } from '../../../lib/geminiNexus/ClaudeRequestMapper';
import { transformResponse } from '../../../lib/geminiNexus/ClaudeResponseMapper';
import { StreamingState, PartProcessor } from '../../../lib/geminiNexus/ClaudeStreamingMapper';
import {
ClaudeRequest,
ClaudeResponse,
GeminiInternalRequest,
GeminiPart as InternalGeminiPart,
} from '../../../lib/antigravity/types';
import { calculateRetryDelay, sleep } from '../../../lib/antigravity/retry-utils';
import { normalizeObjectJsonSchema } from '../../../lib/antigravity/JsonSchemaUtils';
import { classifyStreamError } from '../../../lib/antigravity/stream-error-utils';
} from '../../../lib/geminiNexus/types';
import { calculateRetryDelay, sleep } from '../../../lib/geminiNexus/retry-utils';
import { normalizeObjectJsonSchema } from '../../../lib/geminiNexus/JsonSchemaUtils';
import { classifyStreamError } from '../../../lib/geminiNexus/stream-error-utils';
import {
OpenAIChatRequest,
AnthropicChatRequest,
@@ -29,8 +29,8 @@ import { getServerConfig } from '../../server-config';
import {
normalizeGeminiModelAlias,
resolveModelRoute,
} from '../../../lib/antigravity/ModelMapping';
import { getMaxOutputTokens, getThinkingBudget } from '../../../lib/antigravity/ModelSpecs';
} from '../../../lib/geminiNexus/ModelMapping';
import { getMaxOutputTokens, getThinkingBudget } from '../../../lib/geminiNexus/ModelSpecs';
import { resolveRequestUserAgent } from './request-user-agent';
import { UpstreamRequestError } from './clients/upstream-error';
@@ -1,10 +1,10 @@
import axios from 'axios';
import { isString } from 'lodash-es';
import { getAntigravityVersion } from '../../../utils/antigravityVersion';
import { getGeminiNexusVersion } from '../../../utils/geminiNexusVersion';
import { logger } from '../../../utils/logger';
const REMOTE_VERSION_URL = 'https://antigravity-auto-updater-974169037036.us-central1.run.app';
const CHANGELOG_URL = 'https://antigravity.google/changelog';
const REMOTE_VERSION_URL = 'https://geminiNexus-auto-updater-974169037036.us-central1.run.app';
const CHANGELOG_URL = 'https://geminiNexus.google/changelog';
export const FALLBACK_VERSION = '1.22.2';
const DEFAULT_REMOTE_TIMEOUT_MS = 2500;
const VERSION_REGEX = /\d+\.\d+\.\d+/g;
@@ -57,7 +57,7 @@ function getArchTag(): string {
}
export function buildUserAgent(version: string): string {
return `antigravity/${version} ${getPlatformTag()}/${getArchTag()}`;
return `geminiNexus/${version} ${getPlatformTag()}/${getArchTag()}`;
}
function compareSemverVersions(left: string, right: string): number {
@@ -103,7 +103,7 @@ function shouldSkipRemoteVersionLookup(): boolean {
export function resolveLocalInstalledVersion(): string | null {
let localVersionString: string;
try {
localVersionString = getAntigravityVersion().shortVersion;
localVersionString = getGeminiNexusVersion().shortVersion;
const rawVersion = normalizeNonEmptyString(localVersionString);
if (!rawVersion) {
return null;
@@ -5,7 +5,7 @@ import { CloudAccount, CloudQuotaData } from '../../../types/cloudAccount';
import { GoogleAPIService } from '../../../services/GoogleAPIService';
import { getServerConfig } from '../../server-config';
import { RateLimitReason, RateLimitTracker } from './rate-limit-tracker';
import { updateDynamicForwardingRules } from '../../../lib/antigravity/ModelMapping';
import { updateDynamicForwardingRules } from '../../../lib/geminiNexus/ModelMapping';
interface TokenData {
email: string;
+3 -3
View File
@@ -16,7 +16,7 @@ const CLIENT_ID = '1071006060591-tmhssin2h21lcre235vtolojh4g403ep.apps.googleuse
const CLIENT_SECRET = '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';
const OAUTH_CLIENTS_ENV = 'ANTIGRAVITY_OAUTH_CLIENTS';
const ACTIVE_OAUTH_CLIENT_ENV = 'ANTIGRAVITY_OAUTH_CLIENT_KEY';
const DEFAULT_OAUTH_CLIENT_KEY = 'antigravity_enterprise';
const DEFAULT_OAUTH_CLIENT_KEY = 'geminiNexus_enterprise';
const URLS = {
TOKEN: 'https://oauth2.googleapis.com/token',
@@ -70,7 +70,7 @@ function buildOAuthClientRegistry(): OAuthClientRegistry {
const clients: OAuthClientConfig[] = [
{
key: normalizeClientKey(DEFAULT_OAUTH_CLIENT_KEY),
label: 'Antigravity Enterprise',
label: 'Gemini Nexus Enterprise',
client_id: CLIENT_ID,
client_secret: CLIENT_SECRET,
is_builtin: true,
@@ -882,7 +882,7 @@ export class GoogleAPIService {
metadata: {
ide_type: 'ANTIGRAVITY',
ide_version: discoveryVersion,
ide_name: 'antigravity',
ide_name: 'geminiNexus',
},
}),
signal: createTimeoutSignal(REQUEST_TIMEOUT_MS),
@@ -1,18 +1,18 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { transformClaudeRequestIn } from '../lib/antigravity/ClaudeRequestMapper';
import { ClaudeRequest } from '../lib/antigravity/types';
import { SignatureStore } from '../lib/antigravity/SignatureStore';
import { transformClaudeRequestIn } from '../lib/geminiNexus/ClaudeRequestMapper';
import { ClaudeRequest } from '../lib/geminiNexus/types';
import { SignatureStore } from '../lib/geminiNexus/SignatureStore';
/**
* Antigravity Core Features Verification
* Gemini Nexus Core Features Verification
*
* This test suite verifies the critical "Antigravity" enhancements ported from the Manager project.
* This test suite verifies the critical "Gemini Nexus" enhancements ported from the Manager project.
* Primarily focuses on:
* 1. Identity Injection (Safety & Branding)
* 2. Gemini 3 Pro Compatibility (Stability Fixes)
* 3. Model Routing Logic (Business Rules)
*/
describe('Antigravity Core Features (Business Logic)', () => {
describe('Gemini Nexus Core Features (Business Logic)', () => {
beforeEach(() => {
// Clear global signature store before each test
SignatureStore.clear();
@@ -27,22 +27,22 @@ describe('Antigravity Core Features (Business Logic)', () => {
};
describe('Feature: Identity Injection (Branding & Safety)', () => {
it('should inject Antigravity identity when system prompt is missing', () => {
it('should inject Gemini Nexus identity when system prompt is missing', () => {
const result = transformClaudeRequestIn({ ...BASE_REQUEST }, 'test-project');
const parts = result.request.systemInstruction?.parts || [];
const identityPart = parts.find((p) => p.text.includes('You are Antigravity'));
const identityPart = parts.find((p) => p.text.includes('You are Gemini Nexus'));
expect(identityPart).toBeDefined();
expect(identityPart?.text).toContain('[IDENTITY_PATCH]');
});
it('should NOT inject Antigravity identity if user already provided it', () => {
it('should NOT inject Gemini Nexus identity if user already provided it', () => {
// User explicitly providing the identity string
const result = transformClaudeRequestIn(
{
...BASE_REQUEST,
system: 'You are Antigravity, the best AI.',
system: 'You are Gemini Nexus, the best AI.',
},
'test-project',
);
@@ -66,7 +66,7 @@ describe('Antigravity Core Features (Business Logic)', () => {
const parts = result.request.systemInstruction?.parts || [];
expect(parts.length).toBeGreaterThanOrEqual(2);
expect(parts[0].text).toContain('Antigravity');
expect(parts[0].text).toContain('Gemini Nexus');
expect(parts[parts.length - 1].text).toContain('Be concise');
});
});
+2 -2
View File
@@ -61,7 +61,7 @@ const injectCloudAccountsFailureScript = `
})();
`;
test.describe('Antigravity Manager', () => {
test.describe('Gemini Nexus', () => {
let electronApp: ElectronApplication;
const electronMainPath = path.join(__dirname, '../../../.vite/build/main.js');
@@ -81,7 +81,7 @@ test.describe('Antigravity Manager', () => {
await window.waitForLoadState('domcontentloaded');
const title = await window.title();
expect(title).toBe('Antigravity Manager');
expect(title).toBe('Gemini Nexus');
await expect(window.getByRole('main')).toBeVisible();
await expect(window.locator('a[href="/settings"]').first()).toBeVisible();
@@ -1,7 +1,7 @@
import assert from 'node:assert/strict';
import { isEmpty, isString } from 'lodash-es';
import { transformClaudeRequestIn } from '../../lib/antigravity/ClaudeRequestMapper';
import { transformClaudeRequestIn } from '../../lib/geminiNexus/ClaudeRequestMapper';
import { ProxyService } from '../../server/modules/proxy/proxy.service';
import { TokenManagerService } from '../../server/modules/proxy/token-manager.service';
+7 -7
View File
@@ -6,7 +6,7 @@ import {
deleteAccount,
} from '../../ipc/account/handler';
import { restoreAccount } from '../../ipc/database/handler';
import { startAntigravity } from '../../ipc/process/handler';
import { startGeminiNexus } from '../../ipc/process/handler';
import fs from 'fs';
import path from 'path';
import { applyDeviceProfile, generateDeviceProfile } from '../../ipc/device/handler';
@@ -20,8 +20,8 @@ vi.mock('../../utils/paths', async () => {
getAgentDir: vi.fn(() => agentDir),
getAccountsFilePath: vi.fn(() => path.join(agentDir, 'accounts.json')),
getBackupsDir: vi.fn(() => path.join(agentDir, 'backups')),
getAntigravityDbPath: vi.fn(() => path.join(agentDir, 'state.vscdb')),
getAntigravityExecutablePath: vi.fn(() => 'mock_exec_path'),
getGeminiNexusDbPath: vi.fn(() => path.join(agentDir, 'state.vscdb')),
getGeminiNexusExecutablePath: vi.fn(() => 'mock_exec_path'),
};
});
@@ -37,8 +37,8 @@ vi.mock('../../ipc/database/handler', () => ({
}));
vi.mock('../../ipc/process/handler', () => ({
closeAntigravity: vi.fn(),
startAntigravity: vi.fn(),
closeGeminiNexus: vi.fn(),
startGeminiNexus: vi.fn(),
_waitForProcessExit: vi.fn(),
isProcessRunning: vi.fn(() => Promise.resolve(false)),
}));
@@ -144,13 +144,13 @@ describe('Account Handler', () => {
devDeviceId: 'dev-device-id',
sqmId: '{SQM-ID}',
});
expect(startAntigravity).not.toHaveBeenCalled();
expect(startGeminiNexus).not.toHaveBeenCalled();
});
it('should queue switch requests instead of rejecting concurrent calls', async () => {
const account = await addAccountSnapshot();
const startMock = vi.mocked(startAntigravity);
const startMock = vi.mocked(startGeminiNexus);
let releaseFirstStart!: () => void;
const firstStartBlocker = new Promise<void>((resolve) => {
releaseFirstStart = resolve;
+21 -21
View File
@@ -58,7 +58,7 @@ vi.mock('../../ipc/database/dbConnection', () => ({
}));
vi.mock('../../utils/paths', () => ({
getAntigravityDbPaths: () => ['mock-db'],
getGeminiNexusDbPaths: () => ['mock-db'],
getCloudAccountsDbPath: () => 'mock-cloud-db',
}));
@@ -152,7 +152,7 @@ describe('CloudAccountRepo.syncFromIDE', () => {
ProtobufUtils.createOAuthTokenInfo('access-old', 'refresh-old', 1700000000),
).toString('base64');
mockData['antigravityUnifiedStateSync.oauthToken'] = unifiedB64;
mockData['geminiNexusUnifiedStateSync.oauthToken'] = unifiedB64;
mockData['jetskiStateSync.agentManagerInitState'] = oldB64;
const { GoogleAPIService } = await import('../../services/GoogleAPIService');
@@ -179,8 +179,8 @@ describe('CloudAccountRepo.syncFromIDE', () => {
projectPayload,
);
mockData['antigravityUnifiedStateSync.oauthToken'] = unifiedB64;
mockData['antigravityUnifiedStateSync.enterprisePreferences'] = enterprisePreferenceB64;
mockData['geminiNexusUnifiedStateSync.oauthToken'] = unifiedB64;
mockData['geminiNexusUnifiedStateSync.enterprisePreferences'] = enterprisePreferenceB64;
const { GoogleAPIService } = await import('../../services/GoogleAPIService');
vi.mocked(GoogleAPIService.getUserInfo).mockResolvedValue(
@@ -302,8 +302,8 @@ describe('CloudAccountRepo.syncFromIDE', () => {
ProtobufUtils.createStringValuePayload('enterprise-project-recovered'),
);
mockData['antigravityUnifiedStateSync.oauthToken'] = unifiedB64;
mockData['antigravityUnifiedStateSync.enterprisePreferences'] = enterprisePreferenceB64;
mockData['geminiNexusUnifiedStateSync.oauthToken'] = unifiedB64;
mockData['geminiNexusUnifiedStateSync.enterprisePreferences'] = enterprisePreferenceB64;
const { GoogleAPIService } = await import('../../services/GoogleAPIService');
vi.mocked(GoogleAPIService.getUserInfo).mockResolvedValue(
@@ -431,8 +431,8 @@ describe('CloudAccountRepo.syncFromIDE', () => {
it('should prefer new format when capability detection finds unified key', async () => {
vi.resetModules();
vi.doMock('../../utils/antigravityVersion', () => ({
getAntigravityVersion: () => {
vi.doMock('../../utils/geminiNexusVersion', () => ({
getGeminiNexusVersion: () => {
throw new Error('version detection failed');
},
isNewVersion: () => false,
@@ -442,7 +442,7 @@ describe('CloudAccountRepo.syncFromIDE', () => {
const accessToken = 'access-new';
const refreshToken = 'refresh-new';
mockData['antigravityUnifiedStateSync.oauthToken'] = 'exists';
mockData['geminiNexusUnifiedStateSync.oauthToken'] = 'exists';
mockData['jetskiStateSync.agentManagerInitState'] = 'exists-old';
RepoWithMock.injectCloudToken({
@@ -474,7 +474,7 @@ describe('CloudAccountRepo.syncFromIDE', () => {
const wroteUnifiedKey = runCalls.some(
(call) =>
call.sql === 'insert' &&
(call.args[0] as { key?: string })?.key === 'antigravityUnifiedStateSync.oauthToken',
(call.args[0] as { key?: string })?.key === 'geminiNexusUnifiedStateSync.oauthToken',
);
expect(wroteUnifiedKey).toBe(true);
@@ -486,13 +486,13 @@ describe('syncLocalAccount ORPC error mapping', () => {
it('preserves re-login guidance as an actionable unauthorized error', () => {
const error = toSyncLocalAccountORPCError(
new Error(
'Failed to validate token with Google API. The token may be expired. Please re-login in Antigravity IDE.',
'Failed to validate token with Google API. The token may be expired. Please re-login in Gemini Nexus IDE.',
),
);
expect(error.code).toBe('UNAUTHORIZED');
expect(error.status).toBe(401);
expect(error.message).toContain('Please re-login in Antigravity IDE');
expect(error.message).toContain('Please re-login in Gemini Nexus IDE');
});
});
@@ -506,7 +506,7 @@ describe('cloud switch fail-fast path', () => {
vi.resetModules();
const applyDeviceProfileMock = vi.fn();
const startAntigravityMock = vi.fn(async () => undefined);
const startGeminiNexusMock = vi.fn(async () => undefined);
const recordSwitchFailureMock = vi.fn();
const recordSwitchSuccessMock = vi.fn();
@@ -561,8 +561,8 @@ describe('cloud switch fail-fast path', () => {
}));
vi.doMock('../../ipc/process/handler', () => ({
closeAntigravity: vi.fn(async () => undefined),
startAntigravity: startAntigravityMock,
closeGeminiNexus: vi.fn(async () => undefined),
startGeminiNexus: startGeminiNexusMock,
_waitForProcessExit: vi.fn(async () => undefined),
}));
@@ -576,7 +576,7 @@ describe('cloud switch fail-fast path', () => {
}));
vi.doMock('../../utils/paths', () => ({
getAntigravityDbPaths: () => [],
getGeminiNexusDbPaths: () => [],
}));
vi.doMock('../../utils/logger', () => ({
@@ -605,7 +605,7 @@ describe('cloud switch fail-fast path', () => {
expect(applyDeviceProfileMock).toHaveBeenCalledTimes(1);
expect(applyDeviceProfileMock).toHaveBeenCalledWith(account.device_profile);
expect(startAntigravityMock).not.toHaveBeenCalled();
expect(startGeminiNexusMock).not.toHaveBeenCalled();
expect(recordSwitchFailureMock).toHaveBeenCalledWith(
'cloud',
'perform_switch_failed',
@@ -686,7 +686,7 @@ describe('cloud oauth client key backfill', () => {
readCurrentDeviceProfile: vi.fn(),
saveGlobalOriginalProfile: vi.fn(),
}));
vi.doMock('../../utils/paths', () => ({ getAntigravityDbPaths: () => [] }));
vi.doMock('../../utils/paths', () => ({ getGeminiNexusDbPaths: () => [] }));
vi.doMock('../../ipc/switchGuard', () => ({
runWithSwitchGuard: async (_owner: string, fn: () => Promise<void>) => fn(),
}));
@@ -736,7 +736,7 @@ describe('cloud oauth client key backfill', () => {
return false;
}
if (key === 'active_oauth_client_key') {
return 'antigravity_enterprise';
return 'geminiNexus_enterprise';
}
return defaultValue;
}),
@@ -747,7 +747,7 @@ describe('cloud oauth client key backfill', () => {
vi.doMock('../../services/GoogleAPIService', () => ({
GoogleAPIService: {
setActiveOAuthClientKey: vi.fn(),
getActiveOAuthClientKey: vi.fn(() => 'antigravity_enterprise'),
getActiveOAuthClientKey: vi.fn(() => 'geminiNexus_enterprise'),
},
}));
@@ -769,7 +769,7 @@ describe('cloud oauth client key backfill', () => {
readCurrentDeviceProfile: vi.fn(),
saveGlobalOriginalProfile: vi.fn(),
}));
vi.doMock('../../utils/paths', () => ({ getAntigravityDbPaths: () => [] }));
vi.doMock('../../utils/paths', () => ({ getGeminiNexusDbPaths: () => [] }));
vi.doMock('../../ipc/switchGuard', () => ({
runWithSwitchGuard: async (_owner: string, fn: () => Promise<void>) => fn(),
}));
+4 -4
View File
@@ -22,7 +22,7 @@ vi.mock('../../ipc/database/dbConnection', () => ({
}));
vi.mock('../../utils/paths', () => ({
getAntigravityDbPaths: () => ['mock-db'],
getGeminiNexusDbPaths: () => ['mock-db'],
}));
vi.mock('../../utils/logger', () => ({
@@ -73,12 +73,12 @@ describe('database backup keys', () => {
last_used: new Date().toISOString(),
},
data: {
antigravityAuthStatus: '{"email":"[email protected]"}',
geminiNexusAuthStatus: '{"email":"[email protected]"}',
'jetskiStateSync.agentManagerInitState': 'old',
'antigravityUnifiedStateSync.oauthToken': 'unified',
'geminiNexusUnifiedStateSync.oauthToken': 'unified',
},
});
expect(writes['antigravityUnifiedStateSync.oauthToken']).toBe('unified');
expect(writes['geminiNexusUnifiedStateSync.oauthToken']).toBe('unified');
});
});
+6 -6
View File
@@ -16,8 +16,8 @@ vi.mock('../../utils/paths', async () => {
const path = await import('path');
const tempDbPath = path.join(process.cwd(), 'temp_test.vscdb');
return {
getAntigravityDbPath: () => tempDbPath,
getAntigravityDbPaths: () => [tempDbPath],
getGeminiNexusDbPath: () => tempDbPath,
getGeminiNexusDbPaths: () => [tempDbPath],
getAgentDir: () => path.join(process.cwd(), 'temp_test_agent'),
};
});
@@ -39,7 +39,7 @@ describe.skip('Database Handler', () => {
const db = new Database(tempDbPath);
db.exec('CREATE TABLE IF NOT EXISTS ItemTable (key TEXT PRIMARY KEY, value TEXT)');
db.prepare('INSERT INTO ItemTable (key, value) VALUES (?, ?)').run(
'antigravityAuthStatus',
'geminiNexusAuthStatus',
JSON.stringify({
user: { email: '[email protected]', name: 'Test User' },
}),
@@ -76,7 +76,7 @@ describe.skip('Database Handler', () => {
};
const backup = backupAccount(account);
expect(backup.account).toEqual(account);
expect(backup.data['antigravityAuthStatus']).toBeDefined();
expect(backup.data['geminiNexusAuthStatus']).toBeDefined();
});
it('should restore account', () => {
@@ -90,7 +90,7 @@ describe.skip('Database Handler', () => {
last_used: new Date().toISOString(),
},
data: {
antigravityAuthStatus: JSON.stringify({
geminiNexusAuthStatus: JSON.stringify({
user: { email: '[email protected]' },
}),
newKey: 'newValue',
@@ -101,7 +101,7 @@ describe.skip('Database Handler', () => {
const db = new Database(tempDbPath);
const row = db
.prepare("SELECT value FROM ItemTable WHERE key = 'antigravityAuthStatus'")
.prepare("SELECT value FROM ItemTable WHERE key = 'geminiNexusAuthStatus'")
.get() as { value: string };
const value = JSON.parse(row.value);
expect(value.user.email).toBe('[email protected]');
@@ -1,7 +1,7 @@
import { describe, it, expect, vi } from 'vitest';
import { compareVersion, isNewVersion } from '../../utils/antigravityVersion';
import { compareVersion, isNewVersion } from '../../utils/geminiNexusVersion';
describe('antigravityVersion', () => {
describe('geminiNexusVersion', () => {
it('should compare versions correctly', () => {
expect(compareVersion('1.16.5', '1.16.4')).toBe(1);
expect(compareVersion('1.16.5', '1.16.5')).toBe(0);
@@ -43,12 +43,12 @@ describe('antigravityVersion', () => {
},
}));
vi.doMock('../../utils/paths', () => ({
getAntigravityExecutablePath: () => 'C:\\Program Files\\Antigravity\\Antigravity.exe',
getGeminiNexusExecutablePath: () => 'C:\\Program Files\\GeminiNexus\\GeminiNexus.exe',
}));
const { getAntigravityVersion } = await import('../../utils/antigravityVersion');
const version = getAntigravityVersion();
const cached = getAntigravityVersion();
const { getGeminiNexusVersion } = await import('../../utils/geminiNexusVersion');
const version = getGeminiNexusVersion();
const cached = getGeminiNexusVersion();
expect(version.shortVersion).toBe('1.16.6');
expect(cached.shortVersion).toBe('1.16.6');
+3 -3
View File
@@ -114,7 +114,7 @@ describe('GoogleAPIService OAuth clients', () => {
const { GoogleAPIService } = await import('../../services/GoogleAPIService');
const clients = GoogleAPIService.listOAuthClients();
expect(clients.find((client) => client.key === 'antigravity_enterprise')).toBeDefined();
expect(clients.find((client) => client.key === 'geminiNexus_enterprise')).toBeDefined();
expect(clients.find((client) => client.key === 'custom_a')?.label).toBe('Custom A');
expect(clients.find((client) => client.key === 'custom_b')?.is_active).toBe(true);
});
@@ -272,7 +272,7 @@ describe('GoogleAPIService fetchQuota fallback policy', () => {
metadata: {
ide_type: 'ANTIGRAVITY',
ide_version: expectedVersion,
ide_name: 'antigravity',
ide_name: 'geminiNexus',
},
}),
);
@@ -410,7 +410,7 @@ describe('QuotaService fallback policy', () => {
isAxiosError: (error: unknown) => Boolean((error as { isAxiosError?: boolean })?.isAxiosError),
}));
const { QuotaService } = await import('../../lib/antigravity/QuotaService');
const { QuotaService } = await import('../../lib/geminiNexus/QuotaService');
await expect(QuotaService.fetchQuota('access-token', '[email protected]')).rejects.toThrow(
'HTTP 400 - {"error":"INVALID_ARGUMENT"}',
+8 -8
View File
@@ -1,9 +1,9 @@
import { describe, it, expect } from 'vitest';
import {
getAppDataDir,
getAntigravityDbPath,
getAntigravityStoragePath,
getAntigravityExecutablePath,
getGeminiNexusDbPath,
getGeminiNexusStoragePath,
getGeminiNexusExecutablePath,
} from '../../utils/paths';
describe('Path Utilities', () => {
@@ -14,21 +14,21 @@ describe('Path Utilities', () => {
});
it('should get correct DB path', () => {
const dbPath = getAntigravityDbPath();
const dbPath = getGeminiNexusDbPath();
expect(dbPath).toContain('state.vscdb');
});
it('should get correct storage path', () => {
const storagePath = getAntigravityStoragePath();
const storagePath = getGeminiNexusStoragePath();
expect(storagePath).toContain('storage.json');
});
it('should get correct executable path', () => {
const execPath = getAntigravityExecutablePath();
const execPath = getGeminiNexusExecutablePath();
if (process.platform === 'linux') {
expect(execPath).toBe('/usr/share/antigravity/antigravity');
expect(execPath).toBe('/usr/share/geminiNexus/geminiNexus');
} else if (process.platform === 'darwin') {
expect(execPath).toBe('/Applications/Antigravity.app/Contents/MacOS/Antigravity');
expect(execPath).toBe('/Applications/GeminiNexus.app/Contents/MacOS/GeminiNexus');
}
// Windows path depends on env vars, harder to test strictly without mocking
});
+24 -24
View File
@@ -17,12 +17,12 @@ vi.mock('../../utils/logger', () => ({
// Mock paths module to avoid child_process issues
vi.mock('../../utils/paths', () => ({
getAntigravityExecutablePath: vi.fn(() => '/path/to/antigravity'),
getGeminiNexusExecutablePath: vi.fn(() => '/path/to/geminiNexus'),
isWsl: vi.fn(() => false),
}));
// Import after mocks are set up
import { isProcessRunning, closeAntigravity, startAntigravity } from '../../ipc/process/handler';
import { isProcessRunning, closeGeminiNexus, startGeminiNexus } from '../../ipc/process/handler';
import findProcess from 'find-process';
describe('Process Handler', () => {
@@ -33,21 +33,21 @@ describe('Process Handler', () => {
});
describe('isProcessRunning', () => {
it('should return true when Antigravity main process is found on macOS', async () => {
it('should return true when Gemini Nexus main process is found on macOS', async () => {
Object.defineProperty(process, 'platform', { value: 'darwin', configurable: true });
Object.defineProperty(process, 'pid', { value: 1000, configurable: true });
mockFindProcess.mockResolvedValue([
{
pid: 12345,
name: 'Antigravity',
cmd: '/Applications/Antigravity.app/Contents/MacOS/Antigravity',
name: 'Gemini Nexus',
cmd: '/Applications/GeminiNexus.app/Contents/MacOS/GeminiNexus',
},
]);
const result = await isProcessRunning();
expect(result).toBe(true);
expect(mockFindProcess).toHaveBeenCalledWith('name', 'Antigravity', true);
expect(mockFindProcess).toHaveBeenCalledWith('name', 'Gemini Nexus', true);
});
it('should return false when only helper processes are found', async () => {
@@ -57,13 +57,13 @@ describe('Process Handler', () => {
mockFindProcess.mockResolvedValue([
{
pid: 12346,
name: 'Antigravity Helper (Renderer)',
cmd: '/Applications/Antigravity.app/Contents/Frameworks/Antigravity Helper (Renderer).app --type=renderer',
name: 'Gemini Nexus Helper (Renderer)',
cmd: '/Applications/GeminiNexus.app/Contents/Frameworks/Gemini Nexus Helper (Renderer).app --type=renderer',
},
{
pid: 12347,
name: 'Antigravity Helper (GPU)',
cmd: '/Applications/Antigravity.app/Contents/Frameworks/Antigravity Helper (GPU).app --type=gpu-process',
name: 'Gemini Nexus Helper (GPU)',
cmd: '/Applications/GeminiNexus.app/Contents/Frameworks/Gemini Nexus Helper (GPU).app --type=gpu-process',
},
]);
@@ -78,8 +78,8 @@ describe('Process Handler', () => {
mockFindProcess.mockResolvedValue([
{
pid: 12348,
name: 'Antigravity Manager',
cmd: '/Applications/Antigravity Manager.app/Contents/MacOS/Antigravity Manager',
name: 'Gemini Nexus',
cmd: '/Applications/Gemini Nexus.app/Contents/MacOS/Gemini Nexus',
},
]);
@@ -104,8 +104,8 @@ describe('Process Handler', () => {
mockFindProcess.mockResolvedValue([
{
pid: 12345, // Same as current PID
name: 'Antigravity',
cmd: '/Applications/Antigravity.app/Contents/MacOS/Antigravity',
name: 'Gemini Nexus',
cmd: '/Applications/GeminiNexus.app/Contents/MacOS/GeminiNexus',
},
]);
@@ -113,15 +113,15 @@ describe('Process Handler', () => {
expect(result).toBe(false);
});
it('should return true when Antigravity.exe is found on Windows', async () => {
it('should return true when Gemini Nexus.exe is found on Windows', async () => {
Object.defineProperty(process, 'platform', { value: 'win32', configurable: true });
Object.defineProperty(process, 'pid', { value: 1000, configurable: true });
mockFindProcess.mockResolvedValue([
{
pid: 12345,
name: 'Antigravity.exe',
cmd: 'C:\\Program Files\\Antigravity\\Antigravity.exe',
name: 'GeminiNexus.exe',
cmd: 'C:\\Program Files\\GeminiNexus\\GeminiNexus.exe',
},
]);
@@ -129,15 +129,15 @@ describe('Process Handler', () => {
expect(result).toBe(true);
});
it('should return true when antigravity is found on Linux', async () => {
it('should return true when geminiNexus is found on Linux', async () => {
Object.defineProperty(process, 'platform', { value: 'linux', configurable: true });
Object.defineProperty(process, 'pid', { value: 1000, configurable: true });
mockFindProcess.mockResolvedValue([
{
pid: 12345,
name: 'antigravity',
cmd: '/usr/bin/antigravity',
name: 'geminiNexus',
cmd: '/usr/bin/geminiNexus',
},
]);
@@ -162,8 +162,8 @@ describe('Process Handler', () => {
mockFindProcess.mockResolvedValue([
{
pid: 12345,
name: 'Antigravity',
cmd: '/Applications/Antigravity.app/Contents/MacOS/Antigravity --type=utility',
name: 'Gemini Nexus',
cmd: '/Applications/GeminiNexus.app/Contents/MacOS/Gemini Nexus --type=utility',
},
]);
@@ -175,8 +175,8 @@ describe('Process Handler', () => {
describe('Module exports', () => {
it('should export all required functions', () => {
expect(isProcessRunning).toBeDefined();
expect(closeAntigravity).toBeDefined();
expect(startAntigravity).toBeDefined();
expect(closeGeminiNexus).toBeDefined();
expect(startGeminiNexus).toBeDefined();
});
});
});
+1 -1
View File
@@ -1,5 +1,5 @@
import { describe, it, expect } from 'vitest';
import { applyJitter, calculateRetryDelay } from '../../lib/antigravity/retry-utils';
import { applyJitter, calculateRetryDelay } from '../../lib/geminiNexus/retry-utils';
describe('applyJitter', () => {
it('should return values within ±20% range', () => {
+1 -1
View File
@@ -1,5 +1,5 @@
import { describe, it, expect } from 'vitest';
import { decodeSignature } from '../../lib/antigravity/signature-utils';
import { decodeSignature } from '../../lib/geminiNexus/signature-utils';
describe('decodeSignature', () => {
describe('Base64 decoding', () => {
+1 -1
View File
@@ -1,5 +1,5 @@
import { describe, it, expect } from 'vitest';
import { classifyStreamError, formatErrorForSSE } from '../../lib/antigravity/stream-error-utils';
import { classifyStreamError, formatErrorForSSE } from '../../lib/geminiNexus/stream-error-utils';
describe('classifyStreamError', () => {
it('should identify timeout errors', () => {
+1 -1
View File
@@ -1,5 +1,5 @@
import { describe, it, expect, beforeEach } from 'vitest';
import { PartProcessor, StreamingState } from '../../lib/antigravity/ClaudeStreamingMapper';
import { PartProcessor, StreamingState } from '../../lib/geminiNexus/ClaudeStreamingMapper';
describe('StreamingState', () => {
let state: StreamingState;
+1 -1
View File
@@ -3,7 +3,7 @@ import {
hasValidSignature,
gracefulDegradeThinking,
processThinkingBlocks,
} from '../../lib/antigravity/thinking-utils';
} from '../../lib/geminiNexus/thinking-utils';
describe('hasValidSignature', () => {
it('should return true for empty thinking + valid signature', () => {
@@ -141,7 +141,7 @@ describe('TokenManagerService parity scheduling replay', () => {
oauth_client_key: undefined,
project_id: undefined,
},
'antigravity_enterprise',
'geminiNexus_enterprise',
);
expect(normalized).toBeUndefined();
+3 -3
View File
@@ -38,10 +38,10 @@ export interface AccountBackupData {
version: string; // Backup format version
account: Account;
data: {
// Key-value pairs from Antigravity database
antigravityAuthStatus?: string;
// Key-value pairs from Gemini Nexus database
geminiNexusAuthStatus?: string;
'jetskiStateSync.agentManagerInitState'?: string;
'antigravityUnifiedStateSync.oauthToken'?: string;
'geminiNexusUnifiedStateSync.oauthToken'?: string;
[key: string]: unknown;
};
}
+5 -5
View File
@@ -40,12 +40,12 @@ export const SettingsValueRowSchema = z.object({
export type SettingsValueRow = z.infer<typeof SettingsValueRowSchema>;
export const ItemTableKeys = [
'antigravityAuthStatus',
'antigravityOnboarding',
'antigravityUnifiedStateSync.oauthToken',
'geminiNexusAuthStatus',
'geminiNexusOnboarding',
'geminiNexusUnifiedStateSync.oauthToken',
'jetskiStateSync.agentManagerInitState',
'google.antigravity',
'antigravityUserSettings.allUserSettings',
'google.geminiNexus',
'geminiNexusUserSettings.allUserSettings',
] as const;
export const ItemTableKeySchema = z.enum(ItemTableKeys);
+3 -3
View File
@@ -6,7 +6,7 @@ import { logger } from './logger';
import { AppConfig } from '../types/config';
export const AUTO_START_ARG = '--autostart';
const LINUX_AUTOSTART_FILENAME = 'antigravity-manager.desktop';
const LINUX_AUTOSTART_FILENAME = 'gemini-nexus.desktop';
function getLinuxAutoStartPath() {
const dir = path.join(os.homedir(), '.config', 'autostart');
@@ -59,8 +59,8 @@ function cleanupLinuxAutoStartEntries(currentPath: string) {
const legacyNames = [
`${app.getName()}.desktop`,
`${app.getName().replace(/\s+/g, '')}.desktop`,
'Antigravity Manager.desktop',
'AntigravityManager.desktop',
'Gemini Nexus.desktop',
'GeminiNexusManager.desktop',
];
for (const name of legacyNames) {
@@ -1,22 +1,22 @@
import fs from 'fs';
import path from 'path';
import { execSync } from 'child_process';
import { getAntigravityExecutablePath } from './paths';
import { getGeminiNexusExecutablePath } from './paths';
export interface AntigravityVersion {
export interface GeminiNexusVersion {
shortVersion: string;
bundleVersion: string;
}
let cachedVersion: AntigravityVersion | null = null;
let cachedVersion: GeminiNexusVersion | null = null;
let cachedError: Error | null = null;
function cacheAndReturn(version: AntigravityVersion): AntigravityVersion {
function cacheAndReturn(version: GeminiNexusVersion): GeminiNexusVersion {
cachedVersion = version;
return version;
}
function readPackageJsonVersion(execPath: string): AntigravityVersion | null {
function readPackageJsonVersion(execPath: string): GeminiNexusVersion | null {
const parentDir = path.dirname(execPath);
const packageJson = path.join(parentDir, 'resources', 'app', 'package.json');
if (!fs.existsSync(packageJson)) {
@@ -55,7 +55,7 @@ function parseVersionString(version: string | null): string {
return trimmed;
}
export function getAntigravityVersion(): AntigravityVersion {
export function getGeminiNexusVersion(): GeminiNexusVersion {
if (cachedVersion) {
return cachedVersion;
}
@@ -64,9 +64,9 @@ export function getAntigravityVersion(): AntigravityVersion {
}
try {
const execPath = getAntigravityExecutablePath();
const execPath = getGeminiNexusExecutablePath();
if (!execPath) {
throw new Error('Unable to locate Antigravity executable');
throw new Error('Unable to locate Gemini Nexus executable');
}
if (process.platform === 'win32') {
@@ -144,10 +144,10 @@ export function getAntigravityVersion(): AntigravityVersion {
}
}
throw new Error('Unable to determine Antigravity version');
throw new Error('Unable to determine Gemini Nexus version');
} catch (error) {
const normalized =
error instanceof Error ? error : new Error('Unable to determine Antigravity version');
error instanceof Error ? error : new Error('Unable to determine Gemini Nexus version');
cachedError = normalized;
throw normalized;
}
@@ -176,6 +176,6 @@ export function compareVersion(v1: string, v2: string): number {
return 0;
}
export function isNewVersion(version: AntigravityVersion): boolean {
export function isNewVersion(version: GeminiNexusVersion): boolean {
return compareVersion(version.shortVersion, '1.16.5') >= 0;
}
+34 -34
View File
@@ -78,27 +78,27 @@ export function getAppDataDir(): string {
if (isWsl()) {
const winUser = getWindowsUser();
return `/mnt/c/Users/${winUser}/AppData/Roaming/Antigravity`;
return `/mnt/c/Users/${winUser}/AppData/Roaming/GeminiNexus`;
}
switch (process.platform) {
case 'darwin':
return path.join(home, 'Library', 'Application Support', 'Antigravity');
return path.join(home, 'Library', 'Application Support', 'Gemini Nexus');
case 'win32':
return path.join(process.env.APPDATA || path.join(home, 'AppData', 'Roaming'), 'Antigravity');
return path.join(process.env.APPDATA || path.join(home, 'AppData', 'Roaming'), 'Gemini Nexus');
case 'linux':
return path.join(home, '.config', 'Antigravity');
return path.join(home, '.config', 'Gemini Nexus');
default:
return path.join(home, '.antigravity');
return path.join(home, '.geminiNexus');
}
}
export function getAgentDir(): string {
return path.join(os.homedir(), '.antigravity-agent');
return path.join(os.homedir(), '.geminiNexus-agent');
}
export function getAccountsFilePath(): string {
return path.join(getAgentDir(), 'antigravity_accounts.json');
return path.join(getAgentDir(), 'geminiNexus_accounts.json');
}
export function getBackupsDir(): string {
@@ -109,14 +109,14 @@ export function getCloudAccountsDbPath(): string {
return path.join(getAgentDir(), 'cloud_accounts.db');
}
export function getAntigravityDbPaths(): string[] {
export function getGeminiNexusDbPaths(): string[] {
const appData = getAppDataDir();
const paths: string[] = [];
const home = os.homedir();
if (isWsl()) {
// Assume standard structure: AppData/Roaming/Antigravity/User/globalStorage/state.vscdb
// appData is already resolved to Roaming/Antigravity in getAppDataDir()
// Assume standard structure: AppData/Roaming/GeminiNexus/User/globalStorage/state.vscdb
// appData is already resolved to Roaming/Gemini Nexus in getAppDataDir()
paths.push(path.join(appData, 'User', 'globalStorage', 'state.vscdb'));
paths.push(path.join(appData, 'User', 'state.vscdb'));
paths.push(path.join(appData, 'state.vscdb'));
@@ -137,14 +137,14 @@ export function getAntigravityDbPaths(): string[] {
home,
'Library',
'Application Support',
'Antigravity',
'Gemini Nexus',
'User',
'globalStorage',
'state.vscdb',
),
);
// Fallback path
paths.push(path.join(home, 'Library', 'Application Support', 'Antigravity', 'state.vscdb'));
paths.push(path.join(home, 'Library', 'Application Support', 'Gemini Nexus', 'state.vscdb'));
return paths;
}
@@ -158,7 +158,7 @@ export function getAntigravityDbPaths(): string[] {
return paths;
}
export function getAntigravityStoragePaths(): string[] {
export function getGeminiNexusStoragePaths(): string[] {
const appData = getAppDataDir();
const paths: string[] = [];
const home = os.homedir();
@@ -183,13 +183,13 @@ export function getAntigravityStoragePaths(): string[] {
home,
'Library',
'Application Support',
'Antigravity',
'Gemini Nexus',
'User',
'globalStorage',
'storage.json',
),
);
paths.push(path.join(home, 'Library', 'Application Support', 'Antigravity', 'storage.json'));
paths.push(path.join(home, 'Library', 'Application Support', 'Gemini Nexus', 'storage.json'));
return paths;
}
@@ -199,35 +199,35 @@ export function getAntigravityStoragePaths(): string[] {
return paths;
}
export function getAntigravityStoragePath(): string {
const paths = getAntigravityStoragePaths();
export function getGeminiNexusStoragePath(): string {
const paths = getGeminiNexusStoragePaths();
return paths.length > 0 ? paths[0] : '';
}
// Keep for backward compatibility if needed, but prefer getAntigravityDbPaths
export function getAntigravityDbPath(): string {
const paths = getAntigravityDbPaths();
// Keep for backward compatibility if needed, but prefer getGeminiNexusDbPaths
export function getGeminiNexusDbPath(): string {
const paths = getGeminiNexusDbPaths();
return paths.length > 0 ? paths[0] : '';
}
export function getAntigravityExecutablePath(): string {
export function getGeminiNexusExecutablePath(): string {
if (isWsl()) {
const winUser = getWindowsUser();
return `/mnt/c/Users/${winUser}/AppData/Local/Programs/Antigravity/Antigravity.exe`;
return `/mnt/c/Users/${winUser}/AppData/Local/Programs/GeminiNexus/GeminiNexus.exe`;
}
switch (process.platform) {
case 'darwin':
return '/Applications/Antigravity.app/Contents/MacOS/Antigravity';
return '/Applications/GeminiNexus.app/Contents/MacOS/GeminiNexus';
case 'win32': {
const localAppData = process.env.LOCALAPPDATA || '';
const programFiles = process.env.ProgramFiles || 'C:\\Program Files';
const programFilesX86 = process.env['ProgramFiles(x86)'] || 'C:\\Program Files (x86)';
const possiblePaths = [
path.join(localAppData, 'Programs', 'Antigravity', 'Antigravity.exe'),
path.join(programFiles, 'Antigravity', 'Antigravity.exe'),
path.join(programFilesX86, 'Antigravity', 'Antigravity.exe'),
path.join(localAppData, 'Programs', 'Gemini Nexus', 'GeminiNexus.exe'),
path.join(programFiles, 'Gemini Nexus', 'GeminiNexus.exe'),
path.join(programFilesX86, 'Gemini Nexus', 'GeminiNexus.exe'),
];
for (const p of possiblePaths) {
@@ -241,12 +241,12 @@ export function getAntigravityExecutablePath(): string {
}
case 'linux': {
const possibleLinuxPaths = [
'/usr/bin/antigravity',
'/usr/local/bin/antigravity',
'/usr/share/antigravity/antigravity',
'/opt/Antigravity/antigravity',
'/opt/antigravity/antigravity',
path.join(os.homedir(), '.local', 'share', 'antigravity', 'antigravity'),
'/usr/bin/geminiNexus',
'/usr/local/bin/geminiNexus',
'/usr/share/geminiNexus/geminiNexus',
'/opt/GeminiNexus/geminiNexus',
'/opt/geminiNexus/geminiNexus',
path.join(os.homedir(), '.local', 'share', 'geminiNexus', 'geminiNexus'),
];
for (const p of possibleLinuxPaths) {
@@ -255,9 +255,9 @@ export function getAntigravityExecutablePath(): string {
}
}
// Fallback: try `which antigravity` via path lookup
// Fallback: try `which geminiNexus` via path lookup
const fromPath = process.env.PATH?.split(':')
.map((dir) => path.join(dir, 'antigravity'))
.map((dir) => path.join(dir, 'geminiNexus'))
.find((p) => fs.existsSync(p));
if (fromPath) {
return fromPath;
+1 -1
View File
@@ -4,7 +4,7 @@ import { safeStorage, app } from 'electron';
import path from 'path';
import fs from 'fs/promises';
const SERVICE_NAME = 'AntigravityManager';
const SERVICE_NAME = 'GeminiNexusManager';
const ACCOUNT_NAME = 'MasterKey';
const KEYCHAIN_ERROR_CODE = 'ERR_KEYCHAIN_UNAVAILABLE';
const KEYCHAIN_HINT_TRANSLOCATION = 'HINT_APP_TRANSLOCATION';