better repo config

This commit is contained in:
Noe
2025-12-10 15:18:43 +00:00
parent ea529ce52b
commit 8e58a2474c
11 changed files with 1866 additions and 103 deletions

40
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@@ -0,0 +1,40 @@
---
name: Bug Report
about: Report a bug or issue with the plugin
title: '[BUG] '
labels: bug
assignees: ''
---
**Bug Description**
A clear and concise description of the bug.
**Steps to Reproduce**
1.
2.
3.
**Expected Behavior**
What should happen.
**Actual Behavior**
What actually happens.
**Environment**
- opencode version:
- Plugin version:
- Operating System:
- Node.js version:
**Logs**
If applicable, attach logs (enable verbose logging with `export OPENCODE_ANTIGRAVITY_DEBUG=1`). Logs are saved in the current directory as `antigravity-debug-<timestamp>.log`.
**Compliance Checklist**
Please confirm:
- [ ] I'm using this plugin for personal development only
- [ ] I have an active Google Cloud project with Antigravity enabled
- [ ] This issue is not related to attempting commercial use or TOS violations
- [ ] I've reviewed the README troubleshooting section
**Additional Context**
Add any other relevant information.

11
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@@ -0,0 +1,11 @@
blank_issues_enabled: false
contact_links:
- name: Google Cloud Support
url: https://cloud.google.com/support
about: For Google Cloud account or billing issues, contact Google Cloud directly
- name: Google Terms of Service
url: https://policies.google.com/terms
about: Review Google's Terms of Service for compliance questions
- name: Discussions
url: https://github.com/NoeFabris/opencode-antigravity-auth/discussions
about: Ask questions or discuss the plugin with the community

View File

@@ -0,0 +1,28 @@
---
name: Feature Request
about: Suggest a new feature or enhancement
title: '[FEATURE] '
labels: enhancement
assignees: ''
---
**Feature Description**
A clear description of the feature you'd like to see.
**Use Case**
Explain how this feature would be used and what problem it solves.
**Proposed Implementation**
If you have ideas about how this could be implemented, share them here.
**Compliance Consideration**
Please confirm:
- [ ] This feature is for personal development use
- [ ] This feature does not violate or circumvent Google's Terms of Service
- [ ] This feature is not intended for commercial resale or multi-user access
**Alternatives Considered**
Have you considered any alternative solutions or workarounds?
**Additional Context**
Add any other context, screenshots, or examples.

34
.github/workflows/ci.yml vendored Normal file
View File

@@ -0,0 +1,34 @@
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
name: Test on Node.js
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run type check
run: npm run typecheck
- name: Run tests
run: npm test
- name: Build
run: npm run build

76
AGENTS.MD Normal file
View File

@@ -0,0 +1,76 @@
# AGENTS.md
This file provides coding guidance for AI agents when working with code in this repository.
## Overview
This is an **opencode plugin** that enables OAuth authentication with Google's Antigravity IDE backend. It allows users to access models like `gemini-3-pro-high` and `claude-sonnet-4-5` using their Google credentials.
## Build & Test Commands
```bash
# Install dependencies
npm install
# Build (compiles TypeScript)
npm run build
# Type checking only
npm run typecheck
# Run tests
npm test
```
## Code Architecture
### Plugin Flow (src/plugin.ts)
The main entry point `createAntigravityPlugin` orchestrates a request interception flow:
1. **Auth Validation**: Checks if the request is for the Antigravity provider and if OAuth is used.
2. **Token Refresh**: Checks for expired access tokens and refreshes them using `refreshAccessToken`.
3. **Project Context**: Resolves the effective Google Cloud project ID.
4. **Endpoint Fallback**: Tries multiple Antigravity endpoints in sequence (`daily` → `autopush` → `prod`) if requests fail with specific error codes (403, 404, 429, 5xx).
5. **Response Transformation**: Converts the Antigravity response format to what opencode expects.
### Module Organization
**Core Plugin** (`src/plugin.ts`)
- Plugin definition and request interception logic.
- Implements endpoint fallback strategy.
**Authentication** (`src/antigravity/oauth.ts`, `src/plugin/auth.ts`)
- Handles OAuth flow, token exchange, and token validation.
- `src/plugin/server.ts`: Local HTTP server for OAuth callback.
**Request Handling** (`src/plugin/request.ts`)
- `prepareAntigravityRequest`: Prepares headers and body for Antigravity API.
- `transformAntigravityResponse`: Handles response transformation.
**Configuration** (`src/constants.ts`)
- Contains constants like provider IDs, redirect URIs, and endpoint URLs.
## Key Design Patterns
**1. Endpoint Fallback**:
- The plugin robustly handles service availability by iterating through a list of fallback endpoints defined in `ANTIGRAVITY_ENDPOINT_FALLBACKS`.
**2. Automatic Token Refresh**:
- Access tokens are automatically checked for expiration and refreshed before requests are sent.
**3. Debug Logging**:
- Extensive debug logging is available via `startAntigravityDebugRequest` when enabled.
## TypeScript Configuration
- Target: `ESNext`
- Module: `Preserve`
- Module Resolution: `bundler`
- Strict mode enabled.
## Dependencies
- `@openauthjs/openauth`: Handles OAuth PKCE implementation.
- `vitest`: Testing framework.
- `typescript`: Peer dependency.

