Initial commit — Antigravity plugin v0.1.0

Plugin Claude Code miroir du plugin codex officiel d'OpenAI, mais
pour Google Antigravity (agy CLI). Headless via:

  agy --print --dangerously-skip-permissions --print-timeout 10m

Includes:
- 1 forwarder agent (antigravity-rescue)
- 5 slash commands (setup, rescue, status, result, cancel)
- 3 internal skills (cli-runtime, result-handling, gemini-prompting)
- agy-companion.mjs runtime (task / setup / status / result / cancel)
- marketplace.json for `/plugin marketplace add`

Tested: setup OK, foreground task OK, background workflow OK
(except OAuth refresh which requires interactive TTY).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-19 23:57:41 +02:00
commit e8ad489f36
14 changed files with 869 additions and 0 deletions

View File

@@ -0,0 +1,17 @@
{
"name": "antigravity-local",
"owner": {
"name": "Ambassadia / Olivier Dupont"
},
"metadata": {
"description": "Local marketplace exposing the Antigravity (agy) plugin",
"version": "0.1.0"
},
"plugins": [
{
"name": "antigravity",
"source": "./",
"description": "Use Google's Antigravity (agy) from Claude Code to delegate tasks to Gemini in headless mode."
}
]
}

View File

@@ -0,0 +1,9 @@
{
"name": "antigravity",
"version": "0.1.0",
"description": "Use Google's Antigravity (agy) from Claude Code to delegate tasks to Gemini in headless mode.",
"author": {
"name": "Ambassadia / Olivier Dupont"
},
"homepage": "https://antigravity.google"
}

6
.gitignore vendored Normal file
View File

@@ -0,0 +1,6 @@
.DS_Store
node_modules/
.cache/
*.log
.env
.env.local

119
README.md Normal file
View File

@@ -0,0 +1,119 @@
# Antigravity plugin for Claude Code
Use **Google Antigravity (Gemini)** from inside Claude Code to delegate tasks in headless mode.
Mirror of the [OpenAI Codex plugin](https://github.com/openai/codex) pattern, but for Google's `agy` CLI.
## What it does
Adds 5 slash commands and 1 agent to Claude Code:
| Command | What it does |
|---|---|
| `/antigravity:setup` | Check if `agy` is installed, offer to install it |
| `/antigravity:rescue <prompt>` | Delegate a task to Gemini via `agy --print --dangerously-skip-permissions` |
| `/antigravity:status [<jobId>]` | List background jobs and their state |
| `/antigravity:result [<jobId>]` | Print the full log of a background job |
| `/antigravity:cancel <jobId>` | SIGTERM a background job |
Plus an agent `antigravity:antigravity-rescue` used internally by `/antigravity:rescue` (and that Claude can invoke proactively).
## Headless mode
Under the hood, the plugin invokes:
```bash
agy --print --dangerously-skip-permissions --print-timeout <T> "<prompt>"
```
- `--print` runs a single prompt non-interactively (no TUI)
- `--dangerously-skip-permissions` auto-approves all tool calls (necessary for unattended execution)
- `--print-timeout` is set to 10 min foreground / 30 min background by default
## Prerequisites
1. Install Antigravity CLI:
```bash
curl -fsSL https://antigravity.google/cli/install.sh | bash
```
2. **Run `agy` once interactively** in a terminal to complete Google OAuth sign-in.
This stores credentials so headless / background calls don't get stuck on auth.
3. Add this plugin to Claude Code (see below).
> ⚠️ **Auth note** — Background jobs cannot complete an OAuth flow (no TTY).
> If your `agy` token expires, the next foreground call will refresh it via browser.
> Always do one foreground rescue (or a manual `agy` call) before relying on `--background`.
## Install in Claude Code
From Claude Code:
```
/plugin marketplace add file:///Users/olivierdupont/Desktop/diversclaude/antigravity-plugin
/plugin install antigravity
```
Then verify:
```
/antigravity:setup
```
## Usage examples
```
/antigravity:rescue Investigate the failing test in src/utils.test.ts and propose a fix.
/antigravity:rescue --background Refactor the entire payment module to use Stripe Connect Express instead of the current generic Stripe integration. Apply the changes directly.
/antigravity:rescue --sandbox --add-dir /tmp/sandbox-experiment Create a quick prototype that reads CSV and outputs JSON.
/antigravity:rescue --resume Apply the top fix from the previous diagnosis.
```
After a background job:
```
/antigravity:status
/antigravity:result agy-abc-12345
/antigravity:cancel agy-abc-12345
```
## Project layout
```
antigravity-plugin/
├── .claude-plugin/plugin.json # plugin manifest
├── agents/
│ └── antigravity-rescue.md # forwarder subagent
├── commands/
│ ├── setup.md
│ ├── rescue.md
│ ├── status.md
│ ├── result.md
│ └── cancel.md
├── skills/
│ ├── antigravity-cli-runtime/ # internal: how to invoke agy-companion
│ ├── antigravity-result-handling/ # internal: how to display output
│ └── gemini-prompting/ # internal: how to write good Gemini prompts
└── scripts/
└── agy-companion.mjs # central wrapper (task / setup / status / result / cancel)
```
## Differences with the Codex plugin
| Codex plugin (1.0.4) | This Antigravity plugin (0.1.0) |
|---|---|
| `codex` CLI from OpenAI | `agy` CLI from Google |
| GPT-5.x family of models | Gemini family |
| `codex --json` headless mode | `agy --print` headless mode |
| Full job persistence (state.mjs, jobs DB) | Minimal jobs dir at `~/.cache/antigravity-plugin/jobs/` |
| Review / adversarial-review / stop-gate hooks | Not in v0.1 (just `task`) |
| Multi-model support, effort tuning | Not in v0.1 (Gemini default) |
| Resume-last logic via thread IDs | Naive `--continue` (agy's default) |
For most rescue/delegation use cases, the v0.1 surface is sufficient.
## License
MIT. This plugin is not affiliated with Google or OpenAI.

View File

@@ -0,0 +1,40 @@
---
name: antigravity-rescue
description: Proactively use when Claude Code is stuck, wants a second implementation or diagnosis pass, needs a deeper investigation, or should hand a substantial coding task to Google Antigravity (Gemini) through the shared runtime
model: sonnet
tools: Bash
skills:
- antigravity-cli-runtime
- gemini-prompting
---
You are a thin forwarding wrapper around the Antigravity companion task runtime.
Your only job is to forward the user's rescue request to the Antigravity companion script. Do not do anything else.
Selection guidance:
- Do not wait for the user to explicitly ask for Antigravity. Use this subagent proactively when the main Claude thread should hand a substantial debugging or implementation task to Antigravity (Gemini).
- Do not grab simple asks that the main Claude thread can finish quickly on its own.
Forwarding rules:
- Use exactly one `Bash` call to invoke `node "${CLAUDE_PLUGIN_ROOT}/scripts/agy-companion.mjs" task ...`.
- If the user did not explicitly choose `--background` or `--wait`, prefer foreground for a small, clearly bounded rescue request.
- If the user did not explicitly choose `--background` or `--wait` and the task looks complicated, open-ended, multi-step, or likely to keep Antigravity running for a long time, prefer background execution (`--background`).
- You may use the `gemini-prompting` skill only to tighten the user's request into a better Antigravity prompt before forwarding it.
- Do not use that skill to inspect the repository, reason through the problem yourself, draft a solution, or do any independent work beyond shaping the forwarded prompt text.
- Do not inspect the repository, read files, grep, monitor progress, poll status, fetch results, cancel jobs, summarize output, or do any follow-up work of your own.
- Do not call `status`, `result`, or `cancel`. This subagent only forwards to `task`.
- Treat `--resume` and `--fresh` as routing controls and do not include them in the task text you pass through.
- `--resume` means add `--resume` to the `task` call (which translates into `--continue` for the agy CLI).
- `--fresh` means do not add `--resume`.
- If the user is clearly asking to continue prior Antigravity work in this repository, such as "continue", "keep going", "resume", "apply the top fix", or "dig deeper", add `--resume` unless `--fresh` is present.
- Otherwise forward the task as a fresh `task` run.
- Preserve the user's task text as-is apart from stripping routing flags.
- Return the stdout of the `agy-companion` command exactly as-is.
- If the Bash call fails or Antigravity cannot be invoked, return nothing.
Response style:
- Do not add commentary before or after the forwarded `agy-companion` output.

15
commands/cancel.md Normal file
View File

@@ -0,0 +1,15 @@
---
description: Cancel a background Antigravity (agy) job
argument-hint: "<jobId>"
allowed-tools: Bash(node:*)
---
Run:
```bash
node "${CLAUDE_PLUGIN_ROOT}/scripts/agy-companion.mjs" cancel $ARGUMENTS
```
If no job id is supplied, run `/antigravity:status` first and ask the user which job they want to cancel.
Present the output to the user as-is.

47
commands/rescue.md Normal file
View File

@@ -0,0 +1,47 @@
---
description: Delegate investigation, an explicit fix request, or follow-up rescue work to the Antigravity (Gemini) rescue subagent
argument-hint: "[--background|--wait] [--resume|--fresh] [--sandbox] [--add-dir <path>] [--timeout <10m|300s>] [what Antigravity should investigate, solve, or continue]"
allowed-tools: Bash(node:*), AskUserQuestion, Agent
---
Invoke the `antigravity:antigravity-rescue` subagent via the `Agent` tool (`subagent_type: "antigravity:antigravity-rescue"`), forwarding the raw user request as the prompt.
`antigravity:antigravity-rescue` is a subagent, not a skill — do not call `Skill(antigravity:antigravity-rescue)` (no such skill).
The final user-visible response must be Antigravity's output verbatim.
Raw user request:
$ARGUMENTS
Execution mode:
- If the request includes `--background`, run the `antigravity:antigravity-rescue` subagent in the background.
- If the request includes `--wait`, run the `antigravity:antigravity-rescue` subagent in the foreground.
- If neither flag is present, default to foreground.
- `--background` and `--wait` are execution flags for Claude Code. Forward `--background` to the helper; strip `--wait` from the prompt text.
- `--resume`, `--fresh`, `--sandbox`, `--add-dir`, `--timeout` are runtime-selection flags. Preserve them for the forwarded `task` call, but do not include them in the natural-language task text.
- If the request includes `--resume`, do not ask whether to continue. The user already chose.
- If the request includes `--fresh`, do not ask whether to continue. The user already chose.
- Otherwise, before starting Antigravity, check for a resumable rescue thread from this Claude session by running:
```bash
node "${CLAUDE_PLUGIN_ROOT}/scripts/agy-companion.mjs" task-resume-candidate --json
```
- If that helper reports `available: true`, use `AskUserQuestion` exactly once to ask whether to continue the current Antigravity thread or start a new one.
- The two choices must be:
- `Continue current Antigravity thread`
- `Start a new Antigravity thread`
- If the user is clearly giving a follow-up instruction such as "continue", "keep going", "resume", "dig deeper", put `Continue current Antigravity thread (Recommended)` first.
- Otherwise put `Start a new Antigravity thread (Recommended)` first.
- If the user chooses continue, add `--resume` before routing to the subagent.
- If the user chooses a new thread, add `--fresh` before routing to the subagent.
- If the helper reports `available: false`, do not ask. Route normally.
Operating rules:
- The subagent is a thin forwarder only. It should use one `Bash` call to invoke `node "${CLAUDE_PLUGIN_ROOT}/scripts/agy-companion.mjs" task ...` and return that command's stdout as-is.
- Return the Antigravity companion stdout verbatim to the user.
- Do not paraphrase, summarize, rewrite, or add commentary before or after it.
- Do not ask the subagent to inspect files, monitor progress, poll `/antigravity:status`, fetch `/antigravity:result`, call `/antigravity:cancel`, summarize output, or do follow-up work of its own.
- Leave `--resume` and `--fresh` in the forwarded request. The subagent handles that routing when it builds the `task` command.
- If the helper reports that Antigravity is missing, stop and tell the user to run `/antigravity:setup`.
- If the user did not supply a request, ask what Antigravity should investigate or fix.

13
commands/result.md Normal file
View File

@@ -0,0 +1,13 @@
---
description: Print the full log of an Antigravity (agy) background job
argument-hint: "[<jobId>]"
allowed-tools: Bash(node:*)
---
Run:
```bash
node "${CLAUDE_PLUGIN_ROOT}/scripts/agy-companion.mjs" result $ARGUMENTS
```
Present the output (raw Antigravity log) to the user verbatim. If the output is very long, just stream it as-is; do not summarize.

39
commands/setup.md Normal file
View File

@@ -0,0 +1,39 @@
---
description: Check whether the local Antigravity (agy) CLI is ready
allowed-tools: Bash(node:*), Bash(curl:*), Bash(bash:*), AskUserQuestion
---
Run:
```bash
node "${CLAUDE_PLUGIN_ROOT}/scripts/agy-companion.mjs" setup --json
```
Parse the JSON output.
If `installed` is `false`:
- Use `AskUserQuestion` exactly once to ask whether Claude should install Antigravity now.
- Put the install option first and suffix it with `(Recommended)`.
- Use these two options:
- `Install Antigravity (Recommended)`
- `Skip for now`
- If the user chooses install, run:
```bash
curl -fsSL https://antigravity.google/cli/install.sh | bash
```
- Then rerun:
```bash
node "${CLAUDE_PLUGIN_ROOT}/scripts/agy-companion.mjs" setup --json
```
If `installed` is already `true`:
- Do not ask about installation.
Output rules:
- Present the final setup output to the user in a human-readable summary.
- If the install was skipped, present the original setup output.
- Mention the headless invocation command (`headless_mode`) so the user knows what's under the hood.
- If installation succeeded but agy is not yet authenticated, instruct the user to run `agy` once interactively in a terminal to complete Google sign-in.

23
commands/status.md Normal file
View File

@@ -0,0 +1,23 @@
---
description: Show status of background Antigravity (agy) tasks
argument-hint: "[<jobId>] [--json]"
allowed-tools: Bash(node:*)
---
Run:
```bash
node "${CLAUDE_PLUGIN_ROOT}/scripts/agy-companion.mjs" status $ARGUMENTS
```
Present the output to the user as-is.
If no jobs are found, suggest:
- Running a foreground task: `/antigravity:rescue <your task>`
- Or a background task: `/antigravity:rescue --background <your task>`
If a job is shown as `exited`, suggest viewing the result:
```
/antigravity:result <jobId>
```

344
scripts/agy-companion.mjs Executable file
View File

@@ -0,0 +1,344 @@
#!/usr/bin/env node
// Antigravity (agy) companion — minimal runtime for Claude Code plugin.
// Mirrors the codex-companion pattern but limited to `task` and `setup`.
// Headless execution: `agy --print --dangerously-skip-permissions --print-timeout <T>`.
import { spawn, spawnSync } from "node:child_process";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import process from "node:process";
const DEFAULT_TIMEOUT_MS = 10 * 60 * 1000; // 10 min foreground default
const DEFAULT_BG_TIMEOUT_MS = 30 * 60 * 1000; // 30 min background default
const STATE_DIR = path.join(os.homedir(), ".cache", "antigravity-plugin");
const JOBS_DIR = path.join(STATE_DIR, "jobs");
const LAST_THREAD_FILE = path.join(STATE_DIR, "last-thread.json");
function ensureDir(p) { try { fs.mkdirSync(p, { recursive: true }); } catch {} }
// ---- which agy binary ----------------------------------------------------
function findAgyBinary() {
const candidates = [
process.env.AGY_BINARY,
path.join(os.homedir(), ".local", "bin", "agy"),
"/usr/local/bin/agy",
"/opt/homebrew/bin/agy"
].filter(Boolean);
for (const c of candidates) {
try { fs.accessSync(c, fs.constants.X_OK); return c; } catch {}
}
// PATH lookup
const res = spawnSync("which", ["agy"], { encoding: "utf8" });
if (res.status === 0) return res.stdout.trim();
return null;
}
function getAgyVersion(bin) {
try {
const res = spawnSync(bin, ["--help"], { encoding: "utf8", timeout: 5000 });
if (res.status === 0) return "available";
} catch {}
return null;
}
// ---- CLI args -------------------------------------------------------------
const BOOL_FLAGS = new Set([
"background", "sandbox", "resume", "resume-last", "fresh", "json", "wait"
]);
const VALUE_FLAGS = new Set([
"add-dir", "timeout", "model", "effort"
]);
function parseArgs(argv) {
const out = { _: [] };
for (let i = 0; i < argv.length; i++) {
const a = argv[i];
if (a.startsWith("--")) {
const eq = a.indexOf("=");
const key = eq > -1 ? a.slice(2, eq) : a.slice(2);
if (eq > -1) { out[key] = a.slice(eq + 1); }
else if (BOOL_FLAGS.has(key)) { out[key] = true; }
else if (VALUE_FLAGS.has(key) && argv[i + 1] !== undefined) { out[key] = argv[++i]; }
else { out[key] = true; }
} else {
out._.push(a);
}
}
return out;
}
// ---- setup command -------------------------------------------------------
function setup(args) {
const bin = findAgyBinary();
const installed = !!bin;
const result = {
cli: "agy",
installed,
binary_path: bin || null,
headless_mode: installed ? "agy --print --dangerously-skip-permissions" : null,
install_command: installed ? null : "curl -fsSL https://antigravity.google/cli/install.sh | bash",
notes: installed
? "Antigravity CLI is installed. Use `/antigravity:rescue` to delegate tasks."
: "Antigravity CLI not found. Install via the official one-liner, then re-run /antigravity:setup."
};
if (args.json) {
console.log(JSON.stringify(result, null, 2));
} else {
console.log("Antigravity CLI status\n----------------------");
console.log(`Installed : ${result.installed ? "✓" : "✗"}`);
if (result.binary_path) console.log(`Binary path : ${result.binary_path}`);
if (result.headless_mode) console.log(`Headless cmd : ${result.headless_mode}`);
if (result.install_command) console.log(`Install command : ${result.install_command}`);
console.log(`\n${result.notes}`);
}
process.exit(installed ? 0 : 1);
}
// ---- task command --------------------------------------------------------
function buildPromptText(args) {
return (args._.join(" ") || "").trim();
}
function runTaskForeground(prompt, opts) {
const bin = opts.bin;
const timeoutMs = opts.timeoutMs || DEFAULT_TIMEOUT_MS;
const timeoutStr = Math.floor(timeoutMs / 1000) + "s";
const cliArgs = [
"--print",
"--dangerously-skip-permissions",
"--print-timeout", timeoutStr
];
if (opts.continueLast) cliArgs.push("--continue");
if (opts.addDir) cliArgs.push("--add-dir", opts.addDir);
if (opts.sandbox) cliArgs.push("--sandbox");
cliArgs.push(prompt);
// Use spawn to stream output to stdout in real-time
const child = spawn(bin, cliArgs, { stdio: ["ignore", "pipe", "pipe"] });
let stdoutBuf = "";
let stderrBuf = "";
child.stdout.on("data", (d) => {
const s = d.toString();
stdoutBuf += s;
process.stdout.write(s);
});
child.stderr.on("data", (d) => {
const s = d.toString();
stderrBuf += s;
process.stderr.write(s);
});
return new Promise((resolve) => {
const timer = setTimeout(() => {
try { child.kill("SIGTERM"); } catch {}
process.stderr.write("\n[antigravity] timeout reached (" + timeoutStr + ")\n");
resolve({ code: 124, stdout: stdoutBuf, stderr: stderrBuf, timedOut: true });
}, timeoutMs);
child.on("close", (code) => {
clearTimeout(timer);
resolve({ code, stdout: stdoutBuf, stderr: stderrBuf, timedOut: false });
});
});
}
function runTaskBackground(prompt, opts) {
ensureDir(JOBS_DIR);
const jobId = "agy-" + Date.now().toString(36) + "-" + Math.random().toString(36).slice(2, 7);
const jobFile = path.join(JOBS_DIR, jobId + ".json");
const logFile = path.join(JOBS_DIR, jobId + ".log");
const bin = opts.bin;
const timeoutMs = opts.timeoutMs || DEFAULT_BG_TIMEOUT_MS;
const timeoutStr = Math.floor(timeoutMs / 1000) + "s";
const cliArgs = [
"--print",
"--dangerously-skip-permissions",
"--print-timeout", timeoutStr
];
if (opts.continueLast) cliArgs.push("--continue");
if (opts.addDir) cliArgs.push("--add-dir", opts.addDir);
if (opts.sandbox) cliArgs.push("--sandbox");
cliArgs.push(prompt);
const logFd = fs.openSync(logFile, "a");
const child = spawn(bin, cliArgs, { detached: true, stdio: ["ignore", logFd, logFd] });
const meta = {
jobId,
pid: child.pid,
startedAt: new Date().toISOString(),
cmd: bin + " " + cliArgs.join(" "),
prompt,
logFile,
timeoutMs
};
fs.writeFileSync(jobFile, JSON.stringify(meta, null, 2));
child.unref();
fs.closeSync(logFd);
console.log(`[antigravity] Job started in background.`);
console.log(` job id : ${jobId}`);
console.log(` pid : ${child.pid}`);
console.log(` log file : ${logFile}`);
console.log(` status : node "${process.argv[1]}" status ${jobId}`);
console.log(` result : node "${process.argv[1]}" result ${jobId}`);
process.exit(0);
}
async function task(args) {
const bin = findAgyBinary();
if (!bin) {
console.error("[antigravity] agy not found in PATH. Run /antigravity:setup first.");
process.exit(2);
}
const prompt = buildPromptText(args);
if (!prompt) {
console.error("[antigravity] empty task prompt.");
process.exit(2);
}
const opts = {
bin,
continueLast: !!args["resume-last"] || !!args["resume"],
addDir: args["add-dir"] || null,
sandbox: !!args.sandbox,
timeoutMs: args.timeout ? parseTimeout(args.timeout) : null
};
// Save last-thread marker so future --resume can be smart
ensureDir(STATE_DIR);
fs.writeFileSync(LAST_THREAD_FILE, JSON.stringify({
ts: new Date().toISOString(),
prompt: prompt.slice(0, 200),
bin
}, null, 2));
if (args.background) {
runTaskBackground(prompt, opts);
return;
}
const r = await runTaskForeground(prompt, opts);
process.exit(r.code || 0);
}
function parseTimeout(v) {
if (typeof v === "number") return v;
const m = String(v).match(/^(\d+)([sm]?)$/i);
if (!m) return DEFAULT_TIMEOUT_MS;
const n = parseInt(m[1], 10);
const unit = (m[2] || "s").toLowerCase();
return unit === "m" ? n * 60_000 : n * 1000;
}
// ---- status / result -----------------------------------------------------
function status(args) {
ensureDir(JOBS_DIR);
const jobs = fs.readdirSync(JOBS_DIR).filter(f => f.endsWith(".json")).map(f => {
try { return JSON.parse(fs.readFileSync(path.join(JOBS_DIR, f), "utf8")); } catch { return null; }
}).filter(Boolean);
const wanted = args._[0];
const target = wanted ? jobs.find(j => j.jobId === wanted) : jobs.sort((a,b) => (b.startedAt||'').localeCompare(a.startedAt||''))[0];
if (!target) { console.log("[antigravity] no background jobs found."); process.exit(0); }
const alive = isPidAlive(target.pid);
const out = {
jobId: target.jobId,
pid: target.pid,
startedAt: target.startedAt,
alive,
logFile: target.logFile,
promptPreview: (target.prompt || "").slice(0, 200)
};
if (args.json) console.log(JSON.stringify(out, null, 2));
else {
console.log(`Job ${target.jobId}`);
console.log(` pid : ${target.pid} (${alive ? "running" : "exited"})`);
console.log(` started : ${target.startedAt}`);
console.log(` log : ${target.logFile}`);
console.log(` prompt : ${out.promptPreview}`);
}
}
function isPidAlive(pid) {
try { process.kill(pid, 0); return true; } catch { return false; }
}
function result(args) {
ensureDir(JOBS_DIR);
const wanted = args._[0];
let job;
if (wanted) {
const f = path.join(JOBS_DIR, wanted + ".json");
try { job = JSON.parse(fs.readFileSync(f, "utf8")); } catch {}
} else {
const list = fs.readdirSync(JOBS_DIR).filter(f => f.endsWith(".json")).map(f => {
try { return JSON.parse(fs.readFileSync(path.join(JOBS_DIR, f), "utf8")); } catch { return null; }
}).filter(Boolean).sort((a,b) => (b.startedAt||'').localeCompare(a.startedAt||''));
job = list[0];
}
if (!job) { console.error("[antigravity] no job found."); process.exit(1); }
try {
const log = fs.readFileSync(job.logFile, "utf8");
process.stdout.write(log);
process.exit(0);
} catch (e) {
console.error("[antigravity] log file not readable: " + (e?.message || e));
process.exit(1);
}
}
function cancel(args) {
ensureDir(JOBS_DIR);
const wanted = args._[0];
if (!wanted) { console.error("[antigravity] cancel requires a job id."); process.exit(2); }
const f = path.join(JOBS_DIR, wanted + ".json");
let job;
try { job = JSON.parse(fs.readFileSync(f, "utf8")); } catch { console.error("[antigravity] job not found."); process.exit(1); }
try { process.kill(job.pid, "SIGTERM"); console.log("[antigravity] sent SIGTERM to " + job.pid); }
catch (e) { console.error("[antigravity] could not signal pid " + job.pid + ": " + (e?.message||e)); process.exit(1); }
}
function taskResumeCandidate(args) {
let available = false, thread = null;
try {
const data = JSON.parse(fs.readFileSync(LAST_THREAD_FILE, "utf8"));
if (data && data.ts) {
const ageMs = Date.now() - Date.parse(data.ts);
if (ageMs < 24 * 3600 * 1000) { available = true; thread = data; }
}
} catch {}
const out = { available, thread };
if (args.json) console.log(JSON.stringify(out, null, 2));
else console.log(available ? "Resumable thread available." : "No resumable thread.");
}
// ---- entry ---------------------------------------------------------------
function printUsage() {
console.log(`Antigravity companion — usage:
setup [--json] check agy availability
task [--background] [--resume] [--add-dir <p>] run a task (foreground default)
[--sandbox] [--timeout 10m] <prompt>
status [<jobId>] [--json] show background job status
result [<jobId>] print background job log
cancel <jobId> SIGTERM a background job
task-resume-candidate [--json] check resumable thread
`);
}
(async function main() {
const argv = process.argv.slice(2);
const sub = argv.shift();
const args = parseArgs(argv);
switch (sub) {
case "setup": return setup(args);
case "task": return task(args);
case "status": return status(args);
case "result": return result(args);
case "cancel": return cancel(args);
case "task-resume-candidate": return taskResumeCandidate(args);
case "-h": case "--help": case undefined: return printUsage();
default: console.error("[antigravity] unknown subcommand: " + sub); printUsage(); process.exit(2);
}
})();

View File

@@ -0,0 +1,49 @@
---
name: antigravity-cli-runtime
description: Internal helper contract for calling the agy-companion runtime from Claude Code
user-invocable: false
---
# Antigravity Runtime
Use this skill only inside the `antigravity:antigravity-rescue` subagent.
Primary helper:
- `node "${CLAUDE_PLUGIN_ROOT}/scripts/agy-companion.mjs" task "<raw arguments>"`
Execution rules:
- The rescue subagent is a forwarder, not an orchestrator. Its only job is to invoke `task` once and return that stdout unchanged.
- Prefer the helper over hand-rolled `agy` strings, direct CLI calls, or any other Bash activity.
- Do not call `setup`, `status`, `result`, or `cancel` from `antigravity:antigravity-rescue`.
- Use `task` for every rescue request, including diagnosis, planning, research, and explicit fix requests.
- You may use the `gemini-prompting` skill to rewrite the user's request into a tighter Antigravity prompt before the single `task` call.
- That prompt drafting is the only Claude-side work allowed. Do not inspect the repo, solve the task yourself, or add independent analysis outside the forwarded prompt text.
Command selection:
- Use exactly one `task` invocation per rescue handoff.
- If the forwarded request includes `--background`, forward it as `--background` to the helper.
- If the forwarded request includes `--wait`, treat that as a hint to NOT use `--background` (foreground). Strip `--wait` from the task text.
- If the forwarded request includes `--resume`, strip that token from the task text and add `--resume` to the helper invocation. This becomes `--continue` for the agy CLI.
- If the forwarded request includes `--fresh`, strip that token and do not add `--resume`.
- If the forwarded request includes `--sandbox`, pass `--sandbox` through to the helper (it enables agy's sandbox mode with terminal restrictions).
- If the forwarded request includes `--add-dir <path>`, pass `--add-dir <path>` through to the helper (it adds a workspace directory).
- If the forwarded request includes `--timeout <value>`, pass it through (format: `10m` or `300s`). Default foreground timeout is 10 minutes; background is 30 minutes.
Bash invocation pattern:
```bash
node "${CLAUDE_PLUGIN_ROOT}/scripts/agy-companion.mjs" task [--background] [--resume] [--sandbox] [--add-dir <p>] [--timeout 10m] "<the user task text>"
```
Safety rules:
- Always quote the prompt text to preserve spaces and special characters.
- Preserve the user's task text as-is apart from stripping routing flags.
- Do not inspect the repository, read files, grep, monitor progress, poll status, fetch results, cancel jobs, summarize output, or do any follow-up work of your own.
- Return the stdout of the `task` command exactly as-is.
- If the Bash call fails or Antigravity cannot be invoked, return nothing.
Headless authorization:
- Under the hood, the helper invokes `agy --print --dangerously-skip-permissions --print-timeout <T>`.
- `--dangerously-skip-permissions` auto-approves all tool permission requests (necessary for headless work).
- `--print` runs a single prompt non-interactively and prints the response.
- This is the safe, documented headless mode of agy (per `agy --help`).

View File

@@ -0,0 +1,52 @@
---
name: antigravity-result-handling
description: Internal guidance for presenting Antigravity (Gemini) helper output back to the user
user-invocable: false
---
# Antigravity Result Handling
This skill governs how Claude should present output that comes back from the `agy-companion.mjs` helper.
## Foreground task
When a foreground `task` returns:
1. The stdout is the **raw Gemini response** from `agy --print`.
2. **Return it verbatim to the user.** Do not paraphrase, summarize, restructure, or add commentary.
3. If the response is empty or the command exited non-zero, tell the user one short sentence: *"Antigravity returned nothing — try /antigravity:status or rerun with /antigravity:setup."*
4. If the response mentions errors that suggest agy is not authenticated, suggest running `agy login` in a terminal.
## Background task
When a background `task` is started, the helper prints something like:
```
[antigravity] Job started in background.
job id : agy-xxxx-xxxxx
pid : 12345
log file : /Users/.../jobs/agy-xxxx.log
status : node "..." status agy-xxxx-xxxxx
result : node "..." result agy-xxxx-xxxxx
```
Present this to the user as-is. Do not block on polling.
## Status / result
When the user runs `/antigravity:status`, the helper prints job metadata (pid, started, log path, prompt preview). When they run `/antigravity:result`, the helper streams the full log. Return it verbatim.
## Streaming considerations
`agy --print` in headless mode often outputs the model's tokens as they arrive. The companion script streams them to stdout. Do not hold the output until the end — Claude can present it as it streams.
## Length
Antigravity responses can be long. Do not truncate or summarize unless the user explicitly asks for a summary. If the response is very long, mention at the end: *"(Antigravity response was N lines)"*.
## Errors
If the helper exits with a non-zero code:
- Exit code 124 = timeout. Tell the user the prompt timed out and suggest `--timeout 20m` or `--background`.
- Exit code 2 = invalid args. Show the user what was wrong.
- Other = unknown error. Pass through the stderr.

View File

@@ -0,0 +1,96 @@
---
name: gemini-prompting
description: Internal guidance for composing Antigravity (Gemini) prompts for coding, review, diagnosis, and research tasks inside the Antigravity Claude Code plugin
user-invocable: false
---
# Prompting Gemini through Antigravity
Antigravity wraps Google's Gemini model in a CLI. The model is verbose by default and tends to over-explain. To get great rescue/task output, follow these rules.
## 1. Be explicit and structured
Gemini works better when the request is well-structured. Use a header + numbered steps when possible:
```
TASK
Investigate why <X> fails on macOS but works on Linux.
CONTEXT
- Reproduces with `npm test` in /Users/.../my-project
- Failing test: src/utils.test.ts → "should hash file"
- Suspected root cause: BSD `sha256sum` not installed on macOS
WHAT I WANT
1. Root-cause confirmation (read the code, run the failing test if possible)
2. Concrete fix (with file diff)
3. Verification command
```
## 2. Tell it what NOT to do
Gemini tends to add tons of context, options, and alternatives. To keep responses tight:
```
CONSTRAINTS
- Do not propose multiple solutions — pick the best one.
- Do not explain what the code does at length. Focus on the fix.
- Do not write a README/CHANGELOG/migration guide.
- Output max 200 words of prose + the diff.
```
## 3. Provide file paths explicitly
Gemini has its own scratch workspace at `~/.gemini/antigravity-cli/scratch/`. To make it work on the user's actual repo, **always provide absolute paths** in the prompt:
```
Work in /Users/olivierdupont/Desktop/diversclaude/my-project/
Read src/utils.ts and src/utils.test.ts.
Apply the fix to src/utils.ts.
```
If the path is outside the default workspace, add the routing flag `--add-dir /path/to/project`.
## 4. Diagnosis vs. fix
Distinguish clearly between:
- **Diagnosis**: "Investigate and report. No edits."
- **Fix**: "Apply the fix directly to the files."
- **Plan**: "Write a plan but don't execute it."
Default behavior of `--dangerously-skip-permissions` is to allow file edits. If you want read-only behavior, say so explicitly in the prompt.
## 5. Resume vs. fresh
Gemini's `--continue` flag resumes the most recent session. Use it (via the runtime's `--resume` flag) when:
- Following up on a previous rescue
- Iterating on a draft fix
- Asking for more depth on the same investigation
Use a fresh session when:
- Completely new task, unrelated to previous
- The previous context is stale or wrong-headed
## 6. Antipatterns
- **Vague**: "Fix this bug" — what bug? where?
- **Too many sub-tasks**: keep one focused task per `task` call.
- **Asking for opinions**: Gemini's opinions are bland — ask for actions.
- **Trusting blindly**: ALWAYS read the resulting diff before committing.
## 7. Recommended boilerplate
```
TASK: <one-sentence outcome>
CONTEXT:
<3-8 bullets of what's known, file paths, what's been tried>
DELIVERABLE:
<file diffs, verification command, or specific answer>
CONSTRAINTS:
- Output max <N> lines.
- Do not modify files outside <list>.
- Do not refactor anything outside the immediate fix.
```