Skip to content

Latest commit

 

History

29 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

maene

An OpenCode plugin that authenticates against Google Antigravity (Cloud Code Assist) via OAuth using a dual-bypass identity (Antigravity headers plus Gemini CLI identification spoof) and rotates across multiple Google accounts with a robin-hood soft-quota strategy.

npm version license node CI


Overview

maene (@wenathlan/maene) is an OpenCode plugin, a CLI, and a library-first TypeScript module set that lets OpenCode talk to Google Antigravity — also known as Google Cloud Code Assist at cloudcode-pa.googleapis.com — using plain Google OAuth tokens, with no user-supplied GCP project id. It impersonates the official Gemini CLI client identification (client id 681255809395-oo8f…b135j, User-Agent GeminiCLI/0.35.3, X-Goog-Api-Client: gl-node/22.19.0, Client-Metadata: ideType=IDE_UNSPECIFIED,platform=PLATFORM_UNSPECIFIED,pluginType=GEMINI) and falls back to the shared public project rising-fact-p41fc when no cloudaicompanionProject can be discovered through loadCodeAssist.

The bypass has two independent faces that share the same JSON identification, headers, and User-Agent. The first face, the Antigravity bypass, wraps an OpenCode request as { project, model, request } and posts it to cloudcode-pa.googleapis.com/v1internal:streamGenerateContent?alt=sse with antigravity-style headers (User-Agent: antigravity/<v> <os>/<arch>). The second face, the Gemini CLI identification spoof, sends the same envelope with the exact Gemini CLI identification headers and is used for gemini-cli-only models (gemini-3, gemini-3.1, gemini-3.5, gemini-3.6, gemini-3.7, preview and customtools variants) or when cli_first is true and the model is a Gemini, which preserves the Antigravity quota for Claude calls.

On top of the bypass, maene adds a robin-hood multi-account rotation layer. A single v3 account store at ~/.config/opencode/antigravity-accounts.json (chmod 0600, atomic write) holds multiple Google accounts, each with its own refresh token, per-model rate-limit reset times, and soft or hard quota cooldowns. The plugin rotates between accounts round-robin (or sticky) at the 90 percent soft-quota threshold, falls back to the not-rate-limited pool, then to the full active pool, so a request is always served rather than 401'd. The runtime constraint is strict: only node:* builtins plus global fetch. Zero npm runtime dependencies. Peers @opencode-ai/sdk and @opencode-ai/plugin are optional.

Features

Keyword Feature Module(s)
antigravity, gemini, google, claude, gemini-3, gemini-3.1, claude-opus-4-6 Frozen 2026 model catalog (gemini 2.5, 3, 3.1, 3.5, 3.6, 3.7, claude 4-5, 4-6, gpt-oss-120b, plus antigravity aliases) models.ts (v2.1.15: model identification lives ONLY here — never in any other file), plugin.ts
opencode, opencode-plugin, cloud-code-assist OpenCode plugin contract (default export, getPlugin() singleton, auth hooks, fetch interceptor) plugin.ts, index.ts
auth, oauth, gemini-cli OAuth 2.0 PKCE against Google with the Gemini CLI client id, local 127.0.0.1 callback server, refresh-token rotation auth.ts (v2.1.15: THE auth owner — every OAuth client identity lives only here)
bypass, dual-bypass Project-id bypass through loadCodeAssist plus onboardUser cascade plus Gemini CLI identification spoof plugin.ts, auth.ts, core.ts, project.ts
robin-hood, multi-account, dual-quota, soft-quota v3 account store, rotation, soft 90 percent threshold, rate-limit reset times, dual-pool cli_first routing accounts.ts, auth.ts, plugin.ts, quota.ts
thinking-cache LRU(100) thinking-signature cache (preserves Claude thoughtSignature across turns) core.ts, streaming.ts, recovery.ts
google-search googleSearch plus urlContext tool injection, grounding-chunk citation extraction, markdown rendering search.ts, request.ts, core.ts
image-generation gemini-3.1-flash-image model, BLOCK_NONE safety, configurable aspect ratio, base64 image persistence plugin.ts, core.ts, search.ts, request.ts
schema-cleaner Recursive JSON-schema sanitizer (10 allowed keys, additionalProperties and defaults rewritten into descriptions) core.ts, request.ts
version-dynamic Two-tier dynamic version resolver (auto-updater plus npm plus GitHub plus storage.googleapis.com, governed fallback 1.19.2, blocked list) version.ts, config.ts, constants.ts
tool-hardening Tool-name regex /^[a-zA-Z][a-zA-Z0-9_]{0,63}$/, dedupe, max 128 tools, Claude [hardening] description prefix core.ts, request.ts, plugin.ts
9router, omniroute Logical to physical model mapping plus PID-offset anti-fingerprint (0 to 1000 ms user_prompt_id randomization) models.ts, plugin.ts
llm-gateway OpenAI and Anthropic to Gemini contents transform, SSE parser, dual output format transformers streaming.ts, request.ts, plugin.ts
maene Plugin id plus alternative name (config alternativeName) index.ts, package.json

