feat: complete rebranding to Gemini Nexus with improved UI, proxies and documentation
No files matched your search
@@ -1,13 +1,13 @@
|
||||
---
|
||||
name: antigravity-manager
|
||||
description: Comprehensive guide to Antigravity Manager architecture, workflows, and development. Use this to understand how to work on the project.
|
||||
name: geminiNexus-manager
|
||||
description: Comprehensive guide to Gemini Nexus Manager architecture, workflows, and development. Use this to understand how to work on the project.
|
||||
---
|
||||
|
||||
# Antigravity Manager Developer Guide
|
||||
# Gemini Nexus Manager Developer Guide
|
||||
|
||||
## 🏗️ Architecture Overview
|
||||
|
||||
Antigravity Manager is a hybrid Desktop Application built with Electron, React, and NestJS. It follows a modular architecture where the frontend (Renderer) communicates with the backend (Main) via type-safe IPC (ORPC).
|
||||
Gemini Nexus Manager is a hybrid Desktop Application built with Electron, React, and NestJS. It follows a modular architecture where the frontend (Renderer) communicates with the backend (Main) via type-safe IPC (ORPC).
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
|
||||
@@ -301,7 +301,7 @@
|
||||
|
||||
### Added
|
||||
|
||||
- Initial release of Antigravity Manager
|
||||
- Initial release of Gemini Nexus
|
||||
- Multi-account management for Google Gemini and Claude
|
||||
- Real-time quota monitoring
|
||||
- Intelligent auto-switching capabilities
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# Contributing to Antigravity Manager
|
||||
# Contributing to Gemini Nexus
|
||||
|
||||
First off, thank you for considering contributing to Antigravity Manager! 🎉
|
||||
First off, thank you for considering contributing to Gemini Nexus! 🎉
|
||||
|
||||
It's people like you that make Antigravity Manager such a great tool. We welcome contributions from everyone, whether it's a bug report, feature suggestion, documentation improvement, or code contribution.
|
||||
It's people like you that make Gemini Nexus such a great tool. We welcome contributions from everyone, whether it's a bug report, feature suggestion, documentation improvement, or code contribution.
|
||||
|
||||
## 📋 Table of Contents
|
||||
|
||||
@@ -24,8 +24,8 @@ This project and everyone participating in it is governed by our [Code of Conduc
|
||||
1. **Fork the repository** on GitHub
|
||||
2. **Clone your fork** locally:
|
||||
```bash
|
||||
git clone https://github.com/YOUR_USERNAME/AntigravityManager.git
|
||||
cd AntigravityManager
|
||||
git clone https://github.com/YOUR_USERNAME/GeminiNexusManager.git
|
||||
cd Gemini NexusManager
|
||||
```
|
||||
3. **Add the upstream remote**:
|
||||
```bash
|
||||
@@ -178,7 +178,7 @@ Have an idea? Please [create an issue](https://github.com/Draculabo/AntigravityM
|
||||
|
||||
## 🙏 Thank You
|
||||
|
||||
Your contributions help make Antigravity Manager better for everyone. Thank you for taking the time to contribute!
|
||||
Your contributions help make Gemini Nexus better for everyone. Thank you for taking the time to contribute!
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -1,50 +1,41 @@
|
||||
<p align="center">
|
||||
<img src="docs/assets/logo.png" alt="Antigravity Manager" width="128" height="128" />
|
||||
<img src="docs/assets/logo.png" alt="Gemini Nexus" width="128" height="128" />
|
||||
</p>
|
||||
|
||||
<h1 align="center">Antigravity Manager</h1>
|
||||
<h1 align="center">Gemini Nexus</h1>
|
||||
|
||||
<p align="center">
|
||||
<strong>🚀 Professional multi-account manager for Google Gemini & Claude AI</strong>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
English | <a href="README.zh-CN.md">简体中文</a>
|
||||
English | <a href="README.pt-BR.md">Português (Brasil)</a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/Draculabo/AntigravityManager/actions/workflows/testing.yaml">
|
||||
<img src="https://github.com/Draculabo/AntigravityManager/actions/workflows/testing.yaml/badge.svg" alt="Tests" />
|
||||
<a href="https://github.com/Draculabo/GeminiNexus/actions/workflows/testing.yaml">
|
||||
<img src="https://github.com/Draculabo/GeminiNexus/actions/workflows/testing.yaml/badge.svg" alt="Tests" />
|
||||
</a>
|
||||
<a href="https://github.com/Draculabo/AntigravityManager/actions/workflows/lint.yaml">
|
||||
<img src="https://github.com/Draculabo/AntigravityManager/actions/workflows/lint.yaml/badge.svg" alt="Lint" />
|
||||
<a href="https://github.com/Draculabo/GeminiNexus/actions/workflows/lint.yaml">
|
||||
<img src="https://github.com/Draculabo/GeminiNexus/actions/workflows/lint.yaml/badge.svg" alt="Lint" />
|
||||
</a>
|
||||
<a href="https://github.com/Draculabo/AntigravityManager/releases">
|
||||
<img src="https://img.shields.io/github/v/release/Draculabo/AntigravityManager?style=flat-square" alt="Release" />
|
||||
<a href="https://github.com/Draculabo/GeminiNexus/releases">
|
||||
<img src="https://img.shields.io/github/v/release/Draculabo/GeminiNexus?style=flat-square" alt="Release" />
|
||||
</a>
|
||||
<a href="https://github.com/Draculabo/AntigravityManager/releases">
|
||||
<img src="https://img.shields.io/github/downloads/Draculabo/AntigravityManager/total?style=flat-square&color=blue" alt="Downloads" />
|
||||
<a href="https://github.com/Draculabo/GeminiNexus/releases">
|
||||
<img src="https://img.shields.io/github/downloads/Draculabo/GeminiNexus/total?style=flat-square&color=blue" alt="Downloads" />
|
||||
</a>
|
||||
<a href="https://github.com/Draculabo/AntigravityManager/blob/main/LICENSE">
|
||||
<img src="https://img.shields.io/github/license/Draculabo/AntigravityManager?style=flat-square" alt="License" />
|
||||
<a href="https://github.com/Draculabo/GeminiNexus/blob/main/LICENSE">
|
||||
<img src="https://img.shields.io/github/license/Draculabo/GeminiNexus?style=flat-square" alt="License" />
|
||||
</a>
|
||||
<a href="https://github.com/Draculabo/AntigravityManager/stargazers">
|
||||
<img src="https://img.shields.io/github/stars/Draculabo/AntigravityManager?style=flat-square" alt="Stars" />
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<img src="https://img.shields.io/badge/Electron-191970?style=for-the-badge&logo=Electron&logoColor=white" alt="Electron" />
|
||||
<img src="https://img.shields.io/badge/React-20232A?style=for-the-badge&logo=react&logoColor=61DAFB" alt="React" />
|
||||
<img src="https://img.shields.io/badge/TypeScript-007ACC?style=for-the-badge&logo=typescript&logoColor=white" alt="TypeScript" />
|
||||
<img src="https://img.shields.io/badge/Tailwind_CSS-38B2AC?style=for-the-badge&logo=tailwind-css&logoColor=white" alt="TailwindCSS" />
|
||||
</p>
|
||||
|
||||
---
|
||||
|
||||
## 📖 Table of Contents
|
||||
|
||||
- [Why Antigravity Manager?](#-why-antigravity-manager)
|
||||
- [Why Gemini Nexus?](#-why-gemini-nexus)
|
||||
- [Improvements over Base Project](#-improvements-over-base-project)
|
||||
- [Features](#-features)
|
||||
- [Screenshots](#-screenshots)
|
||||
- [Quick Start](#-quick-start)
|
||||
@@ -57,9 +48,9 @@
|
||||
|
||||
---
|
||||
|
||||
## ✨ Why Antigravity Manager?
|
||||
## ✨ Why Gemini Nexus?
|
||||
|
||||
When using Antigravity IDE, have you ever encountered these problems?
|
||||
When using your favorite IDEs, have you ever encountered these problems?
|
||||
|
||||
- 😫 Single account quota runs out quickly, requiring frequent manual switching
|
||||
- 🔄 Managing multiple Google/Claude accounts is cumbersome
|
||||
@@ -67,7 +58,7 @@ When using Antigravity IDE, have you ever encountered these problems?
|
||||
- ⏰ Worried about missing quota reset times
|
||||
- 🔌 Need a reliable local API proxy for development tools
|
||||
|
||||
**Antigravity Manager** is here to solve these problems! It's a professional Electron desktop app that helps you:
|
||||
**Gemini Nexus** is here to solve these problems! It's a professional Electron desktop app that helps you:
|
||||
|
||||
- ✅ **Unlimited Account Pool** - Add any number of Google Gemini / Claude accounts
|
||||
- ✅ **Smart Auto-Switching** - Automatically switch to the next available account when quota is low or rate-limited
|
||||
@@ -77,6 +68,19 @@ When using Antigravity IDE, have you ever encountered these problems?
|
||||
|
||||
---
|
||||
|
||||
## 🌟 Improvements over Base Project
|
||||
|
||||
This project contains significant development improvements, bug fixes, and configuration enhancements compared to the original base project.
|
||||
|
||||
Key improvements include:
|
||||
- **Enhanced API Management UI:** Implemented dynamic model selectors and "Copy API Name" features to streamline integration with other tools.
|
||||
- **Robust SSE Streaming:** Fixed critical Server-Sent Events (SSE) streaming bugs in the `/v1/chat/completions` proxy endpoint to perfectly support IDEs like Cursor, OpenCode, and Windsurf.
|
||||
- **Rebranding and Localization:** Complete rebranding to Gemini Nexus with improved Portuguese (Brazil) localization and English defaults.
|
||||
- **Agentic Capabilities:** Integration of advanced AI developer agents and skill frameworks (GSD, AutoResearch, Superpowers) directly into the project ecosystem for rapid autonomous development.
|
||||
- **Build & Dev Env:** Better cross-platform build configurations, updated schemas, and automated toolchain processes.
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Features
|
||||
|
||||
<table>
|
||||
@@ -116,24 +120,6 @@ When using Antigravity IDE, have you ever encountered these problems?
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="50%">
|
||||
<h3>💾 Account Backup</h3>
|
||||
<ul>
|
||||
<li>Capture snapshots of account state</li>
|
||||
<li>Fast switching between saved accounts</li>
|
||||
<li>View, organize, and delete snapshots</li>
|
||||
</ul>
|
||||
</td>
|
||||
<td width="50%">
|
||||
<h3>⚙️ Process Control</h3>
|
||||
<ul>
|
||||
<li>Auto-detect if Antigravity is running</li>
|
||||
<li>Launch via URI protocol or executable</li>
|
||||
<li>Graceful close or force kill</li>
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="50%">
|
||||
<h3>🔌 Local API Proxy</h3>
|
||||
@@ -154,19 +140,6 @@ When using Antigravity IDE, have you ever encountered these problems?
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
### Additional Features
|
||||
|
||||
- **🖥️ System Tray** - Background mode with tray icon and right-click menu
|
||||
- **🔗 IDE Sync** - Automatically scan and import accounts from IDE's `state.vscdb`
|
||||
- **📦 Batch Operations** - Batch refresh and delete multiple accounts
|
||||
- **📤 JSON Export/Import** - Export and import account pools with schema validation and deduplication
|
||||
- **🔔 Desktop Notifications** - Configurable alerts when model quota drops below your threshold
|
||||
- **🌐 Per-Account Proxy** - Route individual accounts through their own HTTP/SOCKS5 proxy
|
||||
- **📊 Smart Sorting** - Sort accounts by recently used, overall quota, or specific model groups
|
||||
- **📋 Compact Layout** - Dense horizontal view to maximize visible accounts
|
||||
- **🌏 Internationalization** - Multi-language support (English / 中文 / Русский)
|
||||
- **🎨 Modern UI** - Built with React, TailwindCSS, and Shadcn UI
|
||||
|
||||
---
|
||||
|
||||
## 📸 Screenshots
|
||||
@@ -175,86 +148,23 @@ When using Antigravity IDE, have you ever encountered these problems?
|
||||
<img src="docs/assets/screenshot-main.png" alt="Main Interface" width="80%" />
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<img src="docs/assets/screenshot-proxy.png" alt="Proxy Interface" width="48%" />
|
||||
<img src="docs/assets/screenshot-setting.png" alt="Settings Interface" width="48%" />
|
||||
</p>
|
||||
|
||||
|
||||
---
|
||||
|
||||
## � Quick Start
|
||||
|
||||
### Download
|
||||
|
||||
Download the latest release for your platform from the [Releases](https://github.com/Draculabo/AntigravityManager/releases) page.
|
||||
|
||||
| Platform | Download |
|
||||
|----------|----------|
|
||||
| Windows (x64/ARM64) | [.exe installer](https://github.com/Draculabo/AntigravityManager/releases/latest) |
|
||||
| macOS | [.dmg installer](https://github.com/Draculabo/AntigravityManager/releases/latest) |
|
||||
| Linux | [.deb / .rpm](https://github.com/Draculabo/AntigravityManager/releases/latest) |
|
||||
| NixOS / Nix | `nix run github:Draculabo/AntigravityManager` |
|
||||
|
||||
### ❄️ Nix Integration
|
||||
|
||||
You can integrate Antigravity Manager into your Nix configuration using the provided flake.
|
||||
|
||||
#### 🛠️ NixOS / Home Manager
|
||||
|
||||
Add the flake to your `flake.nix` inputs:
|
||||
|
||||
```nix
|
||||
inputs = {
|
||||
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
|
||||
antigravity-manager.url = "github:Draculabo/AntigravityManager";
|
||||
};
|
||||
```
|
||||
|
||||
To integrate with your system, add `antigravity-manager.overlays.default` to your `nixpkgs.overlays`. This ensures the package is correctly built against your own `nixpkgs` instance and respects your global configuration.
|
||||
|
||||
```nix
|
||||
# Add to your NixOS/Home Manager configuration
|
||||
{
|
||||
nixpkgs.overlays = [ inputs.antigravity-manager.overlays.default ];
|
||||
|
||||
# Antigravity Manager has an unfree license (cc-by-nc-sa-40)
|
||||
nixpkgs.config.allowUnfree = true;
|
||||
|
||||
environment.systemPackages = [
|
||||
pkgs.antigravity-manager
|
||||
];
|
||||
}
|
||||
```
|
||||
|
||||
#### 💻 Development Shell
|
||||
|
||||
If you're using the overlay above, you can simply add it to your `devShell`:
|
||||
|
||||
```nix
|
||||
devShells.default = pkgs.mkShell {
|
||||
packages = [
|
||||
pkgs.antigravity-manager
|
||||
];
|
||||
};
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
> Since the package is unfree, you **must** have `allowUnfree = true;` enabled in your `nixpkgs` configuration for it to evaluate correctly.
|
||||
## ⚡ Quick Start
|
||||
|
||||
### Build from Source
|
||||
|
||||
#### Prerequisites
|
||||
|
||||
- Node.js v18 or higher
|
||||
- npm or yarn
|
||||
- npm or pnpm
|
||||
|
||||
#### Steps
|
||||
|
||||
```bash
|
||||
# Clone the repository
|
||||
git clone https://github.com/Draculabo/AntigravityManager.git
|
||||
cd AntigravityManager
|
||||
git clone https://github.com/Draculabo/GeminiNexus.git
|
||||
cd GeminiNexus
|
||||
|
||||
# Install dependencies
|
||||
npm install
|
||||
@@ -268,56 +178,15 @@ npm run make
|
||||
|
||||
---
|
||||
|
||||
## �🛠️ Tech Stack
|
||||
## 🛠️ Tech Stack
|
||||
|
||||
| Category | Technologies |
|
||||
|----------|-------------|
|
||||
| **Core** | [Electron](https://www.electronjs.org/), [React](https://react.dev/), [TypeScript](https://www.typescriptlang.org/) |
|
||||
| **Build Tool** | [Vite](https://vitejs.dev/) |
|
||||
| **Styling** | [TailwindCSS](https://tailwindcss.com/), [Shadcn UI](https://ui.shadcn.com/) |
|
||||
| **State** | [TanStack Query](https://tanstack.com/query/latest), [TanStack Router](https://tanstack.com/router/latest) |
|
||||
| **State** | [TanStack Query](https://tanstack.com/query/latest) |
|
||||
| **Database** | [Better-SQLite3](https://github.com/WiseLibs/better-sqlite3) |
|
||||
| **Testing** | [Vitest](https://vitest.dev/), [Playwright](https://playwright.dev/) |
|
||||
|
||||
---
|
||||
|
||||
## � Development
|
||||
|
||||
### Available Scripts
|
||||
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
| `npm start` | Start the app in development mode |
|
||||
| `npm run lint` | Run ESLint to check for code issues |
|
||||
| `npm run format:write` | Format code with Prettier |
|
||||
| `npm run test:unit` | Run unit tests with Vitest |
|
||||
| `npm run test:e2e` | Run E2E tests with Playwright |
|
||||
| `npm run test:all` | Run all tests |
|
||||
| `npm run type-check` | Run TypeScript type checking |
|
||||
| `npm run make` | Build production packages |
|
||||
|
||||
### Project Structure
|
||||
|
||||
```
|
||||
AntigravityManager/
|
||||
├── src/
|
||||
│ ├── main.ts # Electron main process
|
||||
│ ├── preload.ts # Preload script
|
||||
│ ├── renderer/ # React renderer process
|
||||
│ ├── ipc/ # IPC communication handlers
|
||||
│ └── server/ # Built-in server
|
||||
├── docs/ # Documentation and assets
|
||||
└── .github/ # GitHub configuration
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔍 Debugging Docs
|
||||
|
||||
If you want to inspect Antigravity or Antigravity Manager traffic with Proxyman, start here:
|
||||
|
||||
- [Proxyman Installation and Basic Setup on Windows](docs/proxyman-install.md)
|
||||
- [Proxyman Debugging Guide for Antigravity and Antigravity Manager](docs/proxyman-debugging.md)
|
||||
|
||||
---
|
||||
|
||||
@@ -342,50 +211,11 @@ Please check:
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>Q: macOS shows Keychain/Credential error and OAuth cannot be saved?</b></summary>
|
||||
|
||||
This is a common macOS security behavior, usually when the app is unsigned or run directly from Downloads.
|
||||
This is a **temporary workaround** for personal use:
|
||||
|
||||
1. Move the app to `/Applications`
|
||||
2. Run the following commands in Terminal (repeat after every update)
|
||||
|
||||
```plaintext
|
||||
sudo xattr -dr com.apple.quarantine "/Applications/Antigravity Manager 2.app"
|
||||
codesign --force --deep --sign - "/Applications/Antigravity Manager 2.app"
|
||||
```
|
||||
|
||||
Reopen the app and allow Keychain access if prompted.
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>Q: How to report issues or suggestions?</b></summary>
|
||||
|
||||
Please submit issues or suggestions via [GitHub Issues](https://github.com/Draculabo/AntigravityManager/issues).
|
||||
|
||||
</details>
|
||||
|
||||
---
|
||||
|
||||
## 🌟 Star History
|
||||
|
||||
<a href="https://github.com/Draculabo/AntigravityManager/stargazers">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=Draculabo/AntigravityManager&type=Date&theme=dark" />
|
||||
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=Draculabo/AntigravityManager&type=Date" />
|
||||
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=Draculabo/AntigravityManager&type=Date" />
|
||||
</picture>
|
||||
</a>
|
||||
|
||||
---
|
||||
|
||||
## 🤝 Contributing
|
||||
|
||||
Contributions are welcome! Please read [CONTRIBUTING.md](CONTRIBUTING.md) for details.
|
||||
|
||||
We follow the [Contributor Covenant](CODE_OF_CONDUCT.md) Code of Conduct.
|
||||
Contributions are welcome! Please read `CONTRIBUTING.md` for details.
|
||||
|
||||
---
|
||||
|
||||
@@ -406,6 +236,6 @@ We follow the [Contributor Covenant](CODE_OF_CONDUCT.md) Code of Conduct.
|
||||
|
||||
---
|
||||
|
||||
<p align="center">
|
||||
If this project helps you, please give it a ⭐ Star!
|
||||
</p>
|
||||
## 🙏 Credits
|
||||
|
||||
**Gemini Nexus** is a significantly enhanced and expanded version of the original project [AntigravityManager](https://github.com/Draculabo/AntigravityManager) created by [Draculabo](https://github.com/Draculabo). All fundamental architecture and design were made by the original author.
|
||||
@@ -0,0 +1,145 @@
|
||||
<p align="center">
|
||||
<img src="docs/assets/logo.png" alt="Gemini Nexus" width="128" height="128" />
|
||||
</p>
|
||||
|
||||
<h1 align="center">Gemini Nexus</h1>
|
||||
|
||||
<p align="center">
|
||||
<strong>🚀 Gerenciador multi-contas profissional para Google Gemini & Claude AI</strong>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="README.md">English</a> | Português (Brasil)
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/Draculabo/GeminiNexus/actions/workflows/testing.yaml">
|
||||
<img src="https://github.com/Draculabo/GeminiNexus/actions/workflows/testing.yaml/badge.svg" alt="Tests" />
|
||||
</a>
|
||||
<a href="https://github.com/Draculabo/GeminiNexus/actions/workflows/lint.yaml">
|
||||
<img src="https://github.com/Draculabo/GeminiNexus/actions/workflows/lint.yaml/badge.svg" alt="Lint" />
|
||||
</a>
|
||||
<a href="https://github.com/Draculabo/GeminiNexus/releases">
|
||||
<img src="https://img.shields.io/github/v/release/Draculabo/GeminiNexus?style=flat-square" alt="Release" />
|
||||
</a>
|
||||
</p>
|
||||
|
||||
---
|
||||
|
||||
## ✨ Por que o Gemini Nexus?
|
||||
|
||||
Quando você usa suas IDEs favoritas, já encontrou estes problemas?
|
||||
|
||||
- 😫 A cota da sua única conta acaba rápido, exigindo trocas manuais frequentes
|
||||
- 🔄 Gerenciar múltiplas contas do Google/Claude é cansativo
|
||||
- 📊 Você não sabe quanta cota resta na conta atual
|
||||
- 🔌 Precisa de um proxy local e confiável para suas ferramentas de desenvolvimento
|
||||
|
||||
**Gemini Nexus** está aqui para resolver tudo isso! Ele é um app desktop profissional construído em Electron que te ajuda a:
|
||||
|
||||
- ✅ **Contas Ilimitadas** - Adicione quantas contas Google Gemini / Claude quiser
|
||||
- ✅ **Troca Automática** - Troca inteligentemente para a próxima conta disponível quando o limite é atingido
|
||||
- ✅ **Monitoramento em Tempo Real** - Visualize o uso de cota de todas as contas
|
||||
- ✅ **Proxy de API Local** - Servidor proxy integrado compatível com OpenAI/Anthropic
|
||||
- ✅ **Criptografia Segura** - Criptografia AES-256-GCM para seus dados sensíveis
|
||||
|
||||
---
|
||||
|
||||
## 🌟 Melhorias sobre o Projeto Base
|
||||
|
||||
Este projeto contém melhorias significativas de desenvolvimento, correção de erros e melhorias de configuração em relação ao projeto original base.
|
||||
|
||||
Principais melhorias incluem:
|
||||
- **Interface de Gerenciamento de API Aprimorada:** Seletores dinâmicos de modelos e recursos de "Copiar Nome da API" implementados para facilitar a integração com outras ferramentas.
|
||||
- **Streaming Robusto (SSE):** Correção de bugs críticos no streaming via Server-Sent Events (SSE) no endpoint proxy `/v1/chat/completions` para suportar perfeitamente IDEs como Cursor, OpenCode e Windsurf.
|
||||
- **Rebranding e Localização:** Rebranding completo para Gemini Nexus com traduções melhoradas em Português (Brasil) e padrões em Inglês.
|
||||
- **Capacidades de Agentes:** Integração direta de frameworks avançados de agentes desenvolvedores de IA e skills (GSD, AutoResearch, Superpowers) no ecossistema do projeto para desenvolvimento autônomo rápido.
|
||||
- **Ambiente de Build e Dev:** Melhores configurações de build cross-platform, schemas atualizados e processos de toolchain automatizados.
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Funcionalidades
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td width="50%">
|
||||
<h3>☁️ Pool de Contas na Nuvem</h3>
|
||||
<ul>
|
||||
<li>Adicione contas via OAuth do Google e Anthropic</li>
|
||||
<li>Mostra avatar, e-mail, status e último horário de uso</li>
|
||||
</ul>
|
||||
</td>
|
||||
<td width="50%">
|
||||
<h3>📊 Monitoramento de Cota</h3>
|
||||
<ul>
|
||||
<li>Suporte multi-modelo: gemini-pro, claude-3-5-sonnet, etc.</li>
|
||||
<li>Barras de progresso visuais com indicadores de cores</li>
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="50%">
|
||||
<h3>🔄 Troca Automática</h3>
|
||||
<ul>
|
||||
<li>Modo de pool ilimitado com seleção inteligente</li>
|
||||
<li>Monitoramento a cada 5 minutos em segundo plano</li>
|
||||
</ul>
|
||||
</td>
|
||||
<td width="50%">
|
||||
<h3>🔌 Proxy Local</h3>
|
||||
<ul>
|
||||
<li>Compatível com API da OpenAI & Anthropic</li>
|
||||
<li>Mapeamento de modelos customizável (ex: Claude → Gemini)</li>
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
---
|
||||
|
||||
## ⚡ Começo Rápido
|
||||
|
||||
### Construindo pelo Código Fonte
|
||||
|
||||
#### Pré-requisitos
|
||||
|
||||
- Node.js v18 ou superior
|
||||
- npm
|
||||
|
||||
#### Passos
|
||||
|
||||
```bash
|
||||
# Clone o repositório
|
||||
git clone https://github.com/Draculabo/GeminiNexus.git
|
||||
cd GeminiNexus
|
||||
|
||||
# Instale as dependências
|
||||
npm install
|
||||
|
||||
# Inicie o modo de desenvolvimento
|
||||
npm start
|
||||
|
||||
# Construa para produção
|
||||
npm run make
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📄 Licença
|
||||
|
||||
[CC BY-NC-SA 4.0](LICENSE)
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ Aviso Legal
|
||||
|
||||
> [!WARNING]
|
||||
> **Apenas para Fins Educacionais**
|
||||
>
|
||||
> Este projeto destina-se exclusivamente a fins de pesquisa e educação. Ele é fornecido "como está" sem qualquer garantia. **O uso comercial é estritamente proibido.**
|
||||
|
||||
---
|
||||
|
||||
## 🙏 Créditos
|
||||
|
||||
**Gemini Nexus** é uma versão significativamente aprimorada e expandida baseada no projeto original [AntigravityManager](https://github.com/Draculabo/AntigravityManager) criado por [Draculabo](https://github.com/Draculabo). Toda a arquitetura fundamental e design do projeto foram criados pelo autor original.
|
||||
@@ -1,411 +0,0 @@
|
||||
<p align="center">
|
||||
<img src="docs/assets/logo.png" alt="Antigravity Manager" width="128" height="128" />
|
||||
</p>
|
||||
|
||||
<h1 align="center">Antigravity Manager</h1>
|
||||
|
||||
<p align="center">
|
||||
<strong>🚀 专业的 Google Gemini & Claude AI 多账号管理器</strong>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="README.md">English</a> | 简体中文
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/Draculabo/AntigravityManager/actions/workflows/testing.yaml">
|
||||
<img src="https://github.com/Draculabo/AntigravityManager/actions/workflows/testing.yaml/badge.svg" alt="Tests" />
|
||||
</a>
|
||||
<a href="https://github.com/Draculabo/AntigravityManager/actions/workflows/lint.yaml">
|
||||
<img src="https://github.com/Draculabo/AntigravityManager/actions/workflows/lint.yaml/badge.svg" alt="Lint" />
|
||||
</a>
|
||||
<a href="https://github.com/Draculabo/AntigravityManager/releases">
|
||||
<img src="https://img.shields.io/github/v/release/Draculabo/AntigravityManager?style=flat-square" alt="Release" />
|
||||
</a>
|
||||
<a href="https://github.com/Draculabo/AntigravityManager/releases">
|
||||
<img src="https://img.shields.io/github/downloads/Draculabo/AntigravityManager/total?style=flat-square&color=blue" alt="Downloads" />
|
||||
</a>
|
||||
<a href="https://github.com/Draculabo/AntigravityManager/blob/main/LICENSE">
|
||||
<img src="https://img.shields.io/github/license/Draculabo/AntigravityManager?style=flat-square" alt="License" />
|
||||
</a>
|
||||
<a href="https://github.com/Draculabo/AntigravityManager/stargazers">
|
||||
<img src="https://img.shields.io/github/stars/Draculabo/AntigravityManager?style=flat-square" alt="Stars" />
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<img src="https://img.shields.io/badge/Electron-191970?style=for-the-badge&logo=Electron&logoColor=white" alt="Electron" />
|
||||
<img src="https://img.shields.io/badge/React-20232A?style=for-the-badge&logo=react&logoColor=61DAFB" alt="React" />
|
||||
<img src="https://img.shields.io/badge/TypeScript-007ACC?style=for-the-badge&logo=typescript&logoColor=white" alt="TypeScript" />
|
||||
<img src="https://img.shields.io/badge/Tailwind_CSS-38B2AC?style=for-the-badge&logo=tailwind-css&logoColor=white" alt="TailwindCSS" />
|
||||
</p>
|
||||
|
||||
---
|
||||
|
||||
## 📖 目录
|
||||
|
||||
- [为什么选择 Antigravity Manager?](#-为什么选择-antigravity-manager)
|
||||
- [功能特性](#-功能特性)
|
||||
- [截图预览](#-截图预览)
|
||||
- [快速开始](#-快速开始)
|
||||
- [技术栈](#️-技术栈)
|
||||
- [开发指南](#-开发指南)
|
||||
- [调试文档](#-调试文档)
|
||||
- [常见问题](#-常见问题)
|
||||
- [贡献指南](#-贡献指南)
|
||||
- [许可证](#-许可证)
|
||||
|
||||
---
|
||||
|
||||
## ✨ 为什么选择 Antigravity Manager?
|
||||
|
||||
在使用 Antigravity IDE 时,你是否遇到过这些问题?
|
||||
|
||||
- 😫 单个账号额度很快用完,需要频繁手动切换
|
||||
- 🔄 管理多个 Google/Claude 账号非常麻烦
|
||||
- 📊 不知道当前账号还剩多少额度
|
||||
- ⏰ 担心错过额度重置时间
|
||||
- 🔌 需要可靠的本地 API 代理用于开发工具
|
||||
|
||||
**Antigravity Manager** 就是为解决这些问题而生的!它是一个专业的 Electron 桌面应用,帮助你:
|
||||
|
||||
- ✅ **无限账号池** - 添加任意数量的 Google Gemini / Claude 账号
|
||||
- ✅ **智能自动切换** - 额度不足或被限速时自动切换到下一个可用账号
|
||||
- ✅ **实时监控** - 可视化显示所有账号的额度使用情况
|
||||
- ✅ **本地 API 代理** - 内置兼容 OpenAI/Anthropic 协议的代理服务器
|
||||
- ✅ **安全加密** - AES-256-GCM 加密存储敏感信息
|
||||
|
||||
---
|
||||
|
||||
## 🎯 功能特性
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td width="50%">
|
||||
<h3>☁️ 云账号池管理</h3>
|
||||
<ul>
|
||||
<li>通过 OAuth 添加无限 Google Gemini / Claude 账号</li>
|
||||
<li>显示头像、邮箱、状态和最后使用时间</li>
|
||||
<li>实时状态监控(活跃、限速、过期)</li>
|
||||
</ul>
|
||||
</td>
|
||||
<td width="50%">
|
||||
<h3>📊 实时额度监控</h3>
|
||||
<ul>
|
||||
<li>多模型支持:gemini-pro、claude-3-5-sonnet 等</li>
|
||||
<li>可视化进度条配色指示器</li>
|
||||
<li>自动刷新与手动刷新</li>
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="50%">
|
||||
<h3>🔄 智能自动切换</h3>
|
||||
<ul>
|
||||
<li>无限池模式,智能选择备用账号</li>
|
||||
<li>额度低于 5% 或被限速时自动切换</li>
|
||||
<li>后台每 5 分钟自动监控</li>
|
||||
</ul>
|
||||
</td>
|
||||
<td width="50%">
|
||||
<h3>🔐 安全优先</h3>
|
||||
<ul>
|
||||
<li>AES-256-GCM 加密敏感数据</li>
|
||||
<li>集成操作系统原生凭证管理器</li>
|
||||
<li>自动迁移旧版明文数据</li>
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="50%">
|
||||
<h3>💾 账号备份</h3>
|
||||
<ul>
|
||||
<li>捕获账号状态快照</li>
|
||||
<li>在已保存的账号间快速切换</li>
|
||||
<li>查看、整理和删除快照</li>
|
||||
</ul>
|
||||
</td>
|
||||
<td width="50%">
|
||||
<h3>⚙️ 进程控制</h3>
|
||||
<ul>
|
||||
<li>自动检测 Antigravity 是否运行</li>
|
||||
<li>通过 URI 协议或可执行文件启动</li>
|
||||
<li>优雅关闭或强制终止</li>
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="50%">
|
||||
<h3>🔌 本地 API 代理</h3>
|
||||
<ul>
|
||||
<li>兼容 OpenAI & Anthropic API 协议</li>
|
||||
<li>可配置端口和请求超时</li>
|
||||
<li>模型映射(如:Claude → Gemini)</li>
|
||||
</ul>
|
||||
</td>
|
||||
<td width="50%">
|
||||
<h3>🛠️ 开发者工具</h3>
|
||||
<ul>
|
||||
<li>内置 cURL & Python 代码生成</li>
|
||||
<li>可视化服务状态监控</li>
|
||||
<li>一键重新生成 API Key</li>
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
### 更多功能
|
||||
|
||||
- **🖥️ 系统托盘** - 后台运行,托盘图标和右键菜单
|
||||
- **🔗 IDE 同步** - 自动扫描导入 IDE 的 `state.vscdb` 账号
|
||||
- **📦 批量操作** - 批量刷新和删除多个账号
|
||||
- **📤 JSON 导出/导入** - 导出和导入账号池,支持架构验证和去重
|
||||
- **🔔 桌面通知** - 模型配额低于设定阈值时自动提醒
|
||||
- **🌐 单账号代理** - 为每个账号单独配置 HTTP/SOCKS5 代理
|
||||
- **📊 智能排序** - 按最近使用、总体配额或特定模型组排序
|
||||
- **📋 紧凑布局** - 水平密集视图,最大化可见账号数量
|
||||
- **🌏 国际化** - 多语言支持(English / 中文 / Русский)
|
||||
- **🎨 现代 UI** - 基于 React、TailwindCSS 和 Shadcn UI 构建
|
||||
|
||||
---
|
||||
|
||||
## 📸 截图预览
|
||||
|
||||
<p align="center">
|
||||
<img src="docs/assets/screenshot-main.png" alt="主界面" width="80%" />
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<img src="docs/assets/screenshot-proxy.png" alt="代理界面" width="48%" />
|
||||
<img src="docs/assets/screenshot-setting.png" alt="设置界面" width="48%" />
|
||||
</p>
|
||||
|
||||
---
|
||||
|
||||
## 🚀 快速开始
|
||||
|
||||
### 下载安装
|
||||
|
||||
从 [Releases](https://github.com/Draculabo/AntigravityManager/releases) 页面下载适合你平台的最新版本。
|
||||
|
||||
| 平台 | 下载链接 |
|
||||
| ------------------- | ------------------------------------------------------------------------------ |
|
||||
| Windows (x64/ARM64) | [.exe 安装包](https://github.com/Draculabo/AntigravityManager/releases/latest) |
|
||||
| macOS | [.dmg 安装包](https://github.com/Draculabo/AntigravityManager/releases/latest) |
|
||||
| Linux | [.deb / .rpm](https://github.com/Draculabo/AntigravityManager/releases/latest) |
|
||||
| NixOS / Nix | `nix run github:Draculabo/AntigravityManager` |
|
||||
|
||||
### ❄️ Nix 集成
|
||||
|
||||
你可以通过项目提供的 flake 将 Antigravity Manager 集成到你的 Nix 配置中。
|
||||
|
||||
#### 🛠️ NixOS / Home Manager
|
||||
|
||||
先在你的 `flake.nix` 的 `inputs` 中加入:
|
||||
|
||||
```nix
|
||||
inputs = {
|
||||
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
|
||||
antigravity-manager.url = "github:Draculabo/AntigravityManager";
|
||||
};
|
||||
```
|
||||
|
||||
然后把 `antigravity-manager.overlays.default` 添加到 `nixpkgs.overlays`。这样会基于你自己的 `nixpkgs` 实例构建,并继承你的全局配置。
|
||||
|
||||
```nix
|
||||
# 添加到你的 NixOS/Home Manager 配置中
|
||||
{
|
||||
nixpkgs.overlays = [ inputs.antigravity-manager.overlays.default ];
|
||||
|
||||
# Antigravity Manager 使用非自由许可证 (cc-by-nc-sa-40)
|
||||
nixpkgs.config.allowUnfree = true;
|
||||
|
||||
environment.systemPackages = [
|
||||
pkgs.antigravity-manager
|
||||
];
|
||||
}
|
||||
```
|
||||
|
||||
#### 💻 开发 Shell
|
||||
|
||||
如果你已经使用上面的 overlay,也可以直接在 `devShell` 里加入:
|
||||
|
||||
```nix
|
||||
devShells.default = pkgs.mkShell {
|
||||
packages = [
|
||||
pkgs.antigravity-manager
|
||||
];
|
||||
};
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
> 由于该包是非自由软件,你必须在 `nixpkgs` 配置中开启 `allowUnfree = true;`,否则会评估失败。
|
||||
|
||||
### 从源码构建
|
||||
|
||||
#### 前置要求
|
||||
|
||||
- Node.js v18 或更高版本
|
||||
- npm 或 yarn
|
||||
|
||||
#### 步骤
|
||||
|
||||
```bash
|
||||
# 克隆仓库
|
||||
git clone https://github.com/Draculabo/AntigravityManager.git
|
||||
cd AntigravityManager
|
||||
|
||||
# 安装依赖
|
||||
npm install
|
||||
|
||||
# 启动开发模式
|
||||
npm start
|
||||
|
||||
# 构建生产版本
|
||||
npm run make
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ 技术栈
|
||||
|
||||
| 类别 | 技术 |
|
||||
| ------------ | ------------------------------------------------------------------------------------------------------------------- |
|
||||
| **核心** | [Electron](https://www.electronjs.org/)、[React](https://react.dev/)、[TypeScript](https://www.typescriptlang.org/) |
|
||||
| **构建工具** | [Vite](https://vitejs.dev/) |
|
||||
| **样式** | [TailwindCSS](https://tailwindcss.com/)、[Shadcn UI](https://ui.shadcn.com/) |
|
||||
| **状态管理** | [TanStack Query](https://tanstack.com/query/latest)、[TanStack Router](https://tanstack.com/router/latest) |
|
||||
| **数据库** | [Better-SQLite3](https://github.com/WiseLibs/better-sqlite3) |
|
||||
| **测试** | [Vitest](https://vitest.dev/)、[Playwright](https://playwright.dev/) |
|
||||
|
||||
---
|
||||
|
||||
## 💻 开发指南
|
||||
|
||||
### 可用脚本
|
||||
|
||||
| 命令 | 描述 |
|
||||
| ---------------------- | ------------------------ |
|
||||
| `npm start` | 启动开发模式 |
|
||||
| `npm run lint` | 运行 ESLint 检查 |
|
||||
| `npm run format:write` | 使用 Prettier 格式化代码 |
|
||||
| `npm run test:unit` | 运行单元测试 |
|
||||
| `npm run test:e2e` | 运行 E2E 测试 |
|
||||
| `npm run test:all` | 运行所有测试 |
|
||||
| `npm run type-check` | TypeScript 类型检查 |
|
||||
| `npm run make` | 构建生产包 |
|
||||
|
||||
### 项目结构
|
||||
|
||||
```
|
||||
AntigravityManager/
|
||||
├── src/
|
||||
│ ├── main.ts # Electron 主进程
|
||||
│ ├── preload.ts # 预加载脚本
|
||||
│ ├── renderer/ # React 渲染进程
|
||||
│ ├── ipc/ # IPC 通信处理
|
||||
│ └── server/ # 内置服务器
|
||||
├── docs/ # 文档和资源
|
||||
└── .github/ # GitHub 配置
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔍 调试文档
|
||||
|
||||
如果你希望用 Proxyman 抓取和排查 Antigravity 或 Antigravity Manager 的流量,请先阅读:
|
||||
|
||||
- [Proxyman Installation and Basic Setup on Windows](docs/proxyman-install.md)
|
||||
- [Proxyman Debugging Guide for Antigravity and Antigravity Manager](docs/proxyman-debugging.md)
|
||||
|
||||
---
|
||||
|
||||
## ❓ 常见问题
|
||||
|
||||
<details>
|
||||
<summary><b>Q: 程序无法启动怎么办?</b></summary>
|
||||
|
||||
请检查:
|
||||
|
||||
1. 确保已安装所有依赖:`npm install`
|
||||
2. 检查 Node.js 版本是否 >= 18
|
||||
3. 尝试删除 `node_modules` 后重新安装
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>Q: 账号登录失败?</b></summary>
|
||||
|
||||
1. 确保网络连接正常
|
||||
2. 尝试清除应用数据后重新登录
|
||||
3. 检查账号是否被 Google/Claude 限制
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>Q: macOS 上提示 Keychain/凭据管理不可用,OAuth 无法保存?</b></summary>
|
||||
|
||||
这是 macOS 安全机制导致的常见问题,通常出现在未签名的应用或从 Downloads 直接运行的场景。
|
||||
这是**临时方案**,仅适合个人设备:
|
||||
|
||||
1. 将应用移动到 `/Applications`
|
||||
2. 打开终端执行以下命令(每次更新后都需要重新执行)
|
||||
|
||||
```plaintext
|
||||
sudo xattr -dr com.apple.quarantine "/Applications/Antigravity Manager 2.app"
|
||||
codesign --force --deep --sign - "/Applications/Antigravity Manager 2.app"
|
||||
```
|
||||
|
||||
重新打开应用后,Keychain 应会提示授权(可选“始终允许”)。
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>Q: 如何反馈问题或建议?</b></summary>
|
||||
|
||||
请通过 [GitHub Issues](https://github.com/Draculabo/AntigravityManager/issues) 提交问题或建议。
|
||||
|
||||
</details>
|
||||
|
||||
---
|
||||
|
||||
## 🌟 Star History
|
||||
|
||||
<a href="https://github.com/Draculabo/AntigravityManager/stargazers">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=Draculabo/AntigravityManager&type=Date&theme=dark" />
|
||||
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=Draculabo/AntigravityManager&type=Date" />
|
||||
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=Draculabo/AntigravityManager&type=Date" />
|
||||
</picture>
|
||||
</a>
|
||||
|
||||
---
|
||||
|
||||
## 🤝 贡献指南
|
||||
|
||||
欢迎贡献代码!请阅读 [CONTRIBUTING.md](CONTRIBUTING.md) 了解详情。
|
||||
|
||||
我们遵循 [Contributor Covenant](CODE_OF_CONDUCT.md) 行为准则。
|
||||
|
||||
---
|
||||
|
||||
## 📄 许可证
|
||||
|
||||
[CC BY-NC-SA 4.0](LICENSE)
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 免责声明
|
||||
|
||||
> [!WARNING]
|
||||
> **仅供学习研究使用**
|
||||
>
|
||||
> 本项目仅供教育和研究目的,按"原样"提供,不提供任何保证。**严禁商业使用。**
|
||||
>
|
||||
> 使用本软件即表示您同意不会将其用于任何商业目的,并自行负责确保您的使用符合所有适用法律法规。作者和贡献者对因使用本软件而产生的任何滥用或损害不承担责任。
|
||||
|
||||
---
|
||||
|
||||
<p align="center">
|
||||
如果这个项目对你有帮助,请给一个 ⭐ Star 支持!
|
||||
</p>
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
## 🔒 Security Features
|
||||
|
||||
Antigravity Manager takes security seriously. Here are the security measures we've implemented:
|
||||
Gemini Nexus takes security seriously. Here are the security measures we've implemented:
|
||||
|
||||
### Encryption
|
||||
|
||||
@@ -35,7 +35,7 @@ We only provide security updates for the latest version. Please ensure you're ru
|
||||
|
||||
## 🚨 Reporting a Vulnerability
|
||||
|
||||
We take all security vulnerabilities seriously. Thank you for helping to improve the security of Antigravity Manager.
|
||||
We take all security vulnerabilities seriously. Thank you for helping to improve the security of Gemini Nexus.
|
||||
|
||||
### How to Report
|
||||
|
||||
@@ -75,7 +75,7 @@ Please include the following information in your report:
|
||||
|
||||
To ensure the security of your data:
|
||||
|
||||
1. **Keep Updated**: Always use the latest version of Antigravity Manager
|
||||
1. **Keep Updated**: Always use the latest version of Gemini Nexus
|
||||
2. **System Security**: Keep your operating system updated
|
||||
3. **Secure Environment**: Use the application on trusted devices only
|
||||
4. **Backup**: Regularly backup your account data using the built-in backup feature
|
||||
@@ -90,4 +90,4 @@ When we receive a security bug report, we will:
|
||||
3. Prepare fixes for all supported versions
|
||||
4. Release new versions as soon as possible
|
||||
|
||||
Thank you for helping keep Antigravity Manager and its users safe!
|
||||
Thank you for helping keep Gemini Nexus and its users safe!
|
||||
@@ -1,4 +1,4 @@
|
||||
# Antigravity Manager CLI
|
||||
# Gemini Nexus CLI
|
||||
|
||||
A powerful command-line interface for managing multiple Google Cloud AI accounts in Antigravity IDE. Supports account switching, quota monitoring, token validation, and more.
|
||||
|
||||
@@ -147,7 +147,7 @@ agm doctor
|
||||
|
||||
## How It Works
|
||||
|
||||
The CLI interacts directly with the Antigravity Manager's encrypted database:
|
||||
The CLI interacts directly with the Gemini Nexus's encrypted database:
|
||||
|
||||
1. **Database Location**: Auto-discovers the database in `%APPDATA%\AntigravityManager` or IDE installation directory
|
||||
2. **Encryption**: Uses Windows DPAPI + AES-256-GCM to decrypt account tokens (same as the IDE)
|
||||
@@ -157,7 +157,7 @@ The CLI interacts directly with the Antigravity Manager's encrypted database:
|
||||
## Troubleshooting
|
||||
|
||||
**"Database not found" error:**
|
||||
- Ensure Antigravity Manager has been run at least once
|
||||
- Ensure Gemini Nexus has been run at least once
|
||||
- Check if accounts exist in the GUI first
|
||||
|
||||
**"Failed to decrypt" error:**
|
||||
@@ -175,7 +175,7 @@ The CLI interacts directly with the Antigravity Manager's encrypted database:
|
||||
|
||||
- Windows 10/11
|
||||
- Python 3.8+
|
||||
- Antigravity Manager (for database access)
|
||||
- Gemini Nexus (for database access)
|
||||
|
||||
## Development
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 390 KiB |
@@ -1,6 +1,6 @@
|
||||
# Cloud Account Management and Intelligent Switching
|
||||
|
||||
This document explains the cloud AI account management and switching capabilities in Antigravity Manager. The feature allows users to manage multiple Google accounts, monitor API quotas, and seamlessly switch the active IDE account with one click.
|
||||
This document explains the cloud AI account management and switching capabilities in Gemini Nexus. The feature allows users to manage multiple Google accounts, monitor API quotas, and seamlessly switch the active IDE account with one click.
|
||||
|
||||
## 1. Core Features
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
# Proxyman Debugging Guide for Antigravity and Antigravity Manager
|
||||
# Proxyman Debugging Guide for Antigravity and Gemini Nexus
|
||||
|
||||
This guide explains how to use Proxyman for two debugging targets:
|
||||
|
||||
1. The installed official `Antigravity` desktop client
|
||||
2. The `Antigravity Manager` development build from this repository
|
||||
2. The `Gemini Nexus` development build from this repository
|
||||
|
||||
Before you start, complete [Proxyman Installation and Basic Setup on Windows](./proxyman-install.md).
|
||||
|
||||
@@ -12,7 +12,7 @@ Before you start, complete [Proxyman Installation and Basic Setup on Windows](./
|
||||
Use this guide when you want to:
|
||||
|
||||
- inspect login, quota, project context, or model-list requests from `Antigravity`
|
||||
- inspect OAuth, quota refresh, or proxy traffic from `Antigravity Manager`
|
||||
- inspect OAuth, quota refresh, or proxy traffic from `Gemini Nexus`
|
||||
- compare requests from the official client with requests produced by this project
|
||||
|
||||
## Understand the Two Traffic Paths
|
||||
@@ -24,7 +24,7 @@ Use this guide when you want to:
|
||||
- Chromium or renderer-side requests
|
||||
- Electron or Node-side requests
|
||||
|
||||
### `Antigravity Manager` in This Repository
|
||||
### `Gemini Nexus` in This Repository
|
||||
|
||||
This project is also an Electron app, but it additionally includes Node, Undici, and internal proxy flows. You may need to inspect:
|
||||
|
||||
@@ -68,7 +68,7 @@ powershell -ExecutionPolicy Bypass -File .\scripts\start-antigravity-with-proxym
|
||||
Notes:
|
||||
|
||||
- This script targets the installed official `Antigravity` client.
|
||||
- It does not launch this repository's `Antigravity Manager` development build.
|
||||
- It does not launch this repository's `Gemini Nexus` development build.
|
||||
- Override `-AppPath`, `-CaPath`, `-ProxyHost`, or `-NoProxy` if your local environment differs.
|
||||
|
||||
### Suggested Flow
|
||||
@@ -111,7 +111,7 @@ If system proxy behavior is unreliable, you can launch the installed client with
|
||||
|
||||
Use this as a fallback, not the default path.
|
||||
|
||||
## Debug Antigravity Manager From This Repository
|
||||
## Debug Gemini Nexus From This Repository
|
||||
|
||||
This project has more layers than the official client. The script above is not the general launcher for this repository's development app.
|
||||
|
||||
@@ -227,7 +227,7 @@ powershell -ExecutionPolicy Bypass -File .\scripts\start-antigravity-with-proxym
|
||||
|
||||
4. Trigger login, quota, or model-list traffic.
|
||||
|
||||
### Antigravity Manager in This Repository
|
||||
### Gemini Nexus in This Repository
|
||||
|
||||
1. Complete [Proxyman Installation and Basic Setup on Windows](./proxyman-install.md).
|
||||
2. Open PowerShell.
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const projectRoot = process.cwd();
|
||||
|
||||
// Extensões permitidas
|
||||
const allowedExtensions = /\.(ts|tsx)$/;
|
||||
|
||||
function walkAndFix(dir) {
|
||||
const files = fs.readdirSync(dir);
|
||||
|
||||
for (const file of files) {
|
||||
if (['node_modules', '.git', 'dist', 'out'].includes(file)) continue;
|
||||
|
||||
const fullPath = path.join(dir, file);
|
||||
const stat = fs.statSync(fullPath);
|
||||
|
||||
if (stat.isDirectory()) {
|
||||
walkAndFix(fullPath);
|
||||
} else if (allowedExtensions.test(file)) {
|
||||
let content = fs.readFileSync(fullPath, 'utf-8');
|
||||
const original = content;
|
||||
|
||||
// Fix specific known broken identifiers
|
||||
content = content.replace(/closeGemini Nexus/g, 'closeGeminiNexus');
|
||||
content = content.replace(/startGemini Nexus/g, 'startGeminiNexus');
|
||||
content = content.replace(/Gemini NexusVersion/g, 'GeminiNexusVersion');
|
||||
content = content.replace(/Gemini NexusDb/g, 'GeminiNexusDb');
|
||||
content = content.replace(/Gemini NexusStorage/g, 'GeminiNexusStorage');
|
||||
content = content.replace(/Gemini NexusExecutable/g, 'GeminiNexusExecutable');
|
||||
content = content.replace(/Gemini NexusCoreFeatures/g, 'GeminiNexusCoreFeatures');
|
||||
content = content.replace(/getGemini Nexus/g, 'getGeminiNexus');
|
||||
|
||||
if (content !== original) {
|
||||
fs.writeFileSync(fullPath, content, 'utf-8');
|
||||
console.log(`Fixed TS errors in: ${fullPath}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
walkAndFix(projectRoot);
|
||||
console.log('Fix complete.');
|
||||
@@ -25,7 +25,7 @@ import { stringify as yamlStringify } from 'yaml';
|
||||
const nativeModules = ['better-sqlite3', 'keytar', 'bindings', 'file-uri-to-path'];
|
||||
const ResolvedMakerAppImage = MakerAppImage;
|
||||
const keepLanguages = new Set(['en', 'en-US', 'zh-CN', 'ru']);
|
||||
const windowsExecutableName = 'antigravity-manager';
|
||||
const windowsExecutableName = 'gemini-nexus';
|
||||
|
||||
const isStartCommand = process.argv.some((arg) => arg.includes('start'));
|
||||
|
||||
@@ -204,7 +204,7 @@ const config: ForgeConfig = {
|
||||
asar: {
|
||||
unpack: '**/{better-sqlite3,keytar}/**/*',
|
||||
},
|
||||
name: 'Antigravity Manager',
|
||||
name: 'Gemini Nexus',
|
||||
executableName: windowsExecutableName,
|
||||
icon: 'images/icon', // Electron Forge automatically adds .icns/.ico
|
||||
extraResource: ['src/assets'], // Copy assets folder to resources/assets
|
||||
|
||||
|
Before Width: | Height: | Size: 353 KiB After Width: | Height: | Size: 353 KiB |
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 5.0 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 6.5 KiB After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 7.8 KiB After Width: | Height: | Size: 42 KiB |
|
Before Width: | Height: | Size: 864 B After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 4.8 KiB |
|
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 7.8 KiB |
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 390 KiB |
@@ -2,7 +2,7 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Antigravity Manager</title>
|
||||
<title>Gemini Nexus</title>
|
||||
<meta http-equiv="Content-Security-Policy" content="script-src 'self';" />
|
||||
<link rel="stylesheet" href="/src/styles/global.css" />
|
||||
</head>
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
- Affected code:
|
||||
- `src/ipc/database/cloudHandler.ts`
|
||||
- `src/ipc/database/handler.ts`
|
||||
- `src/utils/antigravityVersion.ts`
|
||||
- `src/utils/geminiNexusVersion.ts`
|
||||
- `src/utils/paths.ts` (this stage covers default runtime configuration path only)
|
||||
- `src/types/db.ts`
|
||||
|
||||
|
||||
@@ -38,5 +38,5 @@ The system SHALL use a strategy pattern to split new/legacy/dual-write injection
|
||||
The system SHALL prefer new-format injection when new-format keys are detected.
|
||||
|
||||
#### Scenario: New-format key exists
|
||||
- **WHEN** `antigravityUnifiedStateSync.oauthToken` exists
|
||||
- **WHEN** `geminiNexusUnifiedStateSync.oauthToken` exists
|
||||
- **THEN** injection uses new format with higher priority
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
## Summary
|
||||
|
||||
Add multilingual support to Antigravity Manager and automatically set the default UI language based on the user's OS language. If the OS language is unsupported, fall back to English. This includes updating i18n configuration, adding Chinese translation resources, and integrating a language detection plugin.
|
||||
Add multilingual support to Gemini Nexus and automatically set the default UI language based on the user's OS language. If the OS language is unsupported, fall back to English. This includes updating i18n configuration, adding Chinese translation resources, and integrating a language detection plugin.
|
||||
|
||||
## Motivation
|
||||
|
||||
|
||||
@@ -2,32 +2,32 @@
|
||||
|
||||
## Summary
|
||||
|
||||
Enable Antigravity Manager running in WSL (Windows Subsystem for Linux) to manage the Antigravity application installed on the Windows host.
|
||||
Enable Gemini Nexus running in WSL (Windows Subsystem for Linux) to manage the Gemini Nexus application installed on the Windows host.
|
||||
|
||||
## Motivation
|
||||
|
||||
Users running the manager in a WSL development environment need to interact with the actual Antigravity instance running on Windows, as Antigravity is a Windows application.
|
||||
Users running the manager in a WSL development environment need to interact with the actual Gemini Nexus instance running on Windows, as Gemini Nexus is a Windows application.
|
||||
|
||||
## Proposed Changes
|
||||
|
||||
### 1. Path Resolution (`src/utils/paths.ts`)
|
||||
|
||||
* Implement `isWsl()` utility function.
|
||||
* In `getAppDataDir()`: If WSL, return the Windows AppData path (e.g., `/mnt/c/Users/<User>/AppData/Roaming/Antigravity`).
|
||||
* In `getAntigravityExecutablePath()`: If WSL, return the Windows executable path (e.g., `/mnt/c/Users/<User>/AppData/Local/Programs/Antigravity/Antigravity.exe`).
|
||||
* In `getAppDataDir()`: If WSL, return the Windows AppData path (e.g., `/mnt/c/Users/<User>/AppData/Roaming/GeminiNexus`).
|
||||
* In `getGeminiNexusExecutablePath()`: If WSL, return the Windows executable path (e.g., `/mnt/c/Users/<User>/AppData/Local/Programs/GeminiNexus/GeminiNexus.exe`).
|
||||
* **Challenge:** Determining the correct Windows username.
|
||||
* **Strategy:** Assume Windows username matches WSL username, or scan `/mnt/c/Users` for a likely candidate (excluding Public, Default, etc.).
|
||||
|
||||
### 2. Process Management (`src/ipc/process/handler.ts`)
|
||||
|
||||
* In `isProcessRunning()`: If WSL, execute `tasklist.exe` (via `/mnt/c/Windows/System32/tasklist.exe`) to check for `Antigravity.exe`.
|
||||
* In `startAntigravity()`: If WSL, execute the Windows executable directly.
|
||||
* In `closeAntigravity()`: If WSL, use `taskkill.exe` (via `/mnt/c/Windows/System32/taskkill.exe`).
|
||||
* In `isProcessRunning()`: If WSL, execute `tasklist.exe` (via `/mnt/c/Windows/System32/tasklist.exe`) to check for `GeminiNexus.exe`.
|
||||
* In `startGeminiNexus()`: If WSL, execute the Windows executable directly.
|
||||
* In `closeGeminiNexus()`: If WSL, use `taskkill.exe` (via `/mnt/c/Windows/System32/taskkill.exe`).
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
* \[ ] Application detects it is running in WSL.
|
||||
* \[ ] Database file is correctly located on the Windows file system.
|
||||
* \[ ] "Start" button successfully launches Antigravity on Windows.
|
||||
* \[ ] "Stop" button successfully terminates Antigravity on Windows.
|
||||
* \[ ] "Start" button successfully launches Gemini Nexus on Windows.
|
||||
* \[ ] "Stop" button successfully terminates Gemini Nexus on Windows.
|
||||
* \[ ] Process status (Running/Stopped) is correctly reflected in the UI.
|
||||
@@ -1,6 +1,6 @@
|
||||
## ADDED Requirements
|
||||
### Requirement: New-Format Injection for 1.16.5+
|
||||
The system SHALL write only the new format `antigravityUnifiedStateSync.oauthToken` when IDE version is >= 1.16.5.
|
||||
The system SHALL write only the new format `geminiNexusUnifiedStateSync.oauthToken` when IDE version is >= 1.16.5.
|
||||
|
||||
#### Scenario: New-version injection
|
||||
- **WHEN** IDE version is 1.16.5 or above
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
|
||||
## Purpose
|
||||
|
||||
Antigravity Manager is a comprehensive enhancement tool for Antigravity, designed to remove AI development resource limits. It not only solves the lack of native multi-account switching in Antigravity IDE, but also introduces enterprise-grade AI account pool management. By taking control of app configuration state and cloud API resources, it allows users to switch seamlessly between unlimited local accounts and cloud AI services for an effectively "unlimited" AI usage experience.
|
||||
Gemini Nexus is a comprehensive enhancement tool for Gemini Nexus, designed to remove AI development resource limits. It not only solves the lack of native multi-account switching in Gemini Nexus IDE, but also introduces enterprise-grade AI account pool management. By taking control of app configuration state and cloud API resources, it allows users to switch seamlessly between unlimited local accounts and cloud AI services for an effectively "unlimited" AI usage experience.
|
||||
|
||||
**Core goals:**
|
||||
|
||||
* **Local multi-account switching**: Seamlessly switch Antigravity IDE local user state while protecting data integrity (automatic backup).
|
||||
* **Local multi-account switching**: Seamlessly switch Gemini Nexus IDE local user state while protecting data integrity (automatic backup).
|
||||
* **AI resource pool management (new)**: Centrally manage account pools for Google Gemini, Claude 3.5, and similar services.
|
||||
* **Intelligent quota monitoring (new)**: Monitor API quota and health in real time with a visual dashboard.
|
||||
* **Automatic resource scheduling (new)**: Automatically switch to the best available account when the current one runs out of quota (token keepalive + polling).
|
||||
@@ -88,17 +88,17 @@ src/
|
||||
|
||||
## Domain Context
|
||||
|
||||
### 1. Antigravity IDE Data Structure (Local)
|
||||
### 1. Gemini Nexus IDE Data Structure (Local)
|
||||
|
||||
**Database locations:**
|
||||
|
||||
* macOS: `~/Library/Application Support/Antigravity/User/globalStorage/state.vscdb`
|
||||
* Windows: `%APPDATA%/Antigravity/User/globalStorage/state.vscdb`
|
||||
* Linux: `~/.config/Antigravity/User/globalStorage/state.vscdb`
|
||||
* macOS: `~/Library/Application Support/GeminiNexus/User/globalStorage/state.vscdb`
|
||||
* Windows: `%APPDATA%/GeminiNexus/User/globalStorage/state.vscdb`
|
||||
* Linux: `~/.config/GeminiNexus/User/globalStorage/state.vscdb`
|
||||
|
||||
**Key database fields:**
|
||||
|
||||
* `antigravityAuthStatus`: Authentication status and user information.
|
||||
* `geminiNexusAuthStatus`: Authentication status and user information.
|
||||
|
||||
### 2. AI Service Resource Structure (Cloud / New)
|
||||
|
||||
@@ -141,7 +141,7 @@ src/
|
||||
|
||||
### System Dependencies
|
||||
|
||||
* **Antigravity IDE**: Local target to manage.
|
||||
* **Gemini Nexus IDE**: Local target to manage.
|
||||
* **SQLite**: Local data reads.
|
||||
|
||||
### Node.js Dependencies (Key)
|
||||
@@ -159,7 +159,7 @@ src/
|
||||
|
||||
### Filesystem Dependencies
|
||||
|
||||
* `~/.antigravity-agent/`
|
||||
* `~/.geminiNexus-agent/`
|
||||
* `accounts.json`: Local IDE account index.
|
||||
* `cloud_accounts.db`: (new) Cloud account pool database (SQLite).
|
||||
* `backups/`: Backup files.
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"name": "antigravity-manager",
|
||||
"name": "gemini-nexus",
|
||||
"version": "0.11.1",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "antigravity-manager",
|
||||
"name": "gemini-nexus",
|
||||
"version": "0.11.1",
|
||||
"license": "CC-BY-NC-SA-4.0",
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"name": "antigravity-manager",
|
||||
"productName": "Antigravity Manager",
|
||||
"name": "gemini-nexus",
|
||||
"productName": "Gemini Nexus",
|
||||
"version": "0.11.1",
|
||||
"description": "Antigravity Manager - Electron App",
|
||||
"description": "Gemini Nexus - Electron App",
|
||||
"main": ".vite/build/main.js",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const projectRoot = process.cwd();
|
||||
|
||||
// Extensões permitidas
|
||||
const allowedExtensions = /\.(ts|tsx|js|jsx|json|md|html|jsonc|yml|yaml|css)$/;
|
||||
|
||||
// Arquivos/Pastas a ignorar (já curados ou irrelevantes)
|
||||
const ignoreDirs = ['node_modules', '.git', '.gemini', 'dist', 'out', 'build', 'images', 'docs', '.planning'];
|
||||
const ignoreFiles = ['README.md', 'README.pt-BR.md', 'CHANGELOG.md', 'forge.config.ts', 'package.json', 'rename.js', 'rename-deep.js', 'task.md', 'walkthrough.md'];
|
||||
|
||||
function walkAndReplace(dir) {
|
||||
const files = fs.readdirSync(dir);
|
||||
|
||||
for (const file of files) {
|
||||
if (ignoreDirs.includes(file)) continue;
|
||||
|
||||
const fullPath = path.join(dir, file);
|
||||
const stat = fs.statSync(fullPath);
|
||||
|
||||
if (stat.isDirectory()) {
|
||||
walkAndReplace(fullPath);
|
||||
|
||||
// Rename directory if it contains 'antigravity'
|
||||
if (file.toLowerCase().includes('antigravity')) {
|
||||
const newName = file.replace(/antigravity/gi, (match) => {
|
||||
if (match === 'antigravity') return 'geminiNexus';
|
||||
if (match === 'Antigravity') return 'GeminiNexus';
|
||||
return 'geminiNexus';
|
||||
});
|
||||
const newPath = path.join(dir, newName);
|
||||
fs.renameSync(fullPath, newPath);
|
||||
console.log(`Renamed directory: ${fullPath} -> ${newPath}`);
|
||||
}
|
||||
} else {
|
||||
if (ignoreFiles.includes(file)) continue;
|
||||
|
||||
if (allowedExtensions.test(file)) {
|
||||
let content = fs.readFileSync(fullPath, 'utf-8');
|
||||
const originalContent = content;
|
||||
|
||||
// Save original GitHub links
|
||||
const repoUrlRegex = /github\.com\/Draculabo\/AntigravityManager/gi;
|
||||
const tempUrlMarker = '___TEMP_GITHUB_URL___';
|
||||
content = content.replace(repoUrlRegex, tempUrlMarker);
|
||||
|
||||
// 1. Strings exact match "Antigravity" -> "Gemini Nexus"
|
||||
content = content.replace(/['"]Antigravity['"]/g, (match) => {
|
||||
return match[0] + 'Gemini Nexus' + match[0];
|
||||
});
|
||||
|
||||
// 2. String "Antigravity " -> "Gemini Nexus "
|
||||
content = content.replace(/Antigravity /g, 'Gemini Nexus ');
|
||||
|
||||
// 3. String " Antigravity" -> " Gemini Nexus"
|
||||
content = content.replace(/ Antigravity/g, ' Gemini Nexus');
|
||||
|
||||
// 4. Identifiers Antigravity -> GeminiNexus
|
||||
content = content.replace(/Antigravity/g, 'GeminiNexus');
|
||||
|
||||
// 5. Identifiers antigravity -> geminiNexus
|
||||
content = content.replace(/antigravity/g, 'geminiNexus');
|
||||
|
||||
// Restore GitHub links
|
||||
content = content.replace(new RegExp(tempUrlMarker, 'g'), 'github.com/Draculabo/AntigravityManager');
|
||||
|
||||
// Cleanup any double replacements
|
||||
content = content.replace(/GeminiNexus Nexus/g, 'Gemini Nexus');
|
||||
|
||||
if (content !== originalContent) {
|
||||
fs.writeFileSync(fullPath, content, 'utf-8');
|
||||
console.log(`Updated file content: ${fullPath}`);
|
||||
}
|
||||
|
||||
// Rename file if it contains 'antigravity'
|
||||
if (file.toLowerCase().includes('antigravity')) {
|
||||
const newName = file.replace(/antigravity/gi, (match) => {
|
||||
if (match === 'antigravity') return 'geminiNexus'; // For files like antigravityVersion.ts -> geminiNexusVersion.ts
|
||||
if (match === 'Antigravity') return 'GeminiNexus';
|
||||
return 'geminiNexus';
|
||||
});
|
||||
const newPath = path.join(dir, newName);
|
||||
fs.renameSync(fullPath, newPath);
|
||||
console.log(`Renamed file: ${fullPath} -> ${newPath}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log('Starting global deep rename of Antigravity to GeminiNexus...');
|
||||
walkAndReplace(projectRoot);
|
||||
console.log('Rename complete.');
|
||||
@@ -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();
|
||||
}
|
||||
|
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 390 KiB |
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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')}
|
||||
|
||||
@@ -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'] });
|
||||
},
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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`;
|
||||
|
||||
@@ -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 });
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}),
|
||||
}),
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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',
|
||||
@@ -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',
|
||||
},
|
||||
},
|
||||
);
|
||||
@@ -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: 'Аккаунт удален',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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 */}
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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');
|
||||
});
|
||||
});
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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(),
|
||||
}));
|
||||
|
||||
@@ -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');
|
||||
});
|
||||
});
|
||||
@@ -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');
|
||||
@@ -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"}',
|
||||
|
||||
@@ -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
|
||||
});
|
||||
|
||||
@@ -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,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,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,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,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;
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||