Skip to content

Commit 43d8483

Browse files
feat: add Antigravity IDE module with Terraform configuration and tests
1 parent 73a92be commit 43d8483

File tree

3 files changed

+300
-0
lines changed

3 files changed

+300
-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 IDE
3+
description: Add a one-click button to launch Antigravity IDE
4+
icon: ../../../../.icons/antigravity.svg
5+
verified: false
6+
tags: [ide, antigravity, ai]
7+
---
8+
9+
# Antigravity IDE
10+
11+
Add a button to open any workspace with a single click in Antigravity IDE.
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 `~/.antigravity/mcp.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: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
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) => res.type === "coder_app" && res.name === "antigravity",
30+
);
31+
32+
expect(coder_app).not.toBeNull();
33+
expect(coder_app?.instances.length).toBe(1);
34+
expect(coder_app?.instances[0].attributes.order).toBeNull();
35+
});
36+
37+
it("adds folder", async () => {
38+
const state = await runTerraformApply(import.meta.dir, {
39+
agent_id: "foo",
40+
folder: "/foo/bar",
41+
});
42+
expect(state.outputs.antigravity_url.value).toBe(
43+
"antigravity://coder.coder-remote/open?owner=default&workspace=default&folder=/foo/bar&url=https://mydeployment.coder.com&token=$SESSION_TOKEN",
44+
);
45+
});
46+
47+
it("adds folder and open_recent", async () => {
48+
const state = await runTerraformApply(import.meta.dir, {
49+
agent_id: "foo",
50+
folder: "/foo/bar",
51+
open_recent: "true",
52+
});
53+
expect(state.outputs.antigravity_url.value).toBe(
54+
"antigravity://coder.coder-remote/open?owner=default&workspace=default&folder=/foo/bar&openRecent&url=https://mydeployment.coder.com&token=$SESSION_TOKEN",
55+
);
56+
});
57+
58+
it("adds folder but not open_recent", async () => {
59+
const state = await runTerraformApply(import.meta.dir, {
60+
agent_id: "foo",
61+
folder: "/foo/bar",
62+
openRecent: "false",
63+
});
64+
expect(state.outputs.antigravity_url.value).toBe(
65+
"antigravity://coder.coder-remote/open?owner=default&workspace=default&folder=/foo/bar&url=https://mydeployment.coder.com&token=$SESSION_TOKEN",
66+
);
67+
});
68+
69+
it("adds open_recent", async () => {
70+
const state = await runTerraformApply(import.meta.dir, {
71+
agent_id: "foo",
72+
open_recent: "true",
73+
});
74+
expect(state.outputs.antigravity_url.value).toBe(
75+
"antigravity://coder.coder-remote/open?owner=default&workspace=default&openRecent&url=https://mydeployment.coder.com&token=$SESSION_TOKEN",
76+
);
77+
});
78+
79+
it("expect order to be set", async () => {
80+
const state = await runTerraformApply(import.meta.dir, {
81+
agent_id: "foo",
82+
order: "22",
83+
});
84+
85+
const coder_app = state.resources.find(
86+
(res) => res.type === "coder_app" && res.name === "antigravity",
87+
);
88+
89+
expect(coder_app).not.toBeNull();
90+
expect(coder_app?.instances.length).toBe(1);
91+
expect(coder_app?.instances[0].attributes.order).toBe(22);
92+
});
93+
94+
it("writes ~/.antigravity/mcp.json when mcp provided", async () => {
95+
const id = await runContainer("alpine");
96+
try {
97+
const mcp = JSON.stringify({
98+
servers: { demo: { url: "http://localhost:1234" } },
99+
});
100+
const state = await runTerraformApply(import.meta.dir, {
101+
agent_id: "foo",
102+
mcp,
103+
});
104+
const script = findResourceInstance(
105+
state,
106+
"coder_script",
107+
"antigravity_mcp",
108+
).script;
109+
const resp = await execContainer(id, ["sh", "-c", script]);
110+
if (resp.exitCode !== 0) {
111+
console.log(resp.stdout);
112+
console.log(resp.stderr);
113+
}
114+
expect(resp.exitCode).toBe(0);
115+
const content = await readFileContainer(
116+
id,
117+
"/root/.antigravity/mcp.json",
118+
);
119+
expect(content).toBe(mcp);
120+
} finally {
121+
await removeContainer(id);
122+
}
123+
});
124+
});
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
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 ~/.antigravity/mcp.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+
resource "coder_app" "antigravity" {
68+
agent_id = var.agent_id
69+
external = true
70+
icon = "/icon/antigravity.svg"
71+
slug = var.slug
72+
display_name = var.display_name
73+
order = var.order
74+
group = var.group
75+
url = join("", [
76+
"antigravity://coder.coder-remote/open",
77+
"?owner=",
78+
data.coder_workspace_owner.me.name,
79+
"&workspace=",
80+
data.coder_workspace.me.name,
81+
var.folder != "" ? join("", ["&folder=", var.folder]) : "",
82+
var.open_recent ? "&openRecent" : "",
83+
"&url=",
84+
data.coder_workspace.me.access_url,
85+
"&token=$SESSION_TOKEN",
86+
])
87+
}
88+
89+
resource "coder_script" "antigravity_mcp" {
90+
count = var.mcp != "" ? 1 : 0
91+
agent_id = var.agent_id
92+
display_name = "Antigravity MCP"
93+
icon = "/icon/antigravity.svg"
94+
run_on_start = true
95+
start_blocks_login = false
96+
script = <<-EOT
97+
#!/bin/sh
98+
set -eu
99+
mkdir -p "$HOME/.antigravity"
100+
echo -n "${local.mcp_b64}" | base64 -d > "$HOME/.antigravity/mcp.json"
101+
chmod 600 "$HOME/.antigravity/mcp.json"
102+
EOT
103+
}
104+
105+
output "antigravity_url" {
106+
value = coder_app.antigravity.url
107+
description = "Antigravity IDE URL."
108+
}
109+

0 commit comments

Comments
 (0)