supergravity-mcp 0.1.5 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +32 -0
- package/dist/antigravity-client.js +146 -12
- package/dist/mcp-server.js +69 -4
- package/package.json +1 -1
- package/skills/delegate/SKILL.md +36 -5
package/README.md
CHANGED
|
@@ -70,6 +70,38 @@ npm run build
|
|
|
70
70
|
(see `AVAILABLE_MODELS` in `src/antigravity-client.ts`).
|
|
71
71
|
- `timeoutMs` — optional, default 120000 (2 minutes).
|
|
72
72
|
|
|
73
|
+
If the task generates files (images, documents, etc.), the result also
|
|
74
|
+
includes a real filesystem path — Antigravity stores each conversation's
|
|
75
|
+
files at `~/.gemini/antigravity/brain/<conversation-id>/` on disk, confirmed
|
|
76
|
+
directly (not documented anywhere by Google). No need to fetch anything
|
|
77
|
+
through Antigravity's local server; just read the file.
|
|
78
|
+
|
|
79
|
+
### `delegate_to_antigravity_batch(tasks)`
|
|
80
|
+
|
|
81
|
+
Runs several independent tasks at once, each in its own Antigravity window —
|
|
82
|
+
genuinely concurrent, not queued. Confirmed with two simultaneous
|
|
83
|
+
~200-word generations that both completed correctly in the same ~16s window.
|
|
84
|
+
Opens extra windows automatically as needed (macOS's "New Window" menu
|
|
85
|
+
command via System Events, with a fallback for when zero windows are open).
|
|
86
|
+
|
|
87
|
+
Requires Accessibility permission for whatever process runs this (System
|
|
88
|
+
Settings > Privacy & Security > Accessibility) — macOS only. Don't run this
|
|
89
|
+
at the same time as a separate `delegate_to_antigravity` call; window
|
|
90
|
+
allocation between the two isn't coordinated.
|
|
91
|
+
|
|
92
|
+
```ts
|
|
93
|
+
delegateToAntigravityBatch([
|
|
94
|
+
{ task: "..." },
|
|
95
|
+
{ task: "...", model: "Claude Sonnet 4.6 (Thinking)" },
|
|
96
|
+
]);
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### `list_antigravity_files(limit?)`
|
|
100
|
+
|
|
101
|
+
Every file Antigravity has generated, across every conversation, most recent
|
|
102
|
+
first — not scoped to a single delegate call. Useful for finding something
|
|
103
|
+
generated earlier in this session or a past one.
|
|
104
|
+
|
|
73
105
|
### `get_antigravity_quota()`
|
|
74
106
|
|
|
75
107
|
Reads Antigravity's Settings > Models panel and returns remaining quota —
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { execSync, spawn } from "node:child_process";
|
|
1
|
+
import { execFileSync, execSync, spawn } from "node:child_process";
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import os from "node:os";
|
|
4
4
|
import path from "node:path";
|
|
@@ -44,26 +44,160 @@ function withLock(fn) {
|
|
|
44
44
|
}
|
|
45
45
|
export async function delegateToAntigravity(opts) {
|
|
46
46
|
return withLock(async () => {
|
|
47
|
-
const { task, model, timeoutMs = 120_000 } = opts;
|
|
48
47
|
await ensureAntigravityReady();
|
|
49
48
|
const target = await getMainPageTarget();
|
|
49
|
+
return runOnTarget(target, opts);
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Run several tasks at once, each in its own Antigravity window - confirmed
|
|
54
|
+
* these genuinely run concurrently (not just dispatched together then
|
|
55
|
+
* queued), verified with two simultaneous ~200-word generations that both
|
|
56
|
+
* completed correctly in the same ~16s window.
|
|
57
|
+
*
|
|
58
|
+
* macOS only: opening additional windows uses the "New Window" menu command
|
|
59
|
+
* via System Events, which requires Accessibility permission (System
|
|
60
|
+
* Settings > Privacy & Security > Accessibility) for whatever process is
|
|
61
|
+
* running this. Don't mix this with a concurrent single delegateToAntigravity
|
|
62
|
+
* call - window allocation between the two isn't coordinated, so they could
|
|
63
|
+
* both grab the same window.
|
|
64
|
+
*/
|
|
65
|
+
export async function delegateToAntigravityBatch(tasks) {
|
|
66
|
+
if (tasks.length === 0)
|
|
67
|
+
return [];
|
|
68
|
+
await ensureAntigravityReady();
|
|
69
|
+
const targets = await ensureWindowCount(tasks.length);
|
|
70
|
+
return Promise.all(tasks.map((opts, i) => runOnTarget(targets[i], opts)));
|
|
71
|
+
}
|
|
72
|
+
async function runOnTarget(target, opts) {
|
|
73
|
+
const { task, model, timeoutMs = 120_000 } = opts;
|
|
74
|
+
const session = new CdpSession(target.webSocketDebuggerUrl);
|
|
75
|
+
try {
|
|
76
|
+
// The CDP debug port opens before Antigravity's own UI finishes mounting
|
|
77
|
+
// (its window loads content from a local server that itself needs a
|
|
78
|
+
// moment to boot). On a cold start this can leave the chat input
|
|
79
|
+
// missing for several seconds after we've already connected.
|
|
80
|
+
await waitForChatReady(session);
|
|
81
|
+
await startNewConversation(session);
|
|
82
|
+
if (model)
|
|
83
|
+
await selectModel(session, model);
|
|
84
|
+
await pasteAndSend(session, task);
|
|
85
|
+
const reply = await waitForReply(session, task, timeoutMs);
|
|
86
|
+
const conversationId = await getConversationId(session);
|
|
87
|
+
const localFilesDir = conversationId ? resolveBrainDir(conversationId) : null;
|
|
88
|
+
return { reply, conversationId, localFilesDir };
|
|
89
|
+
}
|
|
90
|
+
finally {
|
|
91
|
+
session.close();
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
function openNewAntigravityWindow() {
|
|
95
|
+
try {
|
|
96
|
+
execFileSync("osascript", [
|
|
97
|
+
"-e",
|
|
98
|
+
'tell application "System Events" to tell process "Antigravity" to click menu item "New Window" of menu "File" of menu bar 1',
|
|
99
|
+
]);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
catch (err) {
|
|
103
|
+
// With zero windows open, System Events can't find "Antigravity" as an
|
|
104
|
+
// addressable process at all (confirmed directly - error -600, "app is
|
|
105
|
+
// not open" - even though the underlying process is genuinely running).
|
|
106
|
+
// macOS's normal app-reopen convention handles this: relaunching an
|
|
107
|
+
// already-running, windowless app creates a fresh window instead of
|
|
108
|
+
// just focusing an existing one.
|
|
109
|
+
try {
|
|
110
|
+
execFileSync("open", ["-a", resolveAppPath()]);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
throw new Error(`Could not open a new Antigravity window (needed to run tasks in parallel): ${err.message}. ` +
|
|
115
|
+
"If Antigravity has at least one window open, this needs Accessibility permission - grant it in " +
|
|
116
|
+
"System Settings > Privacy & Security > Accessibility for whatever is running this process. macOS only.");
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
async function ensureWindowCount(n) {
|
|
121
|
+
// No initial wait-for-a-window here: unlike getMainPageTarget() (used right
|
|
122
|
+
// after launching the app, where a window is guaranteed to appear on its
|
|
123
|
+
// own), this can run against an already-running app with zero windows open
|
|
124
|
+
// (user closed them all) - nothing creates that first window except us.
|
|
125
|
+
let pageTargets = (await fetchTargets()).filter(isMainPage);
|
|
126
|
+
while (pageTargets.length < n) {
|
|
127
|
+
const beforeCount = pageTargets.length;
|
|
128
|
+
openNewAntigravityWindow();
|
|
129
|
+
// wait for a genuinely new target to register, not a fixed guess at timing
|
|
130
|
+
await waitFor(async () => (await fetchTargets()).filter(isMainPage).length > beforeCount, 15_000);
|
|
131
|
+
pageTargets = (await fetchTargets()).filter(isMainPage);
|
|
132
|
+
}
|
|
133
|
+
const targets = pageTargets.slice(0, n);
|
|
134
|
+
// A freshly opened window can take longer to mount its chat UI than any
|
|
135
|
+
// fixed delay reliably covers (this is exactly what broke on the first
|
|
136
|
+
// real test - the 3rd window still wasn't ready 45s in). Confirm every
|
|
137
|
+
// window individually before handing any of them out.
|
|
138
|
+
await Promise.all(targets.map(async (target) => {
|
|
50
139
|
const session = new CdpSession(target.webSocketDebuggerUrl);
|
|
51
140
|
try {
|
|
52
|
-
// The CDP debug port opens before Antigravity's own UI finishes mounting
|
|
53
|
-
// (its window loads content from a local server that itself needs a
|
|
54
|
-
// moment to boot). On a cold start this can leave the chat input
|
|
55
|
-
// missing for several seconds after we've already connected.
|
|
56
141
|
await waitForChatReady(session);
|
|
57
|
-
await startNewConversation(session);
|
|
58
|
-
if (model)
|
|
59
|
-
await selectModel(session, model);
|
|
60
|
-
await pasteAndSend(session, task);
|
|
61
|
-
return await waitForReply(session, task, timeoutMs);
|
|
62
142
|
}
|
|
63
143
|
finally {
|
|
64
144
|
session.close();
|
|
65
145
|
}
|
|
66
|
-
});
|
|
146
|
+
}));
|
|
147
|
+
return targets;
|
|
148
|
+
}
|
|
149
|
+
async function getConversationId(session) {
|
|
150
|
+
const href = await session.evaluate("location.href");
|
|
151
|
+
const match = href.match(/\/c\/([a-f0-9-]{36})/i);
|
|
152
|
+
return match ? match[1] : null;
|
|
153
|
+
}
|
|
154
|
+
// Antigravity (built on the Gemini CLI/Windsurf lineage) stores every
|
|
155
|
+
// conversation's generated files under ~/.gemini/antigravity/brain/<id>/ on
|
|
156
|
+
// disk - confirmed directly, not documented anywhere. This is real
|
|
157
|
+
// filesystem storage, separate from the local HTTPS server that serves it to
|
|
158
|
+
// the UI, so it's readable without going through that server at all.
|
|
159
|
+
// os.homedir() resolves per-user at runtime - not specific to any one machine.
|
|
160
|
+
export function getAntigravityBrainDir() {
|
|
161
|
+
return path.join(os.homedir(), ".gemini", "antigravity", "brain");
|
|
162
|
+
}
|
|
163
|
+
function resolveBrainDir(conversationId) {
|
|
164
|
+
const dir = path.join(getAntigravityBrainDir(), conversationId);
|
|
165
|
+
return fs.existsSync(dir) ? dir : null;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Every file Antigravity has ever generated, across every conversation, most
|
|
169
|
+
* recent first - not scoped to any single delegate_to_antigravity call.
|
|
170
|
+
*/
|
|
171
|
+
export function listAntigravityFiles(limit = 20) {
|
|
172
|
+
const brainDir = getAntigravityBrainDir();
|
|
173
|
+
if (!fs.existsSync(brainDir))
|
|
174
|
+
return [];
|
|
175
|
+
const results = [];
|
|
176
|
+
for (const conversationId of fs.readdirSync(brainDir)) {
|
|
177
|
+
const convDir = path.join(brainDir, conversationId);
|
|
178
|
+
let entries;
|
|
179
|
+
try {
|
|
180
|
+
entries = fs.readdirSync(convDir, { withFileTypes: true });
|
|
181
|
+
}
|
|
182
|
+
catch {
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
for (const entry of entries) {
|
|
186
|
+
// skip Antigravity's own internal bookkeeping (.system_generated, etc.)
|
|
187
|
+
if (entry.name.startsWith(".") || !entry.isFile())
|
|
188
|
+
continue;
|
|
189
|
+
const filePath = path.join(convDir, entry.name);
|
|
190
|
+
const mtimeMs = fs.statSync(filePath).mtimeMs;
|
|
191
|
+
results.push({
|
|
192
|
+
path: filePath,
|
|
193
|
+
conversationId,
|
|
194
|
+
filename: entry.name,
|
|
195
|
+
modifiedAt: new Date(mtimeMs).toISOString(),
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
results.sort((a, b) => new Date(b.modifiedAt).getTime() - new Date(a.modifiedAt).getTime());
|
|
200
|
+
return results.slice(0, limit);
|
|
67
201
|
}
|
|
68
202
|
export async function getAntigravityQuota() {
|
|
69
203
|
return withLock(async () => {
|
package/dist/mcp-server.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
3
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
4
|
import { z } from "zod";
|
|
5
|
-
import { AVAILABLE_MODELS, MODEL_GUIDE, delegateToAntigravity, getAntigravityQuota } from "./antigravity-client.js";
|
|
5
|
+
import { AVAILABLE_MODELS, MODEL_GUIDE, delegateToAntigravity, delegateToAntigravityBatch, getAntigravityQuota, listAntigravityFiles, } from "./antigravity-client.js";
|
|
6
6
|
const server = new McpServer({
|
|
7
7
|
name: "supergravity-mcp",
|
|
8
8
|
version: "0.1.0",
|
|
@@ -12,7 +12,9 @@ server.registerTool("delegate_to_antigravity", {
|
|
|
12
12
|
description: "Send a task to the Google Antigravity desktop app and return its reply. " +
|
|
13
13
|
"Drives the real, already-logged-in app UI (types into its chat box and reads the response) — " +
|
|
14
14
|
"does not touch Antigravity's internals or credentials. Requires Antigravity.app to be installed " +
|
|
15
|
-
"and signed in on this machine."
|
|
15
|
+
"and signed in on this machine. If the task produces files (generated images, edited code, etc.), " +
|
|
16
|
+
"the response includes a local filesystem path where they actually live — check that path directly " +
|
|
17
|
+
"instead of trying to fetch anything through Antigravity's UI or local server.",
|
|
16
18
|
inputSchema: {
|
|
17
19
|
task: z.string().describe("The task, question, or instruction to send to Antigravity."),
|
|
18
20
|
model: z
|
|
@@ -29,8 +31,11 @@ server.registerTool("delegate_to_antigravity", {
|
|
|
29
31
|
},
|
|
30
32
|
}, async ({ task, model, timeoutMs }) => {
|
|
31
33
|
try {
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
+
const result = await delegateToAntigravity({ task, model, timeoutMs });
|
|
35
|
+
const text = result.localFilesDir
|
|
36
|
+
? `${result.reply}\n\n(Any files this produced are on disk at: ${result.localFilesDir})`
|
|
37
|
+
: result.reply;
|
|
38
|
+
return { content: [{ type: "text", text }] };
|
|
34
39
|
}
|
|
35
40
|
catch (err) {
|
|
36
41
|
return {
|
|
@@ -39,6 +44,46 @@ server.registerTool("delegate_to_antigravity", {
|
|
|
39
44
|
};
|
|
40
45
|
}
|
|
41
46
|
});
|
|
47
|
+
server.registerTool("delegate_to_antigravity_batch", {
|
|
48
|
+
title: "Delegate multiple tasks to Antigravity in parallel",
|
|
49
|
+
description: "Run several tasks at once, each in its own Antigravity window - genuinely concurrent, not queued " +
|
|
50
|
+
"one after another (verified: two simultaneous ~200-word generations both completed correctly in the " +
|
|
51
|
+
"same ~16s window). Use this instead of multiple delegate_to_antigravity calls when tasks are " +
|
|
52
|
+
"independent of each other (e.g. separate branches, unrelated questions). macOS only: opening extra " +
|
|
53
|
+
"windows needs Accessibility permission (System Settings > Privacy & Security > Accessibility) for " +
|
|
54
|
+
"whatever is running this. Don't call this at the same time as a separate delegate_to_antigravity call - " +
|
|
55
|
+
"window allocation between them isn't coordinated.",
|
|
56
|
+
inputSchema: {
|
|
57
|
+
tasks: z
|
|
58
|
+
.array(z.object({
|
|
59
|
+
task: z.string().describe("The task, question, or instruction to send to Antigravity."),
|
|
60
|
+
model: z
|
|
61
|
+
.enum(AVAILABLE_MODELS)
|
|
62
|
+
.optional()
|
|
63
|
+
.describe("Which model this specific task should use. If omitted, uses whatever is currently selected."),
|
|
64
|
+
timeoutMs: z.number().int().positive().optional().describe("Max time to wait for this task's reply."),
|
|
65
|
+
}))
|
|
66
|
+
.min(1)
|
|
67
|
+
.describe("One entry per task, each running in its own window at the same time."),
|
|
68
|
+
},
|
|
69
|
+
}, async ({ tasks }) => {
|
|
70
|
+
try {
|
|
71
|
+
const results = await delegateToAntigravityBatch(tasks);
|
|
72
|
+
const text = results
|
|
73
|
+
.map((r, i) => {
|
|
74
|
+
const files = r.localFilesDir ? `\n(Files on disk at: ${r.localFilesDir})` : "";
|
|
75
|
+
return `--- Task ${i + 1} ---\n${r.reply}${files}`;
|
|
76
|
+
})
|
|
77
|
+
.join("\n\n");
|
|
78
|
+
return { content: [{ type: "text", text }] };
|
|
79
|
+
}
|
|
80
|
+
catch (err) {
|
|
81
|
+
return {
|
|
82
|
+
content: [{ type: "text", text: `Batch delegation failed: ${err.message}` }],
|
|
83
|
+
isError: true,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
});
|
|
42
87
|
server.registerTool("get_antigravity_quota", {
|
|
43
88
|
title: "Get Antigravity quota",
|
|
44
89
|
description: "Read Antigravity's remaining model quota from its Settings > Models panel. " +
|
|
@@ -58,5 +103,25 @@ server.registerTool("get_antigravity_quota", {
|
|
|
58
103
|
};
|
|
59
104
|
}
|
|
60
105
|
});
|
|
106
|
+
server.registerTool("list_antigravity_files", {
|
|
107
|
+
title: "List Antigravity-generated files",
|
|
108
|
+
description: "List files Antigravity has generated (images, documents, etc.), most recent first, across ALL " +
|
|
109
|
+
"conversations - not just the most recent delegate_to_antigravity call. Use this to find something " +
|
|
110
|
+
"generated earlier in this session or in a past one. Returns real filesystem paths, readable directly.",
|
|
111
|
+
inputSchema: {
|
|
112
|
+
limit: z.number().int().positive().optional().describe("Max number of files to return. Default 20."),
|
|
113
|
+
},
|
|
114
|
+
}, async ({ limit }) => {
|
|
115
|
+
try {
|
|
116
|
+
const files = listAntigravityFiles(limit);
|
|
117
|
+
return { content: [{ type: "text", text: JSON.stringify(files, null, 2) }] };
|
|
118
|
+
}
|
|
119
|
+
catch (err) {
|
|
120
|
+
return {
|
|
121
|
+
content: [{ type: "text", text: `Could not list Antigravity files: ${err.message}` }],
|
|
122
|
+
isError: true,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
});
|
|
61
126
|
const transport = new StdioServerTransport();
|
|
62
127
|
await server.connect(transport);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "supergravity-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "🚀 MCP server that lets Claude (or any MCP client) delegate tasks to Google's Antigravity desktop app 🌌. Built for Intel Macs 💻 that Antigravity's own CLI doesn't support.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/skills/delegate/SKILL.md
CHANGED
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: delegate
|
|
3
|
-
description: Use when the user wants to delegate a task to Google's Antigravity desktop app — phrases like "use gemini", "ask antigravity", "have claude in antigravity do this", "offload this", or "check antigravity's quota". Explains which model to pick and how to handle a disabled-send-button error. Requires the supergravity MCP server (delegate_to_antigravity, get_antigravity_quota tools) to be connected.
|
|
3
|
+
description: Use when the user wants to delegate a task to Google's Antigravity desktop app — phrases like "use gemini", "ask antigravity", "have claude in antigravity do this", "offload this", "run N agents/tasks in parallel", or "check antigravity's quota". Explains which model to pick, when to use the batch/parallel tool, and how to handle a disabled-send-button error. Requires the supergravity MCP server (delegate_to_antigravity, delegate_to_antigravity_batch, get_antigravity_quota, list_antigravity_files tools) to be connected.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# delegate
|
|
7
7
|
|
|
8
8
|
Antigravity is a separate AI desktop app (Google's). This skill runs tasks
|
|
9
|
-
through it via the `supergravity` MCP server's
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
model family, or wants to offload work.
|
|
9
|
+
through it via the `supergravity` MCP server's tools, instead of answering
|
|
10
|
+
yourself — useful when the user explicitly asks to use it, wants a second
|
|
11
|
+
opinion from a different model family, or wants to offload work.
|
|
13
12
|
|
|
14
13
|
## When to trigger
|
|
15
14
|
|
|
@@ -76,6 +75,38 @@ trusting prose. Only fall back to the reply text for context Antigravity
|
|
|
76
75
|
wouldn't have written to disk (its reasoning, or a plain Q&A task with no
|
|
77
76
|
files involved).
|
|
78
77
|
|
|
78
|
+
## Generated files (images, artifacts) - same idea
|
|
79
|
+
|
|
80
|
+
If the reply mentions it made an image or other file, the response also
|
|
81
|
+
includes a real filesystem path (`~/.gemini/antigravity/brain/<conversation
|
|
82
|
+
id>/`) — read the file directly from there rather than trying to fetch it
|
|
83
|
+
through Antigravity's UI or local server. That directory can occasionally
|
|
84
|
+
hold files from an earlier conversation too (Antigravity sometimes reuses a
|
|
85
|
+
conversation ID) — if more than one file is present, each filename ends in
|
|
86
|
+
a millisecond timestamp, so sort by that (or file mtime) to find the one
|
|
87
|
+
this task actually produced.
|
|
88
|
+
|
|
89
|
+
## Running multiple tasks at once
|
|
90
|
+
|
|
91
|
+
If the user wants several independent things done at the same time ("spin up
|
|
92
|
+
3 agents to work on different branches", "ask it these 5 questions"), use
|
|
93
|
+
`delegate_to_antigravity_batch` with one entry per task instead of calling
|
|
94
|
+
`delegate_to_antigravity` several times. This genuinely runs them
|
|
95
|
+
concurrently, each in its own Antigravity window - confirmed with real
|
|
96
|
+
simultaneous multi-paragraph generations that both completed correctly in
|
|
97
|
+
the same ~16s window, not queued one after another.
|
|
98
|
+
|
|
99
|
+
Only use batch when the tasks are actually independent of each other (they
|
|
100
|
+
can't see or depend on each other's output, since they're separate
|
|
101
|
+
conversations). For a multi-step task where step 2 needs step 1's result,
|
|
102
|
+
use sequential `delegate_to_antigravity` calls instead.
|
|
103
|
+
|
|
104
|
+
macOS only - opening extra windows needs Accessibility permission for
|
|
105
|
+
whatever process is running this (System Settings > Privacy & Security >
|
|
106
|
+
Accessibility). If that's missing, the error says so directly. Don't run a
|
|
107
|
+
batch call and a single `delegate_to_antigravity` call at the same time -
|
|
108
|
+
they don't coordinate which window they're using.
|
|
109
|
+
|
|
79
110
|
## Other failure modes
|
|
80
111
|
|
|
81
112
|
- Antigravity not installed / not found: the error will say so — tell the
|