Skip to content

Commit 708dd4d

Browse files
feat: add Antigravity IDE module (#558)
## Description <!-- Briefly describe what this PR does and why --> Adds a module to open coder workspaces in Antigravity. ## Type of Change - [X] New module - [ ] New template - [ ] Bug fix - [ ] Feature/enhancement - [ ] Documentation - [ ] Other ## Module Information <!-- Delete this section if not applicable --> **Path:** `registry/coder/modules/antigravity` **New version:** `v1.0.0` **Breaking change:** [ ] Yes [ ] No ## Testing & Validation - [ ] Tests pass (`bun test`) - [ ] Code formatted (`bun fmt`) - [ ] Changes tested locally ## Related Issues <!-- Link related issues or write "None" if not applicable --> --------- Co-authored-by: Atif Ali <[email protected]>
1 parent b143b7d commit 708dd4d

File tree

3 files changed

+301
-0
lines changed

3 files changed

+301
-0
lines changed
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
---
2+
display_name: Antigravity
3+
description: Add a one-click button to launch Google Antigravity
4+
icon: ../../../../.icons/antigravity.svg
5+
verified: true
6+
tags: [ide, antigravity, ai, google]
7+
---
8+
9+
# Antigravity IDE
10+
11+
Add a button to open any workspace with a single click in [Antigravity IDE](https://antigravity.google).
12+
13+
Uses the [Coder Remote VS Code Extension](https://github.com/coder/vscode-coder).
14+
15+
```tf
16+
module "antigravity" {
17+
count = data.coder_workspace.me.start_count
18+
source = "registry.coder.com/coder/antigravity/coder"
19+
version = "1.0.0"
20+
agent_id = coder_agent.example.id
21+
}
22+
```
23+
24+
## Examples
25+
26+
### Open in a specific directory
27+
28+
```tf
29+
module "antigravity" {
30+
count = data.coder_workspace.me.start_count
31+
source = "registry.coder.com/coder/antigravity/coder"
32+
version = "1.0.0"
33+
agent_id = coder_agent.example.id
34+
folder = "/home/coder/project"
35+
}
36+
```
37+
38+
### Configure MCP servers for Antigravity
39+
40+
Provide a JSON-encoded string via the `mcp` input. When set, the module writes the value to `~/.gemini/antigravity/mcp_config.json` using a `coder_script` on workspace start.
41+
42+
The following example configures Antigravity to use the GitHub MCP server with authentication facilitated by the [`coder_external_auth`](https://coder.com/docs/admin/external-auth#configure-a-github-oauth-app) resource.
43+
44+
```tf
45+
module "antigravity" {
46+
count = data.coder_workspace.me.start_count
47+
source = "registry.coder.com/coder/antigravity/coder"
48+
version = "1.0.0"
49+
agent_id = coder_agent.example.id
50+
folder = "/home/coder/project"
51+
mcp = jsonencode({
52+
mcpServers = {
53+
"github" : {
54+
"url" : "https://api.githubcopilot.com/mcp/",
55+
"headers" : {
56+
"Authorization" : "Bearer ${data.coder_external_auth.github.access_token}",
57+
},
58+
"type" : "http"
59+
}
60+
}
61+
})
62+
}
63+
64+
data "coder_external_auth" "github" {
65+
id = "github"
66+
}
67+
```
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
import { describe, it, expect } from "bun:test";
2+
import {
3+
runTerraformApply,
4+
runTerraformInit,
5+
testRequiredVariables,
6+
runContainer,
7+
execContainer,
8+
removeContainer,
9+
findResourceInstance,
10+
readFileContainer,
11+
} from "~test";
12+
13+
describe("antigravity", async () => {
14+
await runTerraformInit(import.meta.dir);
15+
16+
testRequiredVariables(import.meta.dir, {
17+
agent_id: "foo",
18+
});
19+
20+
it("default output", async () => {
21+
const state = await runTerraformApply(import.meta.dir, {
22+
agent_id: "foo",
23+
});
24+
expect(state.outputs.antigravity_url.value).toBe(
25+
"antigravity://coder.coder-remote/open?owner=default&workspace=default&url=https://mydeployment.coder.com&token=$SESSION_TOKEN",
26+
);
27+
28+
const coder_app = state.resources.find(
29+
(res) =>
30+
res.type === "coder_app" &&
31+
res.module === "module.vscode-desktop-core" &&
32+
res.name === "vscode-desktop",
33+
);
34+
35+
expect(coder_app).not.toBeNull();
36+
expect(coder_app?.instances.length).toBe(1);
37+
expect(coder_app?.instances[0].attributes.order).toBeNull();
38+
});
39+
40+
it("adds folder", async () => {
41+
const state = await runTerraformApply(import.meta.dir, {
42+
agent_id: "foo",
43+
folder: "/foo/bar",
44+
});
45+
expect(state.outputs.antigravity_url.value).toBe(
46+
"antigravity://coder.coder-remote/open?owner=default&workspace=default&folder=/foo/bar&url=https://mydeployment.coder.com&token=$SESSION_TOKEN",
47+
);
48+
});
49+
50+
it("adds folder and open_recent", async () => {
51+
const state = await runTerraformApply(import.meta.dir, {
52+
agent_id: "foo",
53+
folder: "/foo/bar",
54+
open_recent: "true",
55+
});
56+
expect(state.outputs.antigravity_url.value).toBe(
57+
"antigravity://coder.coder-remote/open?owner=default&workspace=default&folder=/foo/bar&openRecent&url=https://mydeployment.coder.com&token=$SESSION_TOKEN",
58+
);
59+
});
60+
61+
it("adds folder but not open_recent", async () => {
62+
const state = await runTerraformApply(import.meta.dir, {
63+
agent_id: "foo",
64+
folder: "/foo/bar",
65+
openRecent: "false",
66+
});
67+
expect(state.outputs.antigravity_url.value).toBe(
68+
"antigravity://coder.coder-remote/open?owner=default&workspace=default&folder=/foo/bar&url=https://mydeployment.coder.com&token=$SESSION_TOKEN",
69+
);
70+
});
71+
72+
it("adds open_recent", async () => {
73+
const state = await runTerraformApply(import.meta.dir, {
74+
agent_id: "foo",
75+
open_recent: "true",
76+
});
77+
expect(state.outputs.antigravity_url.value).toBe(
78+
"antigravity://coder.coder-remote/open?owner=default&workspace=default&openRecent&url=https://mydeployment.coder.com&token=$SESSION_TOKEN",
79+
);
80+
});
81+
82+
it("expect order to be set", async () => {
83+
const state = await runTerraformApply(import.meta.dir, {
84+
agent_id: "foo",
85+
order: "22",
86+
});
87+
88+
const coder_app = state.resources.find(
89+
(res) =>
90+
res.type === "coder_app" &&
91+
res.module === "module.vscode-desktop-core" &&
92+
res.name === "vscode-desktop",
93+
);
94+
95+
expect(coder_app).not.toBeNull();
96+
expect(coder_app?.instances.length).toBe(1);
97+
expect(coder_app?.instances[0].attributes.order).toBe(22);
98+
});
99+
100+
it("writes ~/.gemini/antigravity/mcp_config.json when mcp provided", async () => {
101+
const id = await runContainer("alpine");
102+
try {
103+
const mcp = JSON.stringify({
104+
servers: { demo: { url: "http://localhost:1234" } },
105+
});
106+
const state = await runTerraformApply(import.meta.dir, {
107+
agent_id: "foo",
108+
mcp,
109+
});
110+
const script = findResourceInstance(
111+
state,
112+
"coder_script",
113+
"antigravity_mcp",
114+
).script;
115+
const resp = await execContainer(id, ["sh", "-c", script]);
116+
if (resp.exitCode !== 0) {
117+
console.log(resp.stdout);
118+
console.log(resp.stderr);
119+
}
120+
expect(resp.exitCode).toBe(0);
121+
const content = await readFileContainer(
122+
id,
123+
"/root/.gemini/antigravity/mcp_config.json",
124+
);
125+
expect(content).toBe(mcp);
126+
} finally {
127+
await removeContainer(id);
128+
}
129+
}, 10000);
130+
});
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
terraform {
2+
required_version = ">= 1.0"
3+
4+
required_providers {
5+
coder = {
6+
source = "coder/coder"
7+
version = ">= 2.5"
8+
}
9+
}
10+
}
11+
12+
variable "agent_id" {
13+
type = string
14+
description = "The ID of a Coder agent."
15+
}
16+
17+
variable "folder" {
18+
type = string
19+
description = "The folder to open in Antigravity IDE."
20+
default = ""
21+
}
22+
23+
variable "open_recent" {
24+
type = bool
25+
description = "Open the most recent workspace or folder. Falls back to the folder if there is no recent workspace or folder to open."
26+
default = false
27+
}
28+
29+
variable "order" {
30+
type = number
31+
description = "The order determines the position of app in the UI presentation. The lowest order is shown first and apps with equal order are sorted by name (ascending order)."
32+
default = null
33+
}
34+
35+
variable "group" {
36+
type = string
37+
description = "The name of a group that this app belongs to."
38+
default = null
39+
}
40+
41+
variable "slug" {
42+
type = string
43+
description = "The slug of the app."
44+
default = "antigravity"
45+
}
46+
47+
variable "display_name" {
48+
type = string
49+
description = "The display name of the app."
50+
default = "Antigravity IDE"
51+
}
52+
53+
variable "mcp" {
54+
type = string
55+
description = "JSON-encoded string to configure MCP servers for Antigravity. When set, writes ~/.gemini/antigravity/mcp_config.json."
56+
default = ""
57+
}
58+
59+
data "coder_workspace" "me" {}
60+
61+
data "coder_workspace_owner" "me" {}
62+
63+
locals {
64+
mcp_b64 = var.mcp != "" ? base64encode(var.mcp) : ""
65+
}
66+
67+
module "vscode-desktop-core" {
68+
source = "registry.coder.com/coder/vscode-desktop-core/coder"
69+
version = "1.0.1"
70+
71+
agent_id = var.agent_id
72+
73+
web_app_icon = "/icon/antigravity.svg"
74+
web_app_slug = var.slug
75+
web_app_display_name = var.display_name
76+
web_app_order = var.order
77+
web_app_group = var.group
78+
79+
folder = var.folder
80+
open_recent = var.open_recent
81+
protocol = "antigravity"
82+
}
83+
84+
resource "coder_script" "antigravity_mcp" {
85+
count = var.mcp != "" ? 1 : 0
86+
agent_id = var.agent_id
87+
display_name = "Antigravity MCP"
88+
icon = "/icon/antigravity.svg"
89+
run_on_start = true
90+
start_blocks_login = false
91+
script = <<-EOT
92+
#!/bin/sh
93+
set -eu
94+
mkdir -p "$HOME/.gemini/antigravity"
95+
echo -n "${local.mcp_b64}" | base64 -d > "$HOME/.gemini/antigravity/mcp_config.json"
96+
chmod 600 "$HOME/.gemini/antigravity/mcp_config.json"
97+
EOT
98+
}
99+
100+
output "antigravity_url" {
101+
value = module.vscode-desktop-core.ide_uri
102+
description = "Antigravity IDE URL."
103+
}
104+

0 commit comments

Comments
 (0)