From e8ad489f36d5640ce198132835a5e2ac47b1626d Mon Sep 17 00:00:00 2001 From: Olivier Dupont Date: Tue, 19 May 2026 23:57:41 +0200 Subject: [PATCH] =?UTF-8?q?Initial=20commit=20=E2=80=94=20Antigravity=20pl?= =?UTF-8?q?ugin=20v0.1.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- .claude-plugin/marketplace.json | 17 + .claude-plugin/plugin.json | 9 + .gitignore | 6 + README.md | 119 +++++++ agents/antigravity-rescue.md | 40 +++ commands/cancel.md | 15 + commands/rescue.md | 47 +++ commands/result.md | 13 + commands/setup.md | 39 +++ commands/status.md | 23 ++ scripts/agy-companion.mjs | 344 ++++++++++++++++++++ skills/antigravity-cli-runtime/SKILL.md | 49 +++ skills/antigravity-result-handling/SKILL.md | 52 +++ skills/gemini-prompting/SKILL.md | 96 ++++++ 14 files changed, 869 insertions(+) create mode 100644 .claude-plugin/marketplace.json create mode 100644 .claude-plugin/plugin.json create mode 100644 .gitignore create mode 100644 README.md create mode 100644 agents/antigravity-rescue.md create mode 100644 commands/cancel.md create mode 100644 commands/rescue.md create mode 100644 commands/result.md create mode 100644 commands/setup.md create mode 100644 commands/status.md create mode 100755 scripts/agy-companion.mjs create mode 100644 skills/antigravity-cli-runtime/SKILL.md create mode 100644 skills/antigravity-result-handling/SKILL.md create mode 100644 skills/gemini-prompting/SKILL.md diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json new file mode 100644 index 0000000..575b319 --- /dev/null +++ b/.claude-plugin/marketplace.json @@ -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." + } + ] +} diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json new file mode 100644 index 0000000..60f4aaa --- /dev/null +++ b/.claude-plugin/plugin.json @@ -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" +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4f339b5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +.DS_Store +node_modules/ +.cache/ +*.log +.env +.env.local diff --git a/README.md b/README.md new file mode 100644 index 0000000..1eaa432 --- /dev/null +++ b/README.md @@ -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 ` | Delegate a task to Gemini via `agy --print --dangerously-skip-permissions` | +| `/antigravity:status []` | List background jobs and their state | +| `/antigravity:result []` | Print the full log of a background job | +| `/antigravity:cancel ` | 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 "" +``` + +- `--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. diff --git a/agents/antigravity-rescue.md b/agents/antigravity-rescue.md new file mode 100644 index 0000000..30a50bb --- /dev/null +++ b/agents/antigravity-rescue.md @@ -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. diff --git a/commands/cancel.md b/commands/cancel.md new file mode 100644 index 0000000..06dc05e --- /dev/null +++ b/commands/cancel.md @@ -0,0 +1,15 @@ +--- +description: Cancel a background Antigravity (agy) job +argument-hint: "" +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. diff --git a/commands/rescue.md b/commands/rescue.md new file mode 100644 index 0000000..3256742 --- /dev/null +++ b/commands/rescue.md @@ -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 ] [--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. diff --git a/commands/result.md b/commands/result.md new file mode 100644 index 0000000..99f72d8 --- /dev/null +++ b/commands/result.md @@ -0,0 +1,13 @@ +--- +description: Print the full log of an Antigravity (agy) background job +argument-hint: "[]" +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. diff --git a/commands/setup.md b/commands/setup.md new file mode 100644 index 0000000..f142981 --- /dev/null +++ b/commands/setup.md @@ -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. diff --git a/commands/status.md b/commands/status.md new file mode 100644 index 0000000..3e726ea --- /dev/null +++ b/commands/status.md @@ -0,0 +1,23 @@ +--- +description: Show status of background Antigravity (agy) tasks +argument-hint: "[] [--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 ` +- Or a background task: `/antigravity:rescue --background ` + +If a job is shown as `exited`, suggest viewing the result: + +``` +/antigravity:result +``` diff --git a/scripts/agy-companion.mjs b/scripts/agy-companion.mjs new file mode 100755 index 0000000..c32dbde --- /dev/null +++ b/scripts/agy-companion.mjs @@ -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 `. + +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

] run a task (foreground default) + [--sandbox] [--timeout 10m] + status [] [--json] show background job status + result [] print background job log + cancel 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); + } +})(); diff --git a/skills/antigravity-cli-runtime/SKILL.md b/skills/antigravity-cli-runtime/SKILL.md new file mode 100644 index 0000000..205dbbc --- /dev/null +++ b/skills/antigravity-cli-runtime/SKILL.md @@ -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 ""` + +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 `, pass `--add-dir ` through to the helper (it adds a workspace directory). +- If the forwarded request includes `--timeout `, 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

] [--timeout 10m] "" +``` + +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 `. +- `--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`). diff --git a/skills/antigravity-result-handling/SKILL.md b/skills/antigravity-result-handling/SKILL.md new file mode 100644 index 0000000..05d90f2 --- /dev/null +++ b/skills/antigravity-result-handling/SKILL.md @@ -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. diff --git a/skills/gemini-prompting/SKILL.md b/skills/gemini-prompting/SKILL.md new file mode 100644 index 0000000..46b2567 --- /dev/null +++ b/skills/gemini-prompting/SKILL.md @@ -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 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: + +CONTEXT: +<3-8 bullets of what's known, file paths, what's been tried> + +DELIVERABLE: + + +CONSTRAINTS: +- Output max lines. +- Do not modify files outside . +- Do not refactor anything outside the immediate fix. +```