Requirements

  • Node >=18 (developed and tested against Node 26.7.0 in CI).
  • bun >=1.0 (developed against bun 1.2.0; required for the lint, build, and test scripts).
  • An OpenCode installation (optional — required only for plugin mode).
  • One or more Google accounts willing to grant OAuth consent to the Gemini CLI public client id.

Install

npm install @wenathlan/maene
# or
bun add @wenathlan/maene
# or
pnpm add @wenathlan/maene

Global CLI install (provides the maene bin):

npm install -g @wenathlan/maene

Peer dependencies (optional but required at runtime for plugin mode): @opencode-ai/sdk ^1.18.3, @opencode-ai/plugin >=0.13.0. The published artifact is the root .ts files (per package.json files), with plugin.ts as main, types, module, and exports["."].

Quick start (CLI)

The bin entry is maene backed by cli.ts. Subcommands:

Command Action
maene login Run OAuth 2.0 PKCE against Google, capture a refresh token, add the account to the v3 store.
maene logout [email] [--all] Revoke refresh tokens and remove account(s) from the store.
maene accounts list Print the v3 store: addedAt, createdAt, expiry, projectId, remaining, limit, rateLimitResetTimes, soft-quota cooldowns.
maene accounts add --email <email> --refresh-token <token> [--project <id>] Add an account non-interactively.
maene enable <email> / maene disable <email> Toggle an account in or out of robin-hood rotation.
maene manage Interactive per-account enable/disable TUI.
maene quota [email] [--all] [--json] Retrieve dual-source quota (Antigravity plus Gemini CLI) per account.
maene config {list,get,set,reset} Inspect or mutate ~/.config/opencode/antigravity.json.
maene configure [--global] [--path <file>] Write the 2026-08-25 model definitions into opencode.json (chmod 0600 atomic write).
maene models {list,info} Browse the frozen 2026 catalog.
maene status Show active account, version, and endpoint health.
maene doctor File, permission, and token-expiry diagnostics.
maene version [--json] Print the resolved maene plus dynamic Antigravity versions.
maene menu / maene interactive Open the interactive menu.
maene help Print the help screen.

Example session:

maene login                         # browser opens, OAuth PKCE completes, refresh_token stored
maene accounts list                 # see the first account in the v3 store
maene login                         # log in again with a second Google account
maene quota --all                   # compare remaining quota across accounts
maene configure --global            # write 2026 model defs into ~/.config/opencode/opencode.json
maene doctor                        # sanity-check permissions and token expiry

Environment overrides:

  • OPENCODE_CONFIG_DIR — overrides the default ~/.config/opencode.
  • ANTIGRAVITY_CLI_AUTORUN=0 — disables auto-run when cli.ts is imported as a module.
  • NO_COLOR — disables ANSI colors in CLI output.
  • OPENCODE_IMAGE_ASPECT_RATIO — overrides the image-generation aspect ratio.

OpenCode plugin registration

Register maene in opencode.json (project-local or ~/.config/opencode/opencode.json). The reference is name-agnostic — any of the forms below works and opencode auto-downloads npm references into its plugin cache (~/.cache/opencode/packages/...):

{
  "$schema": "https://opencode.ai/config.json",
  "plugin": ["@wenathlan/maene"]
}

Pick whichever form you prefer — none is forced:

{ "plugin": ["@wenathlan/maene"] }
{ "plugin": ["@wenathlan/maene@latest"] }
{ "plugin": ["@wenathlan/maene@2.1.16"] }
{ "plugin": ["maene@npm:@wenathlan/maene@latest"] }
{ "plugin": ["file:/absolute/path/to/maene"] }