View File

@@ -1,63 +0,0 @@
{
"lockfileVersion": 1,
"workspaces": {
"": {
"name": "opencode-antigravity-auth",
"dependencies": {
"@openauthjs/openauth": "^0.4.3",
},
"devDependencies": {
"@opencode-ai/plugin": "^0.15.30",
"@types/bun": "latest",
},
"peerDependencies": {
"typescript": "^5",
},
},
},
"packages": {
"@openauthjs/openauth": ["@openauthjs/openauth@0.4.3", "", { "dependencies": { "@standard-schema/spec": "1.0.0-beta.3", "aws4fetch": "1.0.20", "jose": "5.9.6" }, "peerDependencies": { "arctic": "^2.2.2", "hono": "^4.0.0" } }, "sha512-RlnjqvHzqcbFVymEwhlUEuac4utA5h4nhSK/i2szZuQmxTIqbGUxZ+nM+avM+VV4Ing+/ZaNLKILoXS3yrkOOw=="],
"@opencode-ai/plugin": ["@opencode-ai/plugin@0.15.30", "", { "dependencies": { "@opencode-ai/sdk": "0.15.30", "zod": "4.1.8" } }, "sha512-m12MGUOTqhaNmt7Xyhs6IH1t8Ki2brvWi6Ho6m32D/827lsJp5fr6j9STGNGaRkTpaxKPNgMW5/xVrWIBenAfw=="],
"@opencode-ai/sdk": ["@opencode-ai/sdk@0.15.30", "", {}, "sha512-Fu4iBoB+ksd9CFFmjiCeEW+x03wwUO3r8/cRlwSW5hVfPyMkY0mCkXFyTeB6n9g5IJ1m/ySpCUM0qzs4RBjyEA=="],
"@oslojs/asn1": ["@oslojs/asn1@1.0.0", "", { "dependencies": { "@oslojs/binary": "1.0.0" } }, "sha512-zw/wn0sj0j0QKbIXfIlnEcTviaCzYOY3V5rAyjR6YtOByFtJiT574+8p9Wlach0lZH9fddD4yb9laEAIl4vXQA=="],
"@oslojs/binary": ["@oslojs/binary@1.0.0", "", {}, "sha512-9RCU6OwXU6p67H4NODbuxv2S3eenuQ4/WFLrsq+K/k682xrznH5EVWA7N4VFk9VYVcbFtKqur5YQQZc0ySGhsQ=="],
"@oslojs/crypto": ["@oslojs/crypto@1.0.1", "", { "dependencies": { "@oslojs/asn1": "1.0.0", "@oslojs/binary": "1.0.0" } }, "sha512-7n08G8nWjAr/Yu3vu9zzrd0L9XnrJfpMioQcvCMxBIiF5orECHe5/3J0jmXRVvgfqMm/+4oxlQ+Sq39COYLcNQ=="],
"@oslojs/encoding": ["@oslojs/encoding@1.1.0", "", {}, "sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ=="],
"@oslojs/jwt": ["@oslojs/jwt@0.2.0", "", { "dependencies": { "@oslojs/encoding": "0.4.1" } }, "sha512-bLE7BtHrURedCn4Mco3ma9L4Y1GR2SMBuIvjWr7rmQ4/W/4Jy70TIAgZ+0nIlk0xHz1vNP8x8DCns45Sb2XRbg=="],
"@standard-schema/spec": ["@standard-schema/spec@1.0.0-beta.3", "", {}, "sha512-0ifF3BjA1E8SY9C+nUew8RefNOIq0cDlYALPty4rhUm8Rrl6tCM8hBT4bhGhx7I7iXD0uAgt50lgo8dD73ACMw=="],
"@types/bun": ["@types/bun@1.3.1", "", { "dependencies": { "bun-types": "1.3.1" } }, "sha512-4jNMk2/K9YJtfqwoAa28c8wK+T7nvJFOjxI4h/7sORWcypRNxBpr+TPNaCfVWq70tLCJsqoFwcf0oI0JU/fvMQ=="],
"@types/node": ["@types/node@24.9.2", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-uWN8YqxXxqFMX2RqGOrumsKeti4LlmIMIyV0lgut4jx7KQBcBiW6vkDtIBvHnHIquwNfJhk8v2OtmO8zXWHfPA=="],
"@types/react": ["@types/react@19.2.2", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA=="],
"arctic": ["arctic@2.3.4", "", { "dependencies": { "@oslojs/crypto": "1.0.1", "@oslojs/encoding": "1.1.0", "@oslojs/jwt": "0.2.0" } }, "sha512-+p30BOWsctZp+CVYCt7oAean/hWGW42sH5LAcRQX56ttEkFJWbzXBhmSpibbzwSJkRrotmsA+oAoJoVsU0f5xA=="],
"aws4fetch": ["aws4fetch@1.0.20", "", {}, "sha512-/djoAN709iY65ETD6LKCtyyEI04XIBP5xVvfmNxsEP0uJB5tyaGBztSryRr4HqMStr9R06PisQE7m9zDTXKu6g=="],
"bun-types": ["bun-types@1.3.1", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-NMrcy7smratanWJ2mMXdpatalovtxVggkj11bScuWuiOoXTiKIu2eVS1/7qbyI/4yHedtsn175n4Sm4JcdHLXw=="],
"csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
"hono": ["hono@4.10.4", "", {}, "sha512-YG/fo7zlU3KwrBL5vDpWKisLYiM+nVstBQqfr7gCPbSYURnNEP9BDxEMz8KfsDR9JX0lJWDRNc6nXX31v7ZEyg=="],
"jose": ["jose@5.9.6", "", {}, "sha512-AMlnetc9+CV9asI19zHmrgS/WYsWUwCn2R7RzlbJWD7F9eWYUTGyBmU9o6PxngtLGOiDGPRu+Uc4fhKzbpteZQ=="],
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
"undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
"zod": ["zod@4.1.8", "", {}, "sha512-5R1P+WwQqmmMIEACyzSvo4JXHY5WiAFHRMg+zBZKgKS+Q1viRa0C1hmUKtHltoIFKtIdki3pRxkmpP74jnNYHQ=="],
"@oslojs/jwt/@oslojs/encoding": ["@oslojs/encoding@0.4.1", "", {}, "sha512-hkjo6MuIK/kQR5CrGNdAPZhS01ZCXuWDRJ187zh6qqF2+yMHZpD9fAYpX8q2bOO6Ryhl3XpCT6kUX76N8hhm4Q=="],
}
}

1623
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,23 +1,57 @@
{
"name": "opencode-antigravity-auth",
"module": "index.ts",
"version": "1.0.2",
"author": "noefabris",
"repository": "https://github.com/NoeFabris/opencode-antigravity-auth",
"files": [
"index.ts",
"src"
],
"license": "MIT",
"description": "Google Antigravity IDE OAuth auth plugin for Opencode - access Gemini 3 Pro and Claude 4.5 using Google credentials",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"type": "module",
"devDependencies": {
"@opencode-ai/plugin": "^0.15.30",
"@types/bun": "latest",
"@types/node": "^24.10.1"
"license": "MIT",
"author": "noefabris",
"repository": {
"type": "git",
"url": "git+https://github.com/NoeFabris/opencode-antigravity-auth.git"
},
"homepage": "https://github.com/NoeFabris/opencode-antigravity-auth#readme",
"bugs": {
"url": "https://github.com/NoeFabris/opencode-antigravity-auth/issues"
},
"keywords": [
"opencode",
"google",
"antigravity",
"gemini",
"oauth",
"plugin",
"auth",
"claude",
"ide"
],
"engines": {
"node": ">=20.0.0"
},
"files": [
"dist/",
"README.md",
"LICENSE"
],
"scripts": {
"build": "tsc",
"typecheck": "tsc --noEmit",
"test": "vitest run",
"test:watch": "vitest",
"test:ui": "vitest --ui",
"test:coverage": "vitest run --coverage"
},
"peerDependencies": {
"typescript": "^5"
},
"devDependencies": {
"@opencode-ai/plugin": "^0.15.30",
"@types/node": "^24.10.1",
"@vitest/ui": "^3.0.0",
"typescript": "^5.0.0",
"vitest": "^3.0.0"
},
"dependencies": {
"@openauthjs/openauth": "^0.4.3"
}

View File

@@ -27,7 +27,7 @@ interface AntigravityUserTier {
}
interface LoadCodeAssistPayload {
cloudaicompanionProject?: string;
cloudaicompanionProject?: string | { id?: string };
currentTier?: {
id?: string;
};

View File

@@ -1,4 +1,4 @@
import { beforeEach, describe, expect, it, mock } from "bun:test";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { ANTIGRAVITY_PROVIDER_ID } from "../constants";
import { refreshAccessToken } from "./token";
@@ -14,21 +14,21 @@ const baseAuth: OAuthAuthDetails = {
function createClient() {
return {
auth: {
set: mock(async () => {}),
set: vi.fn(async () => {}),
},
} as PluginClient & {
auth: { set: ReturnType<typeof mock<(input: any) => Promise<void>>> };
auth: { set: ReturnType<typeof vi.fn> };
};
}
describe("refreshAccessToken", () => {
beforeEach(() => {
mock.restore();
vi.restoreAllMocks();
});
it("updates the caller but skips persisting when refresh token is unchanged", async () => {
it("updates the caller and persists when refresh token is unchanged", async () => {
const client = createClient();
const fetchMock = mock(async () => {
const fetchMock = vi.fn(async () => {
return new Response(
JSON.stringify({
access_token: "new-access",
@@ -37,17 +37,17 @@ describe("refreshAccessToken", () => {
{ status: 200 },
);
});
(globalThis as { fetch: typeof fetch }).fetch = fetchMock as unknown as typeof fetch;
global.fetch = fetchMock as unknown as typeof fetch;
const result = await refreshAccessToken(baseAuth, client);
const result = await refreshAccessToken(baseAuth, client, ANTIGRAVITY_PROVIDER_ID);
expect(result?.access).toBe("new-access");
expect(client.auth.set.mock.calls.length).toBe(0);
expect(client.auth.set.mock.calls.length).toBe(1);
});
it("persists when Google rotates the refresh token", async () => {
const client = createClient();
const fetchMock = mock(async () => {
const fetchMock = vi.fn(async () => {
return new Response(
JSON.stringify({
access_token: "next-access",
@@ -57,9 +57,9 @@ describe("refreshAccessToken", () => {
{ status: 200 },
);
});
(globalThis as { fetch: typeof fetch }).fetch = fetchMock as unknown as typeof fetch;
global.fetch = fetchMock as unknown as typeof fetch;
const result = await refreshAccessToken(baseAuth, client);
const result = await refreshAccessToken(baseAuth, client, ANTIGRAVITY_PROVIDER_ID);
expect(result?.access).toBe("next-access");
expect(client.auth.set.mock.calls.length).toBe(1);

10
vitest.config.ts Normal file
View File

@@ -0,0 +1,10 @@
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
globals: true,
environment: 'node',
include: ['src/**/*.test.ts'],
exclude: ['node_modules', 'dist'],
},
});