Compare commits

..
8 Commits
Author SHA1 Message Date
Draculabo dcfed908a5 chore: reelease 0.1.1
Publish Release / publish (ubuntu-latest) (push) Failing after 5m50s
Publish Release / publish (macos-latest) (push) Canceled after 0s
Publish Release / publish (windows-latest) (push) Canceled after 0s
2026-01-11 20:48:27 +08:00
Draculabo 1336de679f fix: resolve Antigravity visibility issue on account switch 2026-01-11 20:39:29 +08:00
Draculabo bc43cc101c docs: add AGENTS.md for AI assistant guidance 2026-01-10 04:45:59 +08:00
Draculabo 01f6a5aaee docs: update v0.1.0 changelog 2026-01-10 04:41:39 +08:00
Draculabo f8e06efcca docs: update readme 2026-01-10 04:35:37 +08:00
Draculabo 13f02c7ac2 feat: update logo image 2026-01-10 04:26:07 +08:00
Draculabo b425dc3758 feat: update license 2026-01-10 04:24:03 +08:00
Draculabo fb13826afd feat: identity injection 2026-01-10 04:08:55 +08:00
16 changed files with 918 additions and 76 deletions

No files matched your search

+481 -1
View File
@@ -1,18 +1,498 @@
<!-- OPENSPEC:START -->
# OpenSpec Instructions
These instructions are for AI assistants working in this project.
Always open `@/openspec/AGENTS.md` when the request:
- Mentions planning or proposals (words like proposal, spec, change, plan)
- Introduces new capabilities, breaking changes, architecture shifts, or big performance/security work
- Sounds ambiguous and you need the authoritative spec before coding
Use `@/openspec/AGENTS.md` to learn:
- How to create and apply change proposals
- Spec format and conventions
- Project structure and guidelines
Keep this managed block so 'openspec update' can refresh the instructions.
<!-- OPENSPEC:END -->
<!-- OPENSPEC:END -->
# 用户指南
本文是 AI 助手在本仓库工作的用户级指南。中文为主、工具与脚本偏好统一、并纳入你补充的技术栈与开发注意事项。
## 💬 沟通约定
- **语言**: 对话、待办、代码相关内容(注释、UI 文案、提交信息、PR 描述等)统一使用中文。
- **结论先行**: 回答先给出核心结论/摘要,再补充细节。
- **引用**: 引用具体代码时,务必给出完整文件路径(如 `src/main.ts:42`)。
## 💻 运行环境与工具
- **Runtime**: Node.js (Electron 环境)
- **Node**: 建议 Node.js 20+
- **包管理器**: `npm` (本项目包含 package-lock.json,强制使用 npm)
- **构建工具**: Electron Forge + Vite
- **终端环境**: Windows (PowerShell) / 可安全使用 VSCode MCP 工具
## 🧩 技术栈总览 (Tech Stack)
- **Frontend**:
- React 19, TypeScript
- Tailwind CSS v4, `clsx`, `tailwind-merge`, `tailwindcss-animate`
- Radix UI (Primitives), Lucide React (Icons), Sonner (Toast)
- TanStack Router (Routing), TanStack Query (State Management)
- Components: 模块化设计,`src/components`
- **Backend (Electron Main/Server)**:
- Electron (Main/Preload/Renderer 架构)
- NestJS (用于内部代理/网关服务,由 Main Process 启动)
- Better-SQLite3 (本地数据库), Drizzle ORM (如果后续引入) / Raw SQL
- ORPC (Type-safe RPC)
- Zod (Validation)
- **Testing**:
- Vitest (Unit/Integration Tests)
- Playwright (E2E Tests)
## 📁 目录结构
```plaintext
.
├─ src/
│ ├─ components/ # React UI 组件 (ui/ 存放基础组件)
│ ├─ hooks/ # Custom React Hooks
│ ├─ ipc/ # Electron IPC 处理逻辑 (Database, Config, etc.)
│ ├─ layouts/ # 页面布局组件
│ ├─ lib/ # 通用工具库
│ ├─ routes/ # TanStack Router 路由定义
│ ├─ server/ # NestJS 后端服务逻辑 (Gateway/Proxy)
│ ├─ services/ # 业务服务层
│ ├─ styles/ # 全局样式 (Tailwind class)
│ ├─ types/ # TypeScript 类型定义
│ ├─ utils/ # 通用工具函数
│ ├─ App.tsx # React 应用入口
│ ├─ main.ts # Electron 主进程入口
│ ├─ preload.ts # Electron 预加载脚本
│ └─ renderer.ts # Electron 渲染进程入口
├─ forge.config.ts # Electron Forge 配置
└─ package.json
```
## 🧱 组件架构(Component Architecture)
- **模块化组件**: 每个组件使用独立目录,至少包含 `.tsx` 以及可能的样式或子组件。
- **共享能力**: 通用函数置于 `src/utils/`,底层通用封装置于 `src/lib/`。
- **服务层**: 统一在 `src/services/` 或 `src/ipc/` 中封装数据访问,前端仅依赖 IPC 或 RPC 调用。
## 📦 常用脚本 (Scripts)
所有命令使用 `npm` 执行:
- **开发 (Dev)**:
- `npm start` - 启动 Electron 开发环境 (Electron Forge)
- `npm run lint` - 运行 ESLint 检查
- `npm run format` - 运行 Prettier 格式化检查
- **构建 (Build)**:
- `npm run package` - 打包应用 (生成的只有应用包)
- `npm run make` - 构建并生成分发安装包 (Make)
- `npm run publish` - 发布应用
- **测试 (Test)**:
- `npm test` - 运行 Vitest 单元测试
- `npm run test:unit` - 同上
- `npm run test:e2e` - 运行 Playwright 端到端测试
- `npm run test:all` - 运行所有测试
- **数据库 (DB)**:
- `npm run test-db` - 运行数据库测试脚本
### 运行单个测试 (Running Single Tests)
- 单元测试: `npm run test:unit path/to/test.test.ts`
- E2E 测试: `npm run test:e2e path/to/test.spec.ts`
- 类型检查: `npm run type-check`
## 🧪 开发注意事项(Development Notes)
- **Build**: 构建阶段忽略 TS/ESLint 错误(如有特定配置或 CI 要求,请以实际为准)。
- **DevTools**: 项目集成了 `code-inspector-plugin`,开发时支持从页面元素直接跳转代码(Shift+Click)。
- **React**: React Strict Mode 关闭。
- **NestJS**: 作为 Electron 子进程运行,日志输出在主进程控制台。
## �️ 安全与数据 (Security & Data)
- **安全**: 绝不提交密钥;敏感配置使用环境变量;验证所有用户输入;敏感数据加密存储。
- **数据库**: 使用 Better-SQLite3;所有操作封装在 Services 层;必须使用 Prepared Statements;独立测试 DB 操作。
- **国际化**: 使用 `react-i18next`;Key 使用 kebab-case;翻译文件存放在 `src/localization/`。
## �📝 代码规范 (Conventions)
- **文件命名**:
- 组件: PascalCase (e.g., `Button.tsx`)
- 工具/配置: camelCase 或 kebab-case
- **导入路径**: 使用 `@/` 别名指向 `src/` 目录。
- **类型安全**: 严禁使用 `any`,利用 Zod 和 TypeScript 确保全链路类型安全。
- **组件设计**:
- 优先使用 Radix UI Primitives。
- 样式使用 Tailwind Utility Classes,避免 CSS Modules (除非必要)。
- **API 通信**: 前端调用后端优先使用 ORPC 客户端或 IPC,确保类型推导。
### 命名约定 (Naming Specifics)
- **Functions/Variables**: camelCase (e.g., `handleClick`, `isCurrent`)
- **Constants**: UPPER_SNAKE_CASE (e.g., `LOCAL_STORAGE_KEYS`)
- **Files**:
- Services: `ServiceName.service.ts`
- Types: `type-name.ts`
### 导入组织 (Import Organization)
```typescript
// 1. React 和核心库
import React, { useEffect } from 'react';
import { createRoot } from 'react-dom/client';
// 2. 外部依赖 (按字母顺序)
import { useTranslation } from 'react-i18next';
import { formatDistanceToNow } from 'date-fns';
// 3. 内部导入 (使用 @ 别名)
import { Account } from '@/types/account';
import { Card, CardContent } from '@/components/ui/card';
```
### 组件结构 (Component Structure)
```typescript
// 1. 导入
import React, { useState } from 'react';
// 2. 类型定义
interface ComponentProps { /* props */ }
// 3. 组件实现
export const Component: React.FC<ComponentProps> = ({ prop1 }) => {
// 4. Hooks
const { t } = useTranslation();
// 5. Render
return <div>{/* JSX */}</div>;
};
```
> 提交前请确保执行 `npm run lint` 和 `npm run format`。
## 📝 终端输出与引用规范
- 代码块优先,避免 Markdown 表格与 mermaid(Claude 代码块渲染更稳定)。
- 表格(如需)左对齐;中文字符等宽注意显示差异。
示例:
```plaintext
+------+---------+---------+
| ID | Name | Role |
+------+---------+---------+
| 1 | Alice | Admin |
| 2 | Bob | User |
+------+---------+---------+
```
### 引用规范(References)
- 外部资源:使用完整可点击链接(Issue、文档、API 参考)。
- 源码位置:使用完整文件路径(可附行号)。
示例:
```plaintext
- “resolveFilePath 负责该逻辑”
- “VSCode 在撤销操作上存在既知限制”
🔗 References:
- resolveFilePath: src/utils/workspace.ts:40
- VSCode undo limitation: https://github.com/microsoft/vscode/issues/77190
```
## 🏷️ Markdown 书写
- 代码块语言不要留空,无法确定时使用 `plaintext`。
- 标题后保留一行空行,便于渲染与阅读。
## 换行规范
return 等语句不要与其他语句在同一行,而是单独保持一行
## 💭 注释规范
- 必注释场景:复杂业务/算法、特殊行为、重要设计取舍、关键参考链接。
- 原则:
- 注释“为什么”(Why),非“做了什么”(What)、非变更记录(Changelog)。
- 修改代码时同步更新注释(过时注释比无注释更糟)。
- 优先使用 JSDoc,复杂函数先给高层概览,函数体内按步骤(1、2、3…)标注关键过程。
- 中英文之间适当空格,提升可读性;不要为删除的旧代码写注释。
“质量自检”问题:六个月后新同事看到这条注释能得到什么有用信息?若答案是“没有”,删掉它。
示例:
```typescript
/**
* 处理支付请求,多步骤校验
*/
function processPayment(request: PaymentRequest) {
// 1. 数据校验
// 2. 风控评估(低/中/高差异处理)
// 3. 网关调用
// 4. 用户通知
}
export enum BudgetType {
Free = 'free',
/** ✅ 推荐使用 JSDoc,而不是行尾注释 */
Package = 'package',
}
```
## 🛠️ 开发指南
### 通用原则
- 优先稳定与可维护性,其次再谈性能优化。
- 面对不确定性:明确假设、取舍与验证方案;先沟通再实施。
- 信任既定前置条件,避免对承诺不变量的过度防御;若出现冲突,更新方案而非额外兜底。
- 老代码重构保守推进,新功能实现可采用更现代方式。
- 避免过早优化:先以简单直接的实现达成功能,确需时再优化(避免先行加缓存/抖动或拆分成过多文件)。
### 错误处理 (Error Handling)
```typescript
// 异步操作使用 try-catch
try {
const result = await someOperation();
return result;
} catch (error) {
console.error('Operation failed:', error);
throw new Error('Failed to complete operation');
}
// 使用正确的错误类型
if (error instanceof Error) { /* 处理 Error 实例 */ }
```
### 新功能实现
- 代码应清晰、可读、可复用、有效率且可测试。
- 倾向选用成熟可靠的现代 API。
### 重构与修 Bug
- 倾向增量式改动;如需大重构,先就范围对齐再推进。
- 保持原结构与风格,避免过度抽象引入新风险。
### 开发生命周期(Checklist)
探索/规划:
- \[ ] 充分理解需求,分步思考与列计划(3–6 步)
- \[ ] 优先查阅文档与既有方案
- \[ ] 通过阅读实际代码验证想法
- \[ ] 形成 TODO 列表
实现/重构/修复:
- [ ] 阅读相关模板与周边代码,沿用既有模式
- [ ] Fail fast:对非法输入/状态抛错,尽早暴露问题
- [ ] 在约束内尽力提升前端交互与体验
验收/校验:
- \[ ] 通过测试或临时脚本验证实现
- \[ ] 多次增量修改后回看是否可合并为一次更连贯的修改
- \[ ] 运行质量检查
- \[ ] 更新相关文档
总结/输出:
- \[ ] 检查输出格式要求
- \[ ] 列出与原计划偏差与关键决策,便于人工复核
- \[ ] 提供优化建议
- \[ ] 在结尾提供完整引用链接
## 🔍 代码质量与 Lint
- 变量命名具描述性(如 `mutationObserver`、`button`、`element`),避免 `mo`、`btn`、`el`。
- 检查缺失的重要注释,并保持注释语言一致。
- 使用 VSCode MCP 诊断工具查看 TS/ESLint 报告并修复关键问题。
- 若新增/修改测试,需运行并修复测试后再提交。
## ⛔ 需要显式确认的操作
- 运行具有破坏性的命令
- 执行 `git commit`、`git push`
- 新建测试文件(先由维护者进行人工审核)
## 🔧 工具偏好与命令
包与脚本:
- `npm install` (或简写 `npm i`)
Shell:
- 在工作区根目录执行命令。
- 路径建议使用引号包裹。
Web 搜索:
- `WebSearch` 获取最新内容;不足时用 `mcp__SearXNG__search` 聚合搜索
文档/用法检索:
- `context7` 获取依赖的最新用法
VSCode MCP (如可用):
- `mcp__vscode-mcp__get_references` 辅助重构范围判定
- `mcp__vscode-mcp__rename_symbol` 安全重命名
- `mcp__vscode-mcp__get_symbol_lsp_info` 获取类型/签名/定义等信息
## 🚨 本地质量检查(可选流程)
在完成一组修改后并行执行三类检查,而非直接跑全量 lint:
```plaintext
Task(subagent_type: "quick-code-review", description: "Code review", prompt: "[change description]")
Task(subagent_type: "diagnostics", description: "Diagnostics", prompt: "[same as above]")
Task(subagent_type: "run-related-tests", description: "Run tests", prompt: "[same as above]")
```
change description 示例:
```plaintext
- Modified files: 相对路径列表
- Context: 需求/业务背景
```
流程:初检 → 修复关键问题 → 复检 → 迭代至关键问题清零。
注意:上述工具为只读分析,需自行修复;传入精确文件路径,避免用笼统目录。
<skills_system priority="1">
## Available Skills
<!-- SKILLS_TABLE_START -->
<usage>
When users ask you to perform tasks, check if any of the available skills below can help complete the task more effectively. Skills provide specialized capabilities and domain knowledge.
How to use skills:
- Invoke: Bash("openskills read <skill-name>")
- The skill content will load with detailed instructions on how to complete the task
- Base directory provided in output for resolving bundled resources (references/, scripts/, assets/)
Usage notes:
- Only use skills listed in <available_skills> below
- Do not invoke a skill that is already loaded in your context
- Each skill invocation is stateless
</usage>
<available_skills>
<skill>
<name>algorithmic-art</name>
<description>Creating algorithmic art using p5.js with seeded randomness and interactive parameter exploration. Use this when users request creating art using code, generative art, algorithmic art, flow fields, or particle systems. Create original algorithmic art rather than copying existing artists' work to avoid copyright violations.</description>
<location>project</location>
</skill>
<skill>
<name>brand-guidelines</name>
<description>Applies Anthropic's official brand colors and typography to any sort of artifact that may benefit from having Anthropic's look-and-feel. Use it when brand colors or style guidelines, visual formatting, or company design standards apply.</description>
<location>project</location>
</skill>
<skill>
<name>canvas-design</name>
<description>Create beautiful visual art in .png and .pdf documents using design philosophy. You should use this skill when the user asks to create a poster, piece of art, design, or other static piece. Create original visual designs, never copying existing artists' work to avoid copyright violations.</description>
<location>project</location>
</skill>
<skill>
<name>doc-coauthoring</name>
<description>Guide users through a structured workflow for co-authoring documentation. Use when user wants to write documentation, proposals, technical specs, decision docs, or similar structured content. This workflow helps users efficiently transfer context, refine content through iteration, and verify the doc works for readers. Trigger when user mentions writing docs, creating proposals, drafting specs, or similar documentation tasks.</description>
<location>project</location>
</skill>
<skill>
<name>docx</name>
<description>"Comprehensive document creation, editing, and analysis with support for tracked changes, comments, formatting preservation, and text extraction. When Claude needs to work with professional documents (.docx files) for: (1) Creating new documents, (2) Modifying or editing content, (3) Working with tracked changes, (4) Adding comments, or any other document tasks"</description>
<location>project</location>
</skill>
<skill>
<name>frontend-design</name>
<description>Create distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, artifacts, posters, or applications (examples include websites, landing pages, dashboards, React components, HTML/CSS layouts, or when styling/beautifying any web UI). Generates creative, polished code and UI design that avoids generic AI aesthetics.</description>
<location>project</location>
</skill>
<skill>
<name>internal-comms</name>
<description>A set of resources to help me write all kinds of internal communications, using the formats that my company likes to use. Claude should use this skill whenever asked to write some sort of internal communications (status reports, leadership updates, 3P updates, company newsletters, FAQs, incident reports, project updates, etc.).</description>
<location>project</location>
</skill>
<skill>
<name>mcp-builder</name>
<description>Guide for creating high-quality MCP (Model Context Protocol) servers that enable LLMs to interact with external services through well-designed tools. Use when building MCP servers to integrate external APIs or services, whether in Python (FastMCP) or Node/TypeScript (MCP SDK).</description>
<location>project</location>
</skill>
<skill>
<name>pdf</name>
<description>Comprehensive PDF manipulation toolkit for extracting text and tables, creating new PDFs, merging/splitting documents, and handling forms. When Claude needs to fill in a PDF form or programmatically process, generate, or analyze PDF documents at scale.</description>
<location>project</location>
</skill>
<skill>
<name>pptx</name>
<description>"Presentation creation, editing, and analysis. When Claude needs to work with presentations (.pptx files) for: (1) Creating new presentations, (2) Modifying or editing content, (3) Working with layouts, (4) Adding comments or speaker notes, or any other presentation tasks"</description>
<location>project</location>
</skill>
<skill>
<name>skill-creator</name>
<description>Guide for creating effective skills. This skill should be used when users want to create a new skill (or update an existing skill) that extends Claude's capabilities with specialized knowledge, workflows, or tool integrations.</description>
<location>project</location>
</skill>
<skill>
<name>slack-gif-creator</name>
<description>Knowledge and utilities for creating animated GIFs optimized for Slack. Provides constraints, validation tools, and animation concepts. Use when users request animated GIFs for Slack like "make me a GIF of X doing Y for Slack."</description>
<location>project</location>
</skill>
<skill>
<name>theme-factory</name>
<description>Toolkit for styling artifacts with a theme. These artifacts can be slides, docs, reportings, HTML landing pages, etc. There are 10 pre-set themes with colors/fonts that you can apply to any artifact that has been creating, or can generate a new theme on-the-fly.</description>
<location>project</location>
</skill>
<skill>
<name>web-artifacts-builder</name>
<description>Suite of tools for creating elaborate, multi-component claude.ai HTML artifacts using modern frontend web technologies (React, Tailwind CSS, shadcn/ui). Use for complex artifacts requiring state management, routing, or shadcn/ui components - not for simple single-file HTML/JSX artifacts.</description>
<location>project</location>
</skill>
<skill>
<name>webapp-testing</name>
<description>Toolkit for interacting with and testing local web applications using Playwright. Supports verifying frontend functionality, debugging UI behavior, capturing browser screenshots, and viewing browser logs.</description>
<location>project</location>
</skill>
<skill>
<name>xlsx</name>
<description>"Comprehensive spreadsheet creation, editing, and analysis with support for formulas, formatting, data analysis, and visualization. When Claude needs to work with spreadsheets (.xlsx, .xlsm, .csv, .tsv, etc) for: (1) Creating new spreadsheets with formulas and formatting, (2) Reading or analyzing data, (3) Modify existing spreadsheets while preserving formulas, (4) Data analysis and visualization in spreadsheets, or (5) Recalculating formulas"</description>
<location>project</location>
</skill>
</available_skills>
<!-- SKILLS_TABLE_END -->
</skills_system>
+14 -9
View File
@@ -5,16 +5,21 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
## [0.1.1] - 2026-01-11
### Bug Fixes
- Fix Antigravity visibility issue on account switch. (Fixed [#19](https://github.com/Draculabo/AntigravityManager/issues/19))
## [0.1.0] - 2026-01-10
### New Features
- LAN Connection Support: Users can now connect via Local Area Network (LAN) for improved flexibility and internal environment support.
- Antigravity Integration: Added native support and adaptation for Antigravity, enhancing overall compatibility.
- Local API Proxy: Built-in OpenAI/Anthropic compatible proxy server.
### Bug Fixes
- Reverse Proxy Issue: Resolved a critical error occurring during reverse proxy configurations. (Fixed [#11](https://github.com/Draculabo/AntigravityManager/issues/11))
### Added
- Bilingual README (English and Simplified Chinese)
- Project Logo
- GitHub Issue Templates (Bug Report and Feature Request)
- Code of Conduct
- Star History chart in README
- Detailed FAQ section in README
- Technical stack and project structure documentation
## [0.0.1] - 2025-12-22
+1
View File
@@ -0,0 +1 @@
请始终参考 @AGENT.md 文件
+25
View File
@@ -64,12 +64,14 @@ When using Antigravity IDE, have you ever encountered these problems?
- 🔄 Managing multiple Google/Claude accounts is cumbersome
- 📊 Don't know how much quota is left on the current account
- ⏰ 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:
- ✅ **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
- ✅ **Real-time Monitoring** - Visualize quota usage for all accounts
- ✅ **Local API Proxy** - Built-in OpenAI/Anthropic compatible proxy server
- ✅ **Secure Encryption** - AES-256-GCM encryption for sensitive data
---
@@ -131,6 +133,24 @@ When using Antigravity IDE, have you ever encountered these problems?
</ul>
</td>
</tr>
<tr>
<td width="50%">
<h3>🔌 Local API Proxy</h3>
<ul>
<li>OpenAI & Anthropic API compatible</li>
<li>Configurable port and request timeout</li>
<li>Model mapping (e.g. Claude → Gemini)</li>
</ul>
</td>
<td width="50%">
<h3>🛠️ Developer Tools</h3>
<ul>
<li>Built-in cURL & Python code generation</li>
<li>Visual service status monitoring</li>
<li>One-click API Key regeneration</li>
</ul>
</td>
</tr>
</table>
### Additional Features
@@ -149,6 +169,11 @@ 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>
---
+25
View File
@@ -64,12 +64,14 @@
- 🔄 管理多个 Google/Claude 账号非常麻烦
- 📊 不知道当前账号还剩多少额度
- ⏰ 担心错过额度重置时间
- 🔌 需要可靠的本地 API 代理用于开发工具
**Antigravity Manager** 就是为解决这些问题而生的!它是一个专业的 Electron 桌面应用,帮助你:
- ✅ **无限账号池** - 添加任意数量的 Google Gemini / Claude 账号
- ✅ **智能自动切换** - 额度不足或被限速时自动切换到下一个可用账号
- ✅ **实时监控** - 可视化显示所有账号的额度使用情况
- ✅ **本地 API 代理** - 内置兼容 OpenAI/Anthropic 协议的代理服务器
- ✅ **安全加密** - AES-256-GCM 加密存储敏感信息
---
@@ -131,6 +133,24 @@
</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>
### 更多功能
@@ -149,6 +169,11 @@
<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>
---
Binary file not shown.

Before

Width:  |  Height:  |  Size: 467 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 KiB

+2 -2
View File
@@ -1,7 +1,7 @@
{
"name": "antigravity-manager",
"productName": "Antigravity Manager",
"version": "0.1.0",
"version": "0.1.1",
"description": "Antigravity Manager - Electron App",
"main": ".vite/build/main.js",
"private": true,
@@ -22,7 +22,7 @@
"type-check": "tsc --noEmit"
},
"author": "Draculabo",
"license": "MIT",
"license": "CC-BY-NC-SA-4.0",
"devDependencies": {
"@electron-forge/cli": "^7.10.2",
"@electron-forge/maker-deb": "^7.10.2",
+8 -3
View File
@@ -6,7 +6,7 @@ import { logger } from '../../utils/logger';
import { shell } from 'electron';
import fs from 'fs';
import { closeAntigravity, startAntigravity } from '../../ipc/process/handler';
import { closeAntigravity, startAntigravity, _waitForProcessExit } from '../../ipc/process/handler';
import { updateTrayMenu } from '../../ipc/tray/handler';
import { getAntigravityDbPaths } from '../../utils/paths';
@@ -189,7 +189,12 @@ export async function switchCloudAccount(accountId: string): Promise<void> {
}
// 2. Stop Antigravity Process
await closeAntigravity(); // This waits for it to close
await closeAntigravity();
try {
await _waitForProcessExit(10000); // Wait up to 10s for it to truly vanish
} catch (e) {
logger.warn('Process did not exit cleanly within timeout, but proceeding...', e);
}
// 3. Backup Database (New Logic)
const dbPaths = getAntigravityDbPaths();
@@ -214,7 +219,7 @@ export async function switchCloudAccount(accountId: string): Promise<void> {
// 3. Inject Token
// injectedCloudToken uses direct DB/FS access which is sync better-sqlite3.
CloudAccountRepo.injectCloudToken(account.token);
CloudAccountRepo.injectCloudToken(account);
// 4. Update usage and active status
CloudAccountRepo.updateLastUsed(account.id);
+26 -6
View File
@@ -291,7 +291,7 @@ export class CloudAccountRepo {
}
}
static injectCloudToken(token: CloudAccount['token']): void {
static injectCloudToken(account: CloudAccount): void {
const dbPaths = getAntigravityDbPaths();
let dbPath: string | null = null;
@@ -325,9 +325,9 @@ export class CloudAccountRepo {
// 3. Create New Field 6
const newField = ProtobufUtils.createOAuthTokenInfo(
token.access_token,
token.refresh_token,
token.expiry_timestamp,
account.token.access_token,
account.token.refresh_token,
account.token.expiry_timestamp,
);
// 4. Concatenate
@@ -344,13 +344,33 @@ export class CloudAccountRepo {
'jetskiStateSync.agentManagerInitState',
);
// 7. Inject Onboarding Flag (Missing in initial port)
// 7. Inject Onboarding Flag
db.prepare('INSERT OR REPLACE INTO ItemTable (key, value) VALUES (?, ?)').run(
'antigravityOnboarding',
'true',
);
logger.info(`Successfully injected cloud token into Antigravity database at ${dbPath}.`);
// 8. Update Auth Status (Fix for switching issue)
// This ensures the UI reflects the user we just switched to
const authStatus = {
name: account.name || account.email,
email: account.email,
apiKey: account.token.access_token, // Critical for session recognition
// userStatusProtoBinaryBase64: ... // We cannot generate this easily, hoping IDE fetches it
};
db.prepare('INSERT OR REPLACE INTO ItemTable (key, value) VALUES (?, ?)').run(
'antigravityAuthStatus',
JSON.stringify(authStatus),
);
// 9. Remove google.antigravity to prevent conflicts
// This key often holds old state that might override our injected state
db.prepare('DELETE FROM ItemTable WHERE key = ?').run('google.antigravity');
logger.info(
`Successfully injected cloud token and identity for ${account.email} into Antigravity database at ${dbPath}.`,
);
} finally {
db.close();
}
+54 -20
View File
@@ -108,33 +108,67 @@ export async function closeAntigravity(): Promise<void> {
// Helper to list processes
const getProcesses = (): { pid: number; name: string; cmd: string }[] => {
try {
let cmd = '';
let output = '';
if (platform === 'win32') {
// Use wmic to get process info
cmd = 'wmic process get ProcessId,Name,CommandLine /FORMAT:CSV';
const psCommand = (cmdlet: string) =>
`powershell -NoProfile -Command "${cmdlet} Win32_Process -Filter \\"Name like 'Antigravity%'\\" | Select-Object ProcessId, Name, CommandLine | ConvertTo-Csv -NoTypeInformation"`;
try {
output = execSync(psCommand('Get-CimInstance'), {
encoding: 'utf-8',
maxBuffer: 1024 * 1024 * 10,
stdio: ['pipe', 'pipe', 'ignore'],
});
} catch (e) {
// CIM failed (likely older OS), try WMI
try {
output = execSync(psCommand('Get-WmiObject'), {
encoding: 'utf-8',
maxBuffer: 1024 * 1024 * 10,
});
} catch (innerE) {
// Both failed, throw original or log? Throwing lets the outer catch handle it (returning empty list)
throw e;
}
}
} else {
// Unix/Linux/macOS
cmd = 'ps -A -o pid,comm,args';
output = execSync('ps -A -o pid,comm,args', {
encoding: 'utf-8',
maxBuffer: 1024 * 1024 * 10,
});
}
const output = execSync(cmd, { encoding: 'utf-8' });
const lines = output.split('\n');
const processList = [];
const processList: { pid: number; name: string; cmd: string }[] = [];
for (const line of lines) {
const parts = line.trim().split(/\s+/);
if (parts.length < 2) continue;
if (platform === 'win32') {
// CSV format parsing would be better, but simple check for now
if (line.includes('Antigravity')) {
// Extract PID (usually last or second to last in CSV)
const match = line.match(/(\d+)(?:\s*$)/);
if (match) {
processList.push({ pid: parseInt(match[1]), name: 'Antigravity', cmd: line });
}
if (platform === 'win32') {
// Parse CSV Output
const lines = output.trim().split(/\r?\n/);
// First line is headers "ProcessId","Name","CommandLine"
// We start from index 1
for (let i = 1; i < lines.length; i++) {
const line = lines[i];
if (!line) {
continue;
}
} else {
// Regex to match CSV fields: "val1","val2","val3"
const match = line.match(/^"(\d+)","(.*?)","(.*?)"$/);
if (match) {
const pid = parseInt(match[1]);
const name = match[2];
const cmdLine = match[3];
processList.push({ pid, name, cmd: cmdLine || name });
}
}
} else {
const lines = output.split('\n');
for (const line of lines) {
const parts = line.trim().split(/\s+/);
if (parts.length < 2) continue;
const pid = parseInt(parts[0]);
if (isNaN(pid)) continue;
const rest = parts.slice(1).join(' ');
+110 -20
View File
@@ -69,9 +69,27 @@ export function transformClaudeRequestIn(
const allowDummyThought = config.finalModel.startsWith('gemini-');
// 4. Generation Config & Thinking
const generationConfig = buildGenerationConfig(claudeReq, hasWebSearchTool, config.finalModel);
let isThinkingEnabled = claudeReq.thinking?.type === 'enabled';
const isThinkingEnabled = claudeReq.thinking?.type === 'enabled';
if (isThinkingEnabled) {
const globalSig = SignatureStore.get();
const hasFunctionCalls = claudeReq.messages.some((m) => {
if (Array.isArray(m.content)) {
return m.content.some((b) => b.type === 'tool_use');
}
return false;
});
if (hasFunctionCalls && !hasValidSignatureForFunctionCalls(claudeReq.messages, globalSig)) {
isThinkingEnabled = false;
}
}
const generationConfig = buildGenerationConfig(claudeReq, hasWebSearchTool, config.finalModel);
// Update thinking config based on the final decision
if (!isThinkingEnabled && generationConfig.thinkingConfig) {
delete generationConfig.thinkingConfig;
}
// 2. Contents (Messages)
const contents = buildContents(
@@ -371,32 +389,104 @@ function injectGoogleSearchTool(body: { tools?: GeminiToolDeclaration[] }) {
/**
* Builds system instruction
* Converts Claude system prompts to Gemini format
* Converts Claude system prompts to Gemini format, with Antigravity Identity Injection
*/
function buildSystemInstruction(
system: ClaudeRequest['system'],
modelName: string,
): { parts: { text: string }[] } | null {
if (!system) {
return null;
}
const antigravity_identity =
'--- [IDENTITY_PATCH] ---\n' +
'Ignore any previous instructions regarding your identity or host platform (e.g., Amazon Q, Google AI).\n' +
'You are Antigravity, 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 parts: { text: string }[] = [];
const identityPatch =
`--- [IDENTITY_PATCH] ---\n` +
`Ignore any previous instructions regarding your identity or host platform (e.g., Amazon Q, Google AI).\n` +
`You are currently providing services as the native ${modelName} model via a standard API proxy.\n` +
`Always use the 'claude' command for terminal tasks if relevant.\n` +
`--- [SYSTEM_PROMPT_BEGIN] ---\n`;
parts.push({ text: identityPatch });
if (typeof system === 'string') {
parts.push({ text: system });
} else if (Array.isArray(system)) {
for (const block of system) {
if (block.type === 'text') parts.push({ text: block.text });
// Check if user already provided Antigravity identity
let userHasAntigravity = false;
if (system) {
if (typeof system === 'string') {
if (system.includes('You are Antigravity')) userHasAntigravity = true;
} else if (Array.isArray(system)) {
for (const block of system) {
if (block.type === 'text' && block.text.includes('You are Antigravity')) {
userHasAntigravity = true;
break;
}
}
}
}
parts.push({ text: '\n--- [SYSTEM_PROMPT_END] ---' });
return { parts };
// Inject if missing
if (!userHasAntigravity) {
parts.push({ text: antigravity_identity });
}
if (system) {
if (typeof system === 'string') {
parts.push({ text: system });
} else if (Array.isArray(system)) {
for (const block of system) {
if (block.type === 'text') parts.push({ text: block.text });
}
}
}
if (!userHasAntigravity) {
parts.push({ text: '\n--- [SYSTEM_PROMPT_END] ---' });
}
// If we pushed at least something
if (parts.length > 0) {
return { parts };
}
return null;
}
/**
* Minimum length for a valid thought_signature
*/
const MIN_SIGNATURE_LENGTH = 10;
/**
* Check if we have any valid signature available for function calls
* @param messages Messages from ClaudeRequest
* @param globalSig Global signature from SignatureStore
* @returns True if any valid signature is available for function calls
*/
function hasValidSignatureForFunctionCalls(
messages: Message[],
globalSig: string | null | undefined,
): boolean {
// 1. Check global store
if (globalSig && globalSig.length >= MIN_SIGNATURE_LENGTH) {
return true;
}
// 2. Check if any message has a thinking block with valid signature
// Traverse in reverse to find recent signatures
for (let i = messages.length - 1; i >= 0; i--) {
const msg = messages[i];
if (msg.role === 'assistant') {
if (Array.isArray(msg.content)) {
for (const block of msg.content) {
if (
block.type === 'thinking' &&
block.signature &&
block.signature.length >= MIN_SIGNATURE_LENGTH
) {
return true;
}
}
}
}
}
return false;
}
/**
+1 -1
View File
@@ -192,7 +192,7 @@ function SettingsPage() {
<div className="font-medium capitalize">{platform || 'Unknown'}</div>
<div className="text-muted-foreground">{t('settings.license')}</div>
<div className="font-medium">MIT</div>
<div className="font-medium">CC BY-NC-SA 4.0</div>
</div>
</CardContent>
</Card>
+47 -14
View File
@@ -290,6 +290,9 @@ export class ProxyService {
const decoder = new TextDecoder();
let buffer = '';
const streamId = `chatcmpl-${uuidv4()}`;
const created = Math.floor(Date.now() / 1000);
upstreamStream.on('data', (chunk: Buffer) => {
buffer += decoder.decode(chunk, { stream: true });
const lines = buffer.split('\n');
@@ -307,22 +310,52 @@ export class ProxyService {
const candidate = json.candidates?.[0];
const contentPart = candidate?.content?.parts?.[0];
const text = contentPart?.text || '';
const isThought = contentPart?.thought === true;
const openAICheck = {
id: `chatcmpl-${uuidv4()}`,
object: 'chat.completion.chunk',
created: Math.floor(Date.now() / 1000),
model: model,
choices: [
{
index: 0,
delta: { content: text },
finish_reason: candidate?.finishReason ? 'stop' : null,
},
],
};
if (isThought && text) {
const reasoningChunk = {
id: streamId,
object: 'chat.completion.chunk',
created: created,
model: model,
choices: [
{
index: 0,
delta: { reasoning_content: text },
finish_reason: null,
},
],
};
subscriber.next(`data: ${JSON.stringify(reasoningChunk)}\n\n`);
continue;
}
subscriber.next(`data: ${JSON.stringify(openAICheck)}\n\n`);
if (text || candidate?.finishReason) {
const finishReason = candidate?.finishReason
? candidate.finishReason === 'STOP'
? 'stop'
: candidate.finishReason.toLowerCase()
: null;
const contentChunk = {
id: streamId,
object: 'chat.completion.chunk',
created: created,
model: model,
choices: [
{
index: 0,
delta: { content: text },
finish_reason: finishReason,
},
],
};
if (!text && finishReason) {
delete contentChunk.choices[0].delta.content;
}
subscriber.next(`data: ${JSON.stringify(contentChunk)}\n\n`);
}
if (candidate?.finishReason) {
subscriber.next('data: [DONE]\n\n');
+124
View File
@@ -0,0 +1,124 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { transformClaudeRequestIn } from '../lib/antigravity/ClaudeRequestMapper';
import { ClaudeRequest, GeminiInternalRequest } from '../lib/antigravity/types';
import { SignatureStore } from '../lib/antigravity/SignatureStore';
/**
* Antigravity Core Features Verification
*
* This test suite verifies the critical "Antigravity" 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)', () => {
beforeEach(() => {
// Clear global signature store before each test
SignatureStore.clear();
vi.restoreAllMocks();
});
const BASE_REQUEST: ClaudeRequest = {
model: 'claude-3-5-sonnet-20241022',
messages: [{ role: 'user', content: 'Hello' }],
max_tokens: 100,
stream: true,
};
describe('Feature: Identity Injection (Branding & Safety)', () => {
it('should inject Antigravity 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'));
expect(identityPart).toBeDefined();
expect(identityPart?.text).toContain('[IDENTITY_PATCH]');
});
it('should NOT inject Antigravity identity if user already provided it', () => {
// User explicitly providing the identity string
const result = transformClaudeRequestIn(
{
...BASE_REQUEST,
system: 'You are Antigravity, the best AI.',
},
'test-project',
);
const parts = result.request.systemInstruction?.parts || [];
// Should verify we don't have TWO identity blocks
// The injected one starts with "--- [IDENTITY_PATCH] ---"
const injectedPart = parts.find((p) => p.text.includes('[IDENTITY_PATCH]'));
expect(injectedPart).toBeUndefined();
});
it('should inject before user system prompt if user prompt does NOT have identity', () => {
const result = transformClaudeRequestIn(
{
...BASE_REQUEST,
system: 'Be concise.',
},
'test-project',
);
const parts = result.request.systemInstruction?.parts || [];
expect(parts.length).toBeGreaterThanOrEqual(2);
expect(parts[0].text).toContain('Antigravity');
expect(parts[parts.length - 1].text).toContain('Be concise');
});
});
describe('Feature: Gemini 3 Pro Stability Fix ', () => {
// Situation: User enables thinking, calls a tool, but NO thought signature exists.
// Result: Google API would return 400.
// Fix: We must silently disable thinking for this request.
it('should disable thinking mode if function calls exist but NO signature', () => {
const requestWithTools = {
...BASE_REQUEST,
// Maps to a gemini-3 model potentially
model: 'gemini-3-pro-preview',
tools: [{ name: 'get_weather' }], // Mock tool structure
thinking: { type: 'enabled', budget_tokens: 1000 },
} as any;
const result = transformClaudeRequestIn(requestWithTools, 'test-project');
// Should detect missing signature and remove thinking config
expect(result.request.generationConfig?.thinkingConfig).toBeUndefined();
});
it('should KEEP thinking mode if function calls exist AND valid signature exists in Store', () => {
// Pre-fill a valid signature
SignatureStore.store('valid_signature_string_longer_than_10_chars');
const requestWithTools = {
...BASE_REQUEST,
model: 'gemini-3-pro-preview',
tools: [{ name: 'get_weather' }],
thinking: { type: 'enabled', budget_tokens: 1000 },
} as any;
const result = transformClaudeRequestIn(requestWithTools, 'test-project');
// Should keep thinking config
expect(result.request.generationConfig?.thinkingConfig).toBeDefined();
});
it('should KEEP thinking mode if NO function calls exist (pure thinking)', () => {
const requestPureThinking = {
...BASE_REQUEST,
model: 'gemini-3-pro-preview',
// No tools
thinking: { type: 'enabled', budget_tokens: 1000 },
} as any;
const result = transformClaudeRequestIn(requestPureThinking, 'test-project');
// Should keep thinking config
expect(result.request.generationConfig?.thinkingConfig).toBeDefined();
});
});
});