The fourth entry is the standard npm alias — it installs the package under the plain name maene in opencode's plugin cache. It exists because npmjs.org permanently refuses to register a package literally named maene (typosquat-similarity with madge/panene/sane — 403 "Package name too similar", verified 2026-08-26), so {"plugin": ["maene"]} cannot resolve; the alias is the supported scope-free spelling.

Or generate the whole model catalog (93 google models, 29 antigravity models, 37 top-level shortcuts) with the CLI:

maene configure --global   # writes provider.google.models + provider.antigravity.models

Login through OpenCode (opencode auth login)

Run opencode auth login and pick any of the maene entries — they are the plugin acting behind the scenes:

  • Google — the standard Google entry. Selecting it offers Login with Google (Antigravity – Gemini CLI bypass), the maene OAuth method (Antigravity CLI masquerade, PKCE, automatic loopback callback — no code copy/paste).
  • Antigravity — the standard Antigravity entry with the same OAuth method. maene always ensures a provider antigravity (npm @ai-sdk/google + the maene model catalog) exists in memory — unless you defined your own antigravity block (yours always wins) or disabled the provider — so this entry is usable with zero config and credentials stored under antigravity always find their provider. The injection is add-only and never touches your file on disk (opt out with ["maene", { "inject_antigravity_provider": false }] — note the npm-alias spelling caveat: with a custom provider configured, disabling the injection means the plain antigravity id must exist in your config before logging in through that entry).
  • Your custom provider — whatever you named the provider that carries antigravity-* / gemini-* model ids (casasbahia, my-google, ...) appears with its configured name, recognized by MODEL IDS, never by a fixed name.

