Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
e7de187
feat(providers): discover Antigravity account models
sadiksaifi Aug 17, 2026
efd58ae
feat(providers): rank Antigravity models
sadiksaifi Aug 17, 2026
4b74f1a
feat(config): map legacy Gemini model intent
sadiksaifi Aug 17, 2026
127096d
feat(providers): isolate Antigravity generation
sadiksaifi Aug 17, 2026
a55b8ea
feat(providers): register Antigravity CLI
sadiksaifi Aug 17, 2026
b810611
feat(meta): publish Antigravity provider metadata
sadiksaifi Aug 17, 2026
1e23cc2
feat(configure): recommend Antigravity account model
sadiksaifi Aug 17, 2026
6abcfd9
feat(config): migrate Gemini CLI configurations
sadiksaifi Aug 17, 2026
23eca4d
refactor(providers): remove Gemini CLI implementation
sadiksaifi Aug 17, 2026
df563fb
feat(providers): verify Antigravity isolation before generation
sadiksaifi Aug 17, 2026
c0978f6
feat(providers): preserve file-backed Antigravity auth
sadiksaifi Aug 17, 2026
20585cc
fix(providers): honor Antigravity model fallback policy
sadiksaifi Aug 17, 2026
af4d16d
test(providers): cover Antigravity failures and migration
sadiksaifi Aug 17, 2026
41fc304
docs: replace Gemini CLI with Antigravity
sadiksaifi Aug 17, 2026
3503214
fix(providers): probe Antigravity isolation without history
sadiksaifi Aug 17, 2026
cf888ff
fix(providers): validate Antigravity model before generation
sadiksaifi Aug 17, 2026
4d7ce93
fix(providers): clean isolated profiles on Windows
sadiksaifi Aug 18, 2026
c29fe20
fix(providers): preserve Antigravity API-key auth
sadiksaifi Aug 18, 2026
e3b0ddb
fix(config): migrate only the effective legacy config
sadiksaifi Aug 18, 2026
72bd422
fix(config): surface Antigravity migration failures
sadiksaifi Aug 18, 2026
d2fc28f
fix(providers): time out stalled Antigravity processes
sadiksaifi Aug 18, 2026
d492796
fix(config): persist legacy project migrations
sadiksaifi Aug 18, 2026
054e72d
fix(providers): clean partial Antigravity profiles
sadiksaifi Aug 18, 2026
40ad237
fix(providers): preserve Antigravity auth selection
sadiksaifi Aug 18, 2026
8bcdf6e
fix(config): migrate partial legacy overrides
sadiksaifi Aug 18, 2026
1d50087
fix(providers): await Antigravity termination
sadiksaifi Aug 18, 2026
fd46ce7
fix(schema): preserve Antigravity examples
sadiksaifi Aug 18, 2026
92f9f03
chore: merge latest main
sadiksaifi Aug 19, 2026
6c795ce
style(cli): format spinner actor
sadiksaifi Aug 19, 2026
a29695a
fix(providers): preserve Antigravity fallback order
sadiksaifi Aug 19, 2026
577344b
fix(providers): bound Antigravity termination
sadiksaifi Aug 19, 2026
f3eca74
test(providers): add live Antigravity isolation smoke
sadiksaifi Aug 19, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 24 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ A CLI tool that leverages AI to automatically generate semantically correct, con
- 🤖 **AI-Powered** - Analyzes diffs and understands the _intent_ of your changes
- 📝 **Conventional Commits** - Strictly adheres to [v1.0.0](https://www.conventionalcommits.org/en/v1.0.0/) specification
- 🎨 **Interactive TUI** - Beautiful prompts for staging, editing, and confirming
- 🔌 **Multiple Providers** - Claude Code, Gemini CLI, Codex, OpenCode, Pi, OpenRouter, OpenAI, Anthropic, Google AI Studio, Cerebras
- 🔌 **Multiple Providers** - Claude Code, Antigravity CLI, Codex, OpenCode, Pi, OpenRouter, OpenAI, Anthropic, Google AI Studio, Cerebras
- 🔐 **Secure** - API keys stored in keychain, never in config files

## Installation
Expand Down Expand Up @@ -98,7 +98,7 @@ Info:
ai-git

# Override provider for this run
ai-git --provider gemini-cli --model gemini-3-flash-preview
ai-git --provider antigravity-cli --model gemini-3.7-flash-low

# Use Codex with reasoning effort baked in
ai-git --provider codex --model gpt-5.6-luna-low
Expand All @@ -124,25 +124,32 @@ ai-git --dry-run -A

## Supported Providers

| Provider | ID | Type | Requirements |
| :--------------- | :----------------- | :--- | :-------------------------------------------------------------------------------------- |
| Claude Code | `claude-code` | CLI | [Install CLI](https://claude.com/claude-code) |
| Gemini CLI | `gemini-cli` | CLI | [Install CLI](https://ai.google.dev/gemini-api/docs/cli) |
| Codex | `codex` | CLI | [Install CLI](https://developers.openai.com/codex/cli) |
| OpenCode | `opencode` | CLI | [Install CLI](https://opencode.ai) |
| Pi | `pi` | CLI | [Install CLI](https://pi.dev) |
| OpenRouter | `openrouter` | API | [Get API Key](https://openrouter.ai/keys) |
| OpenAI | `openai` | API | [Get API Key](https://platform.openai.com/api-keys) |
| Google AI Studio | `google-ai-studio` | API | [Get API Key](https://aistudio.google.com/app/apikey) |
| Anthropic | `anthropic` | API | [Get API Key](https://console.anthropic.com/settings/keys) |
| Cerebras | `cerebras` | API | [Get API Key](https://cloud.cerebras.ai/) |
| Provider | ID | Type | Requirements |
| :--------------- | :------------------ | :--- | :-------------------------------------------------------------------------------------- |
| Claude Code | `claude-code` | CLI | [Install CLI](https://claude.com/claude-code) |
| Antigravity CLI | `antigravity-cli` | CLI | [Install CLI](https://antigravity.google/docs/cli/install) |
| Codex | `codex` | CLI | [Install CLI](https://developers.openai.com/codex/cli) |
| OpenCode | `opencode` | CLI | [Install CLI](https://opencode.ai) |
| Pi | `pi` | CLI | [Install CLI](https://pi.dev) |
| OpenRouter | `openrouter` | API | [Get API Key](https://openrouter.ai/keys) |
| OpenAI | `openai` | API | [Get API Key](https://platform.openai.com/api-keys) |
| Google AI Studio | `google-ai-studio` | API | [Get API Key](https://aistudio.google.com/app/apikey) |
| Anthropic | `anthropic` | API | [Get API Key](https://console.anthropic.com/settings/keys) |
| Cerebras | `cerebras` | API | [Get API Key](https://cloud.cerebras.ai/) |

Configure with `ai-git configure`

Codex defaults to `gpt-5.6-luna-low` and Claude Code defaults to `haiku` for fast,
repeatable commit-message generation. Pi, OpenCode, and API provider models are discovered at
runtime. Retired Codex configurations are migrated to the corresponding GPT-5.6 family while
preserving their supported effort level.
repeatable commit-message generation. Antigravity CLI, Pi, OpenCode, and API provider models are
discovered at runtime. Antigravity recommends the newest Gemini Flash Low model available to the
signed-in account. Existing Gemini CLI configurations are migrated to Antigravity CLI with a live,
account-compatible model. Retired Codex configurations are migrated to the corresponding GPT-5.6
family while preserving their supported effort level.

Antigravity generation runs with `--sandbox` in an empty workspace and an isolated temporary
profile. AI Git installs deny-all permissions and a deny-all tool hook for the invocation, disables
slash-command expansion, validates structured output, and removes the temporary conversation data
afterward.

## Configuration

Expand Down
2 changes: 1 addition & 1 deletion apps/cli/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ Reusable `fromPromise()` actors with factory pattern for test dependency injecti

Two modes with unified adapter interface:

- **CLI Mode** (`cli/`): Spawns installed binaries (`claude`, `gemini`, `codex`)
- **CLI Mode** (`cli/`): Spawns installed binaries (`claude`, `agy`, `codex`, `opencode`, `pi`)
- **API Mode** (`api/`): HTTP APIs via Vercel AI SDK (OpenRouter, OpenAI, Anthropic, Google AI Studio)

Provider registry in `registry.ts` defines available providers and models.
Expand Down
39 changes: 39 additions & 0 deletions apps/cli/scripts/sync-schema-models.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { describe, expect, it } from "bun:test";
import { readFileSync } from "node:fs";
import { resolve } from "node:path";
import { updateSchemaModels } from "./sync-schema-models.ts";

describe("updateSchemaModels", () => {
it("preserves Antigravity examples and provider guidance", () => {
const schema = JSON.parse(
readFileSync(resolve(import.meta.dir, "../../../schema.json"), "utf8"),
);
const payload = {
openai: {
models: {
"gpt-5.6": { release_date: "2026-08-01", last_updated: "2026-08-01" },
},
},
google: {
models: {
"gemini-3.7-flash": { release_date: "2026-08-01", last_updated: "2026-08-01" },
},
},
anthropic: {
models: {
"claude-opus-5": { release_date: "2026-08-01", last_updated: "2026-08-01" },
},
},
};

updateSchemaModels(schema, payload);

expect(schema.properties.model.examples).toContain("gemini-3.7-flash-low");
const antigravitySchema = schema.allOf.find(
(entry: any) => entry.if?.properties?.provider?.const === "antigravity-cli",
);
expect(antigravitySchema.then.properties.model.description).toContain(
"Antigravity CLI model ID",
);
});
});
44 changes: 32 additions & 12 deletions apps/cli/scripts/sync-schema-models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ import {
import { PROVIDERS } from "../src/providers/registry.ts";

const MODELS_DEV_API_URL = "https://models.dev/api.json";
const DYNAMIC_CLI_EXAMPLES: Record<string, string> = {
"antigravity-cli": "gemini-3.7-flash-low",
};

type ModelsDevStatus = "alpha" | "beta" | "deprecated";

Expand Down Expand Up @@ -133,6 +136,7 @@ function updateProviderDescription(
if (providerId === "openai") providerLabel = "OpenAI";
if (providerId === "google-ai-studio") providerLabel = "Google AI Studio";
if (providerId === "anthropic") providerLabel = "Anthropic API";
if (providerId === "antigravity-cli") providerLabel = "Antigravity CLI";
Comment thread
sadiksaifi marked this conversation as resolved.

modelSchema.description = `${providerLabel} model ID (e.g., '${example}'). Models are fetched dynamically.`;

Expand All @@ -146,7 +150,6 @@ function updateStaticCLIModels(schema: Record<string, unknown>): void {
"Claude Code virtual model ID. Fable, Sonnet, and Opus support low, medium, high, xhigh, and max. Haiku has no effort level and is recommended for this latency-sensitive workload.",
codex:
"Codex CLI virtual model ID: '<base-model>-<effort>'. Supported effort levels depend on the model and include low, medium, high, xhigh, and max. Default: 'gpt-5.6-luna-low'. Ultra is not a model effort and is not supported.",
"gemini-cli": "Gemini CLI model to use. 'gemini-3-flash-preview' is recommended for speed.",
};

for (const provider of PROVIDERS.filter(
Expand All @@ -164,17 +167,20 @@ function updateStaticCLIModels(schema: Record<string, unknown>): void {
}
}

async function main(): Promise<void> {
const payload = await fetchModelsDev();

export function updateSchemaModels(
current: Record<string, any>,
payload: ModelsDevPayload,
): {
openRouterExample: string;
openAIExample: string;
googleExample: string;
anthropicExample: string;
} {
const openAIExample = pickProviderExample("openai", payload);
const googleExample = pickProviderExample("google", payload);
const anthropicExample = pickProviderExample("anthropic", payload);
const openRouterExample = `openai/${openAIExample}`;

const schemaPath = path.resolve(import.meta.dir, "../../../schema.json");
const current = JSON.parse(await readFile(schemaPath, "utf8")) as Record<string, any>;

const staticCliExamples = PROVIDERS.filter(
(provider) => provider.mode === "cli" && !provider.dynamicModels,
).flatMap((provider) =>
Expand All @@ -183,6 +189,7 @@ async function main(): Promise<void> {

current.properties.model.examples = unique([
...staticCliExamples,
...Object.values(DYNAMIC_CLI_EXAMPLES),
"opencode/gpt-5-nano#minimal",
"openai-codex/gpt-5.6-luna#low",
openRouterExample,
Expand All @@ -195,15 +202,28 @@ async function main(): Promise<void> {
updateProviderDescription(current, "openai", openAIExample);
updateProviderDescription(current, "google-ai-studio", googleExample);
updateProviderDescription(current, "anthropic", anthropicExample);
for (const [providerId, example] of Object.entries(DYNAMIC_CLI_EXAMPLES)) {
updateProviderDescription(current, providerId, example);
}
updateStaticCLIModels(current);

return { openRouterExample, openAIExample, googleExample, anthropicExample };
}

async function main(): Promise<void> {
const payload = await fetchModelsDev();
const schemaPath = path.resolve(import.meta.dir, "../../../schema.json");
const current = JSON.parse(await readFile(schemaPath, "utf8")) as Record<string, any>;

const examples = updateSchemaModels(current, payload);

await writeFile(schemaPath, JSON.stringify(current, null, 2) + "\n", "utf8");

console.log(`Updated ${schemaPath}`);
console.log(`openrouter example: ${openRouterExample}`);
console.log(`openai example: ${openAIExample}`);
console.log(`google example: ${googleExample}`);
console.log(`anthropic example: ${anthropicExample}`);
console.log(`openrouter example: ${examples.openRouterExample}`);
console.log(`openai example: ${examples.openAIExample}`);
console.log(`google example: ${examples.googleExample}`);
console.log(`anthropic example: ${examples.anthropicExample}`);
}

await main();
if (import.meta.main) await main();
13 changes: 10 additions & 3 deletions apps/cli/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,10 @@ export async function getProjectConfigPath(): Promise<string> {
/** Pending migration notice to display after the welcome screen. */
let pendingMigrationNotice: MigrationNotice | null = null;

export function queueMigrationNotice(notice: MigrationNotice): void {
pendingMigrationNotice = notice;
}

/**
* Show any pending migration notice and clear it.
* Call this after the welcome screen renders so `console.clear()` doesn't wipe it.
Expand Down Expand Up @@ -296,16 +300,19 @@ export function mergePromptConfig(
*/
export async function resolveConfigAsync(
cliOptions: Partial<ResolvedConfig>,
loaded?: { userConfig?: UserConfig; projectConfig?: UserConfig },
): Promise<ResolvedConfig> {
const userConfig = await loadUserConfig();
const projectConfig = await loadProjectConfig();
const userConfig = loaded ? loaded.userConfig : await loadUserConfig();
const projectConfig = loaded ? loaded.projectConfig : await loadProjectConfig();
const hasCompleteCliOptions = Boolean(cliOptions.provider && cliOptions.model);

// Config file must exist and be valid (setup wizard ensures this)
// We check userConfig primarily, but if projectConfig exists and is complete, that's fine too.
// However, usually we expect at least a user config to exist after setup.
if (
(!userConfig || !isConfigComplete(userConfig)) &&
(!projectConfig || !isConfigComplete(projectConfig))
(!projectConfig || !isConfigComplete(projectConfig)) &&
!hasCompleteCliOptions
) {
throw new Error("Configuration is incomplete. Please run: ai-git configure");
}
Expand Down
Loading
Loading