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:
17
.claude-plugin/marketplace.json
Normal file
17
.claude-plugin/marketplace.json
Normal 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."
|
||||
}
|
||||
]
|
||||
}
|
||||
9
.claude-plugin/plugin.json
Normal file
9
.claude-plugin/plugin.json
Normal 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
6
.gitignore
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
.DS_Store
|
||||
node_modules/
|
||||
.cache/
|
||||
*.log
|
||||
.env
|
||||
.env.local
|
||||
119
README.md
Normal file
119
README.md
Normal 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.
|
||||
40
agents/antigravity-rescue.md
Normal file
40
agents/antigravity-rescue.md
Normal 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
15
commands/cancel.md
Normal 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
47
commands/rescue.md
Normal 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
13
commands/result.md
Normal 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
39
commands/setup.md
Normal 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
23
commands/status.md
Normal 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
344
scripts/agy-companion.mjs
Executable 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);
|
||||
}
|
||||
})();
|
||||
49
skills/antigravity-cli-runtime/SKILL.md
Normal file
49
skills/antigravity-cli-runtime/SKILL.md
Normal 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`).
|
||||
52
skills/antigravity-result-handling/SKILL.md
Normal file
52
skills/antigravity-result-handling/SKILL.md
Normal 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.
|
||||
96
skills/gemini-prompting/SKILL.md
Normal file
96
skills/gemini-prompting/SKILL.md
Normal 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.
|
||||
```
|
||||
Reference in New Issue
Block a user