The exchanged credential is stored under the provider that actually carries the maene models (model-id detection first, the picked entry's provider id as fallback), so streaming works no matter which entry you picked: auth.loader supplies the active account credentials (auto-refreshed, identity-aware) merged straight into the provider SDK options.

The provider block itself is yours to shape: the npm may be @ai-sdk/google OR @ai-sdk/openai-compatible (both verified live — the loader injects baseURL, the OAuth apiKey, the identity headers and the fetch translator either way, so an openai-compatible block needs no baseURL at all), the provider name is irrelevant (model ids decide), and the same plugin array happily carries other npm plugins next to maene. A real-world example of the verified shape:

{
  "$schema": "https://opencode.ai/config.json",
  "plugin": ["@wenathlan/maene@latest", "@zilliz/memsearch-opencode", "opencode-universal-memory"],
  "provider": {
    "google": {
      "name": "google",
      "npm": "@ai-sdk/openai-compatible",
      "options": {},
      "models": { "antigravity-gemini-3-flash": {}, "gemini-2.5-pro": {} }
    }
  }
}

OpenCode >= 1.18 resolves exports["./server"] (server.ts) first; that entry registers the three auth entries above from the single plugin reference. Loaders that read main (plugin.ts) directly keep working through the V1 module shape { id: "maene", server }, which always carries one auth entry (detected provider, google fallback). The hooks object provides:

  • auth — provider entry with one oauth method: authorize() starts the PKCE local-callback flow (the Antigravity CLI masquerade client) and returns the Google consent URL that opencode renders; after the account owner signs in and grants the consent, the returned callback() resolves with the exchanged refresh/access tokens and persists the account in the robin-hood store. auth.loader supplies the active account credentials (auto-refreshed) for provider requests.
  • config — the documented injection point opencode runs before reading cfg.provider; used only for the zero-config antigravity provider.
  • chat.headers — injects the Gemini CLI identification (User-Agent GeminiCLI/0.57.0, X-Goog-Api-Client gl-node/22.19.0 gccl/0.9.2, Client-Metadata ideType=IDE_UNSPECIFIED,platform=PLATFORM_UNSPECIFIED,pluginType=GEMINI) on requests from MAENE providers only (the detected provider plus the standard google/antigravity entries — since 2.1.13; model-id substrings alone no longer qualify, so openai/gpt-oss-120b under nvidia/openrouter is never polluted) — the dual-bypass second face.
  • tool.execute.before / tool.execute.after — instrumentation passthrough.

The legacy library shape (getPlugin(), the plugin definition object with hooks/auth/config/models) remains exported for direct consumers.

Public API

index.ts is the library-first ESM barrel. Import the default plugin or named helpers:

// plugin root (what opencode loads): the plugin definition + core exports
import plugin, { getPlugin, VERSION, CLIENT_ID } from "@wenathlan/maene";

// full barrel (aggregates, namespaces, all helpers)
import {
  GEMINI_CLI_BYPASS,
  ANTIGRAVITY_CLI_BYPASS,
  ALL_MODELS_2026,
  MODEL_BY_ID,
  MODEL_ROUTING,
} from "@wenathlan/maene/index";

// or namespace imports
import * as Maene from "@wenathlan/maene/index";

const mgr = new Maene.Accounts.AccountManager();
await mgr.load();
const acc = mgr.getNext("round-robin", 90);
console.log(acc?.email);

Top-level named exports:

Export Purpose
plugin (default and named) The OpenCode plugin definition object.
getPlugin(options?) Singleton factory returning the AntigravityPlugin instance.
VERSION maene version string (matches package.json).
PLUGIN_ID The constant string "maene".
GEMINI_CLI_BYPASS Frozen aggregate of the Gemini CLI identification (clientId, clientSecret, userAgent, xGoogApiClient, clientMetadata, fallbackProject, endpoints, isolatedDir).
ANTIGRAVITY_CLI_BYPASS Frozen aggregate of the official Antigravity CLI (agy) identification — clientId, clientSecret, userAgent, xGoogApiClient, official redirect URI, scopes, PKCE flag. This is the client new logins present since 2.1.8.
ALL_MODELS_2026 Headline 2026 model catalog (rich ModelDefinition[]).
MODEL_BY_ID Lookup record by model id.
MODEL_ROUTING Routing config record (per model group, endpoint order, sandbox skip flag).

The 21 namespace re-exports from index.ts:

Namespace Module Purpose
Constants constants.ts L0 raw-value owner (v2.1.15): every cross-domain string/number/map declared once — base URLs, ENDPOINTS host map, user agents, timeouts, version pools.
Fingerprint fingerprint.ts L2 identity-masquerade owner (v2.1.15: absorbed the UA/client-metadata/header builders) — weighted platform pools, FNV-1a session ids, getDynamicUserAgent, getXGoogApiClient.
OAuth auth.ts v2.1.14 alias: the full OAuth 2.0 PKCE flow (callback server, token refresh, userinfo) now lives in the consolidated authentication module.
Accounts accounts.ts L3 account-store owner (v2.1.15: owns the Account type and RotationStrategy): rich v3 AccountManager, store I/O, atomic chmod-0600 writes.
Auth auth.ts L2 THE authentication module (v2.1.15: every OAuth client identity — the gemini pair declared once as GEMINI_CLI_OAUTH_CLIENT_ID/SECRET; project discovery lives in project.ts): accountManager, robin-hood getNext, full OAuth flow.
Core core.ts L1 generic-utility leaf (v2.1.15: hashing, jitter, fetchWithTimeout, retry classifiers, id generators, LRU thinking cache — schema-cleaner moved to request.ts, transformers to streaming.ts).
System system.ts v2.1.14 shim: re-exports the configuration module's system utilities (debug logger, version fetcher, validators, atomic config write).
Models / Models2026 models.ts L1 THE model-identification owner (v2.1.15: catalogs, classifiers, thinking tables, routing orders — model names never appear in any other file).
Project project.ts L3 THE project-discovery owner (v2.1.15: loadCodeAssist plus onboardUser cascade, projectId cache, resolveProjectId).
Quota quota.ts L4 quota owner: dual-source quota manager, adaptive TTL cache, file-backed quota cache, runtime QuotaConfig.
Config config.ts L2 THE configuration module (v2.1.15: config + validate + system — schema-driven config (Draft-07), sync and async loaders, validators, config-file IO).
Debug debug.ts L1 logging owner: production logger (10 MB rotation, 7-day retention, secret redaction).
Version version.ts L2 version-detection owner: dynamic Antigravity version resolver (4-tier legacy plus 4-tier observer chain).
RequestHelpers request.ts v2.1.14 alias: schema-cleaner, message normalizer, thinking-budget mapper, variant parser now live in the consolidated request module.
Request request.ts L4 THE request-building owner (v2.1.15: the LIVE opencode pipeline): OpenAI/Anthropic to Gemini transform, dual builder, endpoint cascade, tool sanitizers, API_PATHS.
Streaming streaming.ts L4 streaming owner: tolerant SSE parser, OpenAI/Anthropic streaming transformers, replay cache, retry.
Recovery recovery.ts L4 recovery owner: auto-recovery engine (toast notifier, error detectors, signature preservation, retry wrapper).
Search search.ts L4 search-grounding owner: Google Search grounding, urlContext, image-gen config, citation extraction.
CLI cli.ts L5 CLI entry (runCLI, subcommand handlers, atomic writers).
PluginModule plugin.ts L5 plugin entry, orchestration only (fetch hook, dual-bypass, account selection — every duplicated family imported from its owner).

package.json subpath exports: ., ./plugin, ./server, ./constants, ./cli, ./auth, ./core, ./models, ./streaming, ./recovery, ./search, ./system, ./antigravity-cli, ./index. The ./system and ./antigravity-cli subpaths resolve to compatibility shims over the consolidated modules (config.ts and auth.ts respectively). All three resolution keys (types, import, default) point at the root .ts file (no build artifact required at dev time).

Configuration

maene is configured through ~/.config/opencode/antigravity.json (override the location with OPENCODE_CONFIG_DIR). The shipped antigravity.json is dual-purpose: it is both a JSON Schema (Draft-07) for the user-editable keys and a frozen manifest of the Gemini CLI bypass identity (the x-antigravity-bypass object). User keys:

Key Type Default Purpose
oauth_identity "auto" or "antigravity-cli" or "gemini-cli" "auto" OAuth client identity for NEW logins. auto/antigravity-cli masquerades as the official Antigravity CLI (agy) — its client onboards the current free tier and Google accepts a loopback redirect for it, so the login is fully automatic (no code copy/paste). gemini-cli keeps the legacy client. Stored accounts always refresh with the client they authenticated with.
cli_first boolean true Prefer Gemini CLI pool for Gemini models, preserving Antigravity quota for Claude.
pid_offset_enabled boolean true 9router PID-offset anti-fingerprint (0 to 1000 ms randomization on user_prompt_id).
google_search_enabled boolean or "auto" or string false Inject googleSearch plus urlContext tools. "auto" enables grounding for Gemini models only.
quota_refresh_interval_minutes number 1 to 1440 15 Quota cache refresh interval.
soft_quota_cache_ttl_minutes number 1 to 1440 or "auto" 5 Soft-quota cooldown TTL.
soft_quota_threshold_percent number 0 to 100 90 Robin-hood soft-quota rotation threshold.
rotation_strategy "round-robin" or "sticky" "round-robin" Account selection strategy.
claude_tool_hardening boolean true Prefix Claude tool descriptions with [hardening] hint.
keep_thinking boolean false Preserve thinking blocks across turns.
toast_scope "all" or "project" or "minimal" or "none" or "root_only" "all" Toast notification scope.
quota_fallback boolean or "auto" or "antigravity-first" or "cli-first" "antigravity-first" Quota-exhausted fallback policy.
quiet_mode boolean false Suppress non-error toasts.
debug boolean or object false Safe verbose logs (no secret exposure). Object form: { level, log_file, retain_days, max_bytes, tui_buffer_lines, redact_secrets }.
version_cache_ttl number 1 to 10080 60 Dynamic Antigravity version cache TTL in minutes.
endpoints object omitted Per-tier endpoint overrides: { prod, daily, sandbox, autopush }.

Minimal example:

{
  "cli_first": true,
  "google_search_enabled": "auto",
  "soft_quota_threshold_percent": 90,
  "rotation_strategy": "round-robin",
  "claude_tool_hardening": true,
  "keep_thinking": false
}

Validation: antigravity.schema.json is the strict standalone schema (Draft-07, $id https://opencode.ai/schemas/antigravity.json, additionalProperties: false). The in-code schema (config.ts ANTIGRAVITY_CONFIG_JSON_SCHEMA) is a third superset that unifies both files. The bypass identity itself (Gemini CLI client id, secret, UA, headers, fallback project) is shipped-frozen and is not user-editable.

Dual-bypass mechanism

The bypass has two faces that share the same JSON identification:

  1. Antigravity bypass (buildAntigravityRequest in plugin.ts and core.ts): wraps the OpenCode request as { project: "projects/<id>", model, request: { model, contents, tools, generationConfig } } and POSTs to https://cloudcode-pa.googleapis.com/v1internal:streamGenerateContent?alt=sse with antigravity-style headers (User-Agent: antigravity/<v> <os>/<arch>, Authorization: Bearer <token>). Cascade-fallbacks to daily-cloudcode-pa.googleapis.com then daily-cloudcode-pa.sandbox.googleapis.com (and autopush-cloudcode-pa.sandbox.googleapis.com) on 403, 404, or 5xx, with 0 to 80 ms jitter. Forbidden headers are stripped on every request: x-goog-user-project, x-goog-quota-user, x-client-device-id, x-goog-request-reason.

  2. Gemini CLI identification spoof (buildGeminiCLIRequest in plugin.ts and core.ts): same envelope shape, but headers carry the exact Gemini CLI identification (User-Agent: GeminiCLI/0.57.0, X-Goog-Api-Client: gl-node/22.19.0, Client-Metadata: ideType=IDE_UNSPECIFIED,platform=PLATFORM_UNSPECIFIED,pluginType=GEMINI). Used when the model is gemini-cli-only (gemini-3, gemini-3.1, gemini-3.5, gemini-3.6, gemini-3.7, preview, customtools variants) or when cli_first:true and the model is a Gemini. Sandbox endpoints are skipped for these models.

  3. Antigravity CLI client (identity bundle in the consolidated auth.ts, exposed via the antigravity-cli.ts shim; default OAuth identity for NEW logins since 2.1.8): maene authenticates with the exact OAuth client of the official Antigravity CLI (agy) — consent screen renders as "Google Antigravity", PKCE S256, scopes include aicode/cclog/experimentsandconfigs, and the login uses a loopback redirect Google accepts for this client, making the whole flow automatic (the official CLI shows a code to copy/paste; maene captures the authorization on 127.0.0.1 directly — no copy/paste ever). Request headers for these accounts: User-Agent: antigravity-cli/1.1.21 (<os>; <arch>), X-Goog-Api-Client: antigravity-cli/1.1.21 grpc-go/1.85.0.

All faces share the resolveProjectId cascade: if no project id is cached for the access token, call loadCodeAssist (metadata-only, no project field). If null, onboardUser with tierId: "FREE" (long-running op polled up to 5 times with backoff). Retry loadCodeAssist. If still null, fall back to rising-fact-p41fc. Project ids are cached in-memory keyed by a SHA-256-truncated access token, so a heap snapshot cannot leak the raw token. maene never asks the user for a GCP project.

Robin-hood rotation

Implemented in auth.ts accountManager.getNext() (with a lightweight InlineAccountManager.getNextAccount fallback in plugin.ts, and a richer AccountManager class in accounts.ts). The account store format is v3:

{
  "version": 3,
  "accounts": [ /* accountEntry objects */ ],
  "activeIndex": 0,
  "activeIndexByFamily": { "claude": 0, "gemini": 0 }
}

Each accountEntry carries: email, refreshToken, accessToken, expiry, createdAt, addedAt, lastUsed, projectId, managedProjectId, disabled, quotaExhaustedUntil, softQuotaUntil, remaining, limit, rateLimitResetTimes (a per-model reset-time map).

getNext(strategy, softThreshold=90) filters accounts by, in order:

  1. Not disabled.
  2. quotaExhaustedUntil expired.
  3. softQuotaUntil expired.
  4. All rateLimitResetTimes for the current model expired.
  5. Soft-quota filter — used percentage ((limit - remaining) / limit) * 100 < softThreshold (90 percent by default).

If the filtered pool is non-empty, it picks accounts[idx % pool.length] (round-robin) or the sticky account, advances idx, and persists activeIndex. If empty, it falls back to the not-rate-limited pool, then to the full active pool. setRateLimit(email, modelKey, resetMs) is called when cloudcode-pa returns a Retry-After or X-RateLimit-Reset. markExhausted (1 hour default) and markSoftQuota (15 minutes default, configurable via soft_quota_cache_ttl_minutes) implement the dual-quota cooldowns. The plugin's getAccountForModel further applies cli_first dual-quota routing: if the model is Gemini and cli_first:true, prefer a gemini-cli pool account so the Antigravity pool is preserved for Claude calls.

Quota is fetched in parallel from both Antigravity and Gemini CLI sources by quota.ts retrieveDualQuota. The result is cached through an adaptive TTL (2 to 30 minutes, based on fill ratio) backed by a file at ~/.config/opencode/cache/antigravity/quota.json (atomic chmod-0600 write). Soft threshold is 90 percent; hard threshold is 98 percent.

Project structure

Root-first flat layout (no /src). Since v2.1.15 the root follows a strict layer DAG — every logic family has exactly one owner file and synchronization between files is import dependency, never copy. Runtime imports point downward only (a lower layer never imports a higher one; import type may cross layers because it erases at runtime):

L0  constants.ts   raw shared values only (strings, numbers, host maps, pools)
L1  core.ts        generic pure utilities (hash, jitter, retry, ids, timeouts)
L1  models.ts      ALL model identification (catalogs, classifiers, routing)
L1  debug.ts       logging and redaction
L2  fingerprint.ts identity masquerade (user agents, client metadata, headers)
L2  version.ts     version detection
L2  config.ts      configuration management, validators, config file IO
L2  auth.ts        ALL authentication (oauth clients, scopes, urls, flows)
L3  accounts.ts    account store and rotation
L3  project.ts     project discovery (loadCodeAssist, onboardUser, projectId cache)
L4  request.ts     request building (transforms, builders, cascade, header stripping)
L4  streaming.ts   response streaming (sse parse, openai/anthropic transforms)
L4  recovery.ts    recovery (toast, error types, signature preservation)
L4  quota.ts       quota
L4  search.ts      search grounding
L5  plugin.ts / cli.ts / server.ts / index.ts   entry orchestration only
shim system.ts, antigravity-cli.ts              published-subpath re-exports
Path Role
plugin.ts L5 plugin entry, orchestration only (default export, fetch hook, dual-bypass, account selection — every duplicated family imported from its owner).
cli.ts L5 bin entry (maene), subcommand dispatch.
server.ts L5 ./server package entry: the three auth registrations for opencode auth login (google, antigravity, detected provider).
antigravity-cli.ts compatibility shim over auth.ts (published ./antigravity-cli subpath).
index.ts L5 ESM barrel (namespace re-exports incl. compat aliases, default plugin, named constants).
config.ts L2 THE configuration module (v2.1.15: config + validate + system — owns validators and config-file IO).
constants.ts L0 THE raw-value owner (v2.1.15: every cross-domain string/number/map lives here once — base URLs, ENDPOINTS host map, user agents, timeouts, version pools).
models.ts L1 THE model-identification owner (v2.1.15: catalogs, classifiers, thinking tables, endpoint routing orders — model names never appear in any other file).
accounts.ts L3 THE account-store owner (v3 AccountManager, store I/O, rotation, Account type).
auth.ts L2 THE authentication module (v2.1.15: every OAuth client identity, PKCE flow, callback server, token refresh, userinfo — the gemini client pair declared once as GEMINI_CLI_OAUTH_CLIENT_ID/SECRET).
core.ts L1 THE generic-utility leaf (v2.1.15: fnv hashes, jitter, fetchWithTimeout, retry classifiers, id generators, LRU thinking cache — every legacy compact duplicate deleted).
debug.ts L1 THE logging owner (production logger, 10 MB rotation, 7-day retention, secret redaction).
fingerprint.ts L2 THE identity-masquerade owner (v2.1.15: getDynamicUserAgent, getXGoogApiClient, buildClientMetadata, buildGeminiUserAgent, gemini/antigravity header builders, weighted platform pools).
project.ts L3 THE project-discovery owner (v2.1.15: loadCodeAssist, onboardUser, projectId cache, resolveProjectId).
quota.ts L4 THE quota owner (dual-source manager, adaptive TTL cache, runtime QuotaConfig).
recovery.ts L4 THE recovery owner (toast, error detectors, signature preservation, retry wrapper).
request.ts L4 THE request-building owner (v2.1.15: the LIVE opencode pipeline — transforms, dual builders, fetchWithCascade, tool sanitizers, API_PATHS).
search.ts L4 THE search-grounding owner (googleSearch, urlContext, image-gen config, citations).
streaming.ts L4 THE streaming owner (tolerant SSE parser, OpenAI/Anthropic streaming transformers, replay cache).
system.ts compatibility shim over config.ts (published ./system subpath).
version.ts L2 THE version-detection owner (dynamic resolver: legacy plus observer chains).
antigravity.json Dual-purpose schema plus bypass manifest.
antigravity.schema.json Strict standalone JSON Schema (Draft-07).
tests/ node:test plus node:assert suites (constants, config validators, system shim, auth PKCE, fingerprint FNV-1a-32, models 9router mapping).
docs/ Reference docs: README, RESEARCH, WAVES, MODELS, BYPASS, FLUXOGRAMA, CHECKLIST.
.github/workflows/ The eight release/CI pipeline definitions (see Releases and CI).
SECURITY.md Vulnerability reporting policy for the maintained 2.x line.

Development

bun install                       # install dev deps
bun run typecheck                 # tsc --noEmit --skipLibCheck
bun run lint                      # biome check .
bun run lint:fix                  # biome check . --write
bun run format                    # biome format . --write
bun run build                     # tsup plugin.ts constants.ts --format esm,cjs --clean --splitting=false
bun run pack:check                # npm pack --dry-run --json (verify published file list)
bun test                          # node --test tests/

The CI workflow runs bun install --frozen-lockfile, bun run typecheck, bun run lint, bun test, bun run build plus npm pack --dry-run on every push and pull request. Node 26.7.0 is the CI runtime; bun 1.4.0 is the package manager.

Releases and CI

Current release: 2.1.9 (26 Aug 2026). See CHANGELOG.md for the full history.

GitHub Actions definitions live directly under .github/workflows/ — the e2ugh layout contract: no nested .github/actions composite folder, every pipeline is a single self-contained workflow file (the workflowlint.yml gate asserts the contract). All the build, pack and publish computation happens on the GitHub Actions runners.

  • ci.yml — five gates on every push to main and every pull_request: Biome 2.5.10 lint, TypeScript 7.0.2 typecheck (tsc --noEmit), the node:test suite through bun, the tsup production bundle plus npm pack --dry-run validation, and strict JSON validation of every tracked document. Concurrency cancels superseded runs on the same ref.
  • release.yml — the reference release scheme: a metadata job resolves the version tag (creating it automatically when a push to main bumps package.json), verifies the index.ts VERSION export and the CHANGELOG.md section agree with the release, a release-artifacts job builds a maximum-compression zip -9 source archive of the tagged tree plus SHA-256 checksums, and the github-release job publishes the release through softprops/action-gh-release@v3.0.2 with the curated notes extracted from CHANGELOG.md and auto-generated commit listing.
  • publishnpmjs.yml — publishes @wenathlan/maene to npmjs.com. Fires on release: [published], on the workflow_run completion of the Release workflow (the GITHUB_TOKEN event-suppression escape hatch) and on workflow_dispatch with a tag input. A resolve job validates the tag against the on-disk package.json and the GitHub release list (untrusted-input hardening: event data is never interpolated into checkout refs); the publish job runs typecheck, lint, tests and npm pack --dry-run inline, normalizes the NPM_TOKEN secret (strips CR/LF), runs npm whoami, and publishes with skip-if-version-exists. When the secret is absent the job skips with a notice instead of failing the pipeline.
  • publishgithubnpm.yml — the same two-job scheme publishing @wenathlan/maene to the GitHub npm registry (npm.pkg.github.com) with the job token (packages: write), scope-aligned to the repository owner, with skip-if-version-exists against the GitHub registry.
  • cachecleanup.yml — aggressive cache retention on a twice-daily schedule (03:17 and 15:17 UTC) plus after the Security workflow: exactly one most-recently-accessed entry per cache family (bun, codeql) on main, everything deleted on non-default refs. Manual dispatch runs in dry-run mode unless apply=true is passed.
  • workflowlint.yml — actionlint syntax and policy lint via reviewdog/action-actionlint@v1.73.2 on every workflow change, plus the layout assertion that .github/actions/ does not exist.
  • security.yml — CodeQL analysis (javascript-typescript and actions languages), dependency-review on pull requests, TruffleHog secret scan of the full git history with --results=verified, and the OSSF Scorecard supply-chain analysis published to the public API. Runs on pushes, PRs and weekly.
  • securitypolicy.yml — asserts SECURITY.md documents the supported 2.x line in lockstep with the shipped package version.

Required secret: NPM_TOKEN — an automation-scoped npm access token with publish rights on @wenathlan/maene. Configure under Settings, Secrets and variables, Actions. The GitHub Packages publication needs no secret (it uses the job token).

To publish manually (skip the release workflow):

npm publish --access public                     # npmjs.com
npm publish --registry=https://npm.pkg.github.com --access public  # GitHub Packages

Contributing

  1. Fork the repository at https://github.com/wenathlan/maene.
  2. Create a feature branch from main.
  3. bun install, write code, add tests under tests/, run bun test.
  4. bun run lint and bun run typecheck must pass.
  5. Open a pull request against main. Conventional commits preferred (feat:, fix:, docs:, chore:, refactor:, test:).

License

MIT — see LICENSE.

Links

  • npm: https://www.npmjs.com/package/@wenathlan/maene
  • GitHub: https://github.com/wenathlan/maene
  • OpenCode: https://github.com/opencode-ai

About

Antrigravity puglin

Resources

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages