From c0ec5bfa99dc8fcd3e12966f938c13164a4bcb5c Mon Sep 17 00:00:00 2001 From: Shagun Prasad Date: Thu, 25 Jun 2026 17:44:09 +0530 Subject: [PATCH 1/5] feat(hosts): add agy config for Antigravity --- hosts/agy.ts | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ hosts/index.ts | 5 +++-- 2 files changed, 51 insertions(+), 2 deletions(-) create mode 100644 hosts/agy.ts diff --git a/hosts/agy.ts b/hosts/agy.ts new file mode 100644 index 000000000..d8c267576 --- /dev/null +++ b/hosts/agy.ts @@ -0,0 +1,48 @@ +import type { HostConfig } from '../scripts/host-config'; + +const agy: HostConfig = { + name: 'agy', + displayName: 'Antigravity', + cliCommand: 'agy', + cliAliases: ['antigravity'], + + globalRoot: '.gemini/config/skills/gstack', + localSkillRoot: '.agents/skills/gstack', + hostSubdir: '.agy', + usesEnvVars: true, + + frontmatter: { + mode: 'allowlist', + keepFields: ['name', 'description'], + descriptionLimit: null, + }, + + generation: { + generateMetadata: false, + skipSkills: ['codex'], + }, + + pathRewrites: [ + { from: '~/.claude/skills/gstack', to: '~/.gemini/config/skills/gstack' }, + { from: '.claude/skills/gstack', to: '.agents/skills/gstack' }, + { from: '.claude/skills', to: '.agents/skills' }, + ], + + suppressedResolvers: ['GBRAIN_CONTEXT_LOAD', 'GBRAIN_SAVE_RESULTS'], + + runtimeRoot: { + globalSymlinks: ['bin', 'browse/dist', 'browse/bin', 'design/dist', 'gstack-upgrade', 'ETHOS.md', 'review/specialists', 'qa/templates', 'qa/references', 'plan-devex-review/dx-hall-of-fame.md'], + globalFiles: { + 'review': ['checklist.md', 'design-checklist.md', 'greptile-triage.md', 'TODOS-format.md'], + }, + }, + + install: { + prefixable: false, + linkingStrategy: 'symlink-generated', + }, + + learningsMode: 'basic', +}; + +export default agy; diff --git a/hosts/index.ts b/hosts/index.ts index cc1c213b5..4cd643ce4 100644 --- a/hosts/index.ts +++ b/hosts/index.ts @@ -16,9 +16,10 @@ import cursor from './cursor'; import openclaw from './openclaw'; import hermes from './hermes'; import gbrain from './gbrain'; +import agy from './agy'; /** All registered host configs. Add new hosts here. */ -export const ALL_HOST_CONFIGS: HostConfig[] = [claude, codex, factory, kiro, opencode, slate, cursor, openclaw, hermes, gbrain]; +export const ALL_HOST_CONFIGS: HostConfig[] = [claude, codex, factory, kiro, opencode, slate, cursor, openclaw, hermes, gbrain, agy]; /** Map from host name to config. */ export const HOST_CONFIG_MAP: Record = Object.fromEntries( @@ -65,4 +66,4 @@ export function getExternalHosts(): HostConfig[] { } // Re-export individual configs for direct import -export { claude, codex, factory, kiro, opencode, slate, cursor, openclaw, hermes, gbrain }; +export { claude, codex, factory, kiro, opencode, slate, cursor, openclaw, hermes, gbrain, agy }; From c4150b7122b45047252c0528f9cd3e61b4f9d0ff Mon Sep 17 00:00:00 2001 From: Shagun Prasad Date: Thu, 25 Jun 2026 17:44:15 +0530 Subject: [PATCH 2/5] chore: ignore .agy directory in .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 5196c0d05..a79ca4f6e 100644 --- a/.gitignore +++ b/.gitignore @@ -22,6 +22,7 @@ bin/gstack-global-discover* .openclaw/ .hermes/ .gbrain/ +.agy/ .gbrain-source .context/ extension/.auth.json From 8a3f6273eb3564c5023a89ef3cac1a7b1b741218 Mon Sep 17 00:00:00 2001 From: Shagun Prasad Date: Thu, 25 Jun 2026 17:44:21 +0530 Subject: [PATCH 3/5] test: update host-config tests for agy --- test/host-config.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/host-config.test.ts b/test/host-config.test.ts index 577057033..ab0d2cc88 100644 --- a/test/host-config.test.ts +++ b/test/host-config.test.ts @@ -30,8 +30,8 @@ const ROOT = path.resolve(import.meta.dir, '..'); // ─── hosts/index.ts ───────────────────────────────────────── describe('hosts/index.ts', () => { - test('ALL_HOST_CONFIGS has 10 hosts', () => { - expect(ALL_HOST_CONFIGS.length).toBe(10); + test('ALL_HOST_CONFIGS has 11 hosts', () => { + expect(ALL_HOST_CONFIGS.length).toBe(11); }); test('ALL_HOST_NAMES matches config names', () => { From 112cfc5843b314606efd771669b9e0297741ce59 Mon Sep 17 00:00:00 2001 From: Shagun Prasad Date: Thu, 25 Jun 2026 17:44:27 +0530 Subject: [PATCH 4/5] docs: add Antigravity to supported agents in README --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 4bb177c3a..d88f01ced 100644 --- a/README.md +++ b/README.md @@ -121,6 +121,7 @@ Or target a specific agent with `./setup --host `: | Kiro | `--host kiro` | `~/.kiro/skills/gstack-*/` | | Hermes | `--host hermes` | `~/.hermes/skills/gstack-*/` | | GBrain (mod) | `--host gbrain` | `~/.gbrain/skills/gstack-*/` | +| Antigravity | `--host agy` | `~/.gemini/config/skills/gstack-*/` | **Want to add support for another agent?** See [docs/ADDING_A_HOST.md](docs/ADDING_A_HOST.md). It's one TypeScript config file, zero code changes. From 535faf0e6f574ac1470957476c97ee0419e77fe0 Mon Sep 17 00:00:00 2001 From: Shagun Prasad Date: Thu, 16 Jul 2026 23:06:40 +0530 Subject: [PATCH 5/5] feat(hosts): complete runtime installation path for agy This addresses feedback from time-attack and KiDDarn on PR #2134 by wiring the agy host into the setup script. It adds the installation logic (runtime root creation and skill linking) following the existing opencode pattern. It also incorporates the safeguard to verify SKILL.md presence before reporting success. --- setup | 127 +++++++++++++++++++++++++++++++++++++-- test/host-config.test.ts | 30 +++++++++ 2 files changed, 153 insertions(+), 4 deletions(-) diff --git a/setup b/setup index 275236cd3..d795d314e 100755 --- a/setup +++ b/setup @@ -24,6 +24,8 @@ FACTORY_SKILLS="$HOME/.factory/skills" FACTORY_GSTACK="$FACTORY_SKILLS/gstack" OPENCODE_SKILLS="$HOME/.config/opencode/skills" OPENCODE_GSTACK="$OPENCODE_SKILLS/gstack" +AGY_SKILLS="$HOME/.gemini/config/skills" +AGY_GSTACK="$AGY_SKILLS/gstack" IS_WINDOWS=0 case "$(uname -s)" in @@ -85,7 +87,7 @@ NO_TEAM_MODE=0 PLAN_TUNE_HOOKS_MODE="" # "" = resolve from env/config/prompt; "yes"/"no" = explicit while [ $# -gt 0 ]; do case "$1" in - --host) [ -z "$2" ] && echo "Missing value for --host (expected claude, codex, kiro, factory, opencode, openclaw, hermes, gbrain, or auto)" >&2 && exit 1; HOST="$2"; shift 2 ;; + --host) [ -z "$2" ] && echo "Missing value for --host (expected claude, codex, kiro, factory, opencode, openclaw, hermes, gbrain, agy, or auto)" >&2 && exit 1; HOST="$2"; shift 2 ;; --host=*) HOST="${1#--host=}"; shift ;; --local) LOCAL_INSTALL=1; shift ;; --prefix) SKILL_PREFIX=1; SKILL_PREFIX_FLAG=1; shift ;; @@ -101,7 +103,7 @@ while [ $# -gt 0 ]; do done case "$HOST" in - claude|codex|kiro|factory|opencode|auto) ;; + claude|codex|kiro|factory|opencode|agy|auto) ;; openclaw) echo "" echo "OpenClaw integration uses a different model — OpenClaw spawns Claude Code" @@ -136,7 +138,7 @@ case "$HOST" in echo "GBrain setup and brain skills ship from the GBrain repo." echo "" exit 0 ;; - *) echo "Unknown --host value: $HOST (expected claude, codex, kiro, factory, opencode, openclaw, hermes, gbrain, or auto)" >&2; exit 1 ;; + *) echo "Unknown --host value: $HOST (expected claude, codex, kiro, factory, opencode, openclaw, hermes, gbrain, agy, or auto)" >&2; exit 1 ;; esac # ─── Resolve skill prefix preference ───────────────────────── @@ -200,14 +202,16 @@ INSTALL_CODEX=0 INSTALL_KIRO=0 INSTALL_FACTORY=0 INSTALL_OPENCODE=0 +INSTALL_AGY=0 if [ "$HOST" = "auto" ]; then command -v claude >/dev/null 2>&1 && INSTALL_CLAUDE=1 command -v codex >/dev/null 2>&1 && INSTALL_CODEX=1 command -v kiro-cli >/dev/null 2>&1 && INSTALL_KIRO=1 command -v droid >/dev/null 2>&1 && INSTALL_FACTORY=1 command -v opencode >/dev/null 2>&1 && INSTALL_OPENCODE=1 + command -v agy >/dev/null 2>&1 && INSTALL_AGY=1 # If none found, default to claude - if [ "$INSTALL_CLAUDE" -eq 0 ] && [ "$INSTALL_CODEX" -eq 0 ] && [ "$INSTALL_KIRO" -eq 0 ] && [ "$INSTALL_FACTORY" -eq 0 ] && [ "$INSTALL_OPENCODE" -eq 0 ]; then + if [ "$INSTALL_CLAUDE" -eq 0 ] && [ "$INSTALL_CODEX" -eq 0 ] && [ "$INSTALL_KIRO" -eq 0 ] && [ "$INSTALL_FACTORY" -eq 0 ] && [ "$INSTALL_OPENCODE" -eq 0 ] && [ "$INSTALL_AGY" -eq 0 ]; then INSTALL_CLAUDE=1 fi elif [ "$HOST" = "claude" ]; then @@ -220,6 +224,8 @@ elif [ "$HOST" = "factory" ]; then INSTALL_FACTORY=1 elif [ "$HOST" = "opencode" ]; then INSTALL_OPENCODE=1 +elif [ "$HOST" = "agy" ]; then + INSTALL_AGY=1 fi migrate_direct_codex_install() { @@ -475,6 +481,16 @@ if [ "$INSTALL_OPENCODE" -eq 1 ] && [ "$NEEDS_BUILD" -eq 0 ]; then ) fi +# 1e. Generate .agy/ Antigravity skill docs +if [ "$INSTALL_AGY" -eq 1 ] && [ "$NEEDS_BUILD" -eq 0 ]; then + log "Generating .agy/ skill docs..." + ( + cd "$SOURCE_GSTACK_DIR" + bun_cmd install --frozen-lockfile 2>/dev/null || bun_cmd install + bun_cmd run gen:skill-docs --host agy + ) +fi + # 2. Ensure Playwright's Chromium is available if ! ensure_playwright_browser; then echo "Installing Playwright Chromium..." @@ -972,6 +988,99 @@ link_opencode_skill_dirs() { fi } +create_agy_runtime_root() { + local gstack_dir="$1" + local agy_gstack="$2" + local agy_dir="$gstack_dir/.agy/skills" + + if [ -L "$agy_gstack" ]; then + rm -f "$agy_gstack" + elif [ -d "$agy_gstack" ] && [ "$agy_gstack" != "$gstack_dir" ]; then + rm -rf "$agy_gstack" + fi + + mkdir -p "$agy_gstack" "$agy_gstack/browse" "$agy_gstack/design" "$agy_gstack/gstack-upgrade" "$agy_gstack/review" "$agy_gstack/qa" "$agy_gstack/plan-devex-review" + + if [ -f "$agy_dir/gstack/SKILL.md" ]; then + _link_or_copy "$agy_dir/gstack/SKILL.md" "$agy_gstack/SKILL.md" + fi + if [ -d "$gstack_dir/bin" ]; then + _link_or_copy "$gstack_dir/bin" "$agy_gstack/bin" + fi + if [ -d "$gstack_dir/browse/dist" ]; then + _link_or_copy "$gstack_dir/browse/dist" "$agy_gstack/browse/dist" + fi + if [ -d "$gstack_dir/browse/bin" ]; then + _link_or_copy "$gstack_dir/browse/bin" "$agy_gstack/browse/bin" + fi + if [ -d "$gstack_dir/design/dist" ]; then + _link_or_copy "$gstack_dir/design/dist" "$agy_gstack/design/dist" + fi + if [ -f "$agy_dir/gstack-upgrade/SKILL.md" ]; then + _link_or_copy "$agy_dir/gstack-upgrade/SKILL.md" "$agy_gstack/gstack-upgrade/SKILL.md" + fi + for f in checklist.md design-checklist.md greptile-triage.md TODOS-format.md; do + if [ -f "$gstack_dir/review/$f" ]; then + _link_or_copy "$gstack_dir/review/$f" "$agy_gstack/review/$f" + fi + done + if [ -d "$gstack_dir/review/specialists" ]; then + _link_or_copy "$gstack_dir/review/specialists" "$agy_gstack/review/specialists" + fi + if [ -d "$gstack_dir/qa/templates" ]; then + _link_or_copy "$gstack_dir/qa/templates" "$agy_gstack/qa/templates" + fi + if [ -d "$gstack_dir/qa/references" ]; then + _link_or_copy "$gstack_dir/qa/references" "$agy_gstack/qa/references" + fi + if [ -f "$gstack_dir/plan-devex-review/dx-hall-of-fame.md" ]; then + _link_or_copy "$gstack_dir/plan-devex-review/dx-hall-of-fame.md" "$agy_gstack/plan-devex-review/dx-hall-of-fame.md" + fi + if [ -f "$gstack_dir/ETHOS.md" ]; then + _link_or_copy "$gstack_dir/ETHOS.md" "$agy_gstack/ETHOS.md" + fi +} + +link_agy_skill_dirs() { + local gstack_dir="$1" + local skills_dir="$2" + local agy_dir="$gstack_dir/.agy/skills" + local linked=() + + if [ ! -d "$agy_dir" ]; then + echo " Generating .agy/ skill docs..." + ( cd "$gstack_dir" && bun run gen:skill-docs --host agy ) + fi + + if [ ! -d "$agy_dir" ]; then + echo " warning: .agy/skills/ generation failed — run 'bun run gen:skill-docs --host agy' manually" >&2 + return 1 + fi + + # Verify at least one SKILL.md exists before reporting success (KiDDarn safeguard) + local skill_count + skill_count=$(find "$agy_dir" -name "SKILL.md" | wc -l | tr -d ' ') + if [ "$skill_count" -eq 0 ]; then + echo " warning: no SKILL.md files found in .agy/skills/ — generation may have failed" >&2 + return 1 + fi + + for skill_dir in "$agy_dir"/gstack*/; do + if [ -f "$skill_dir/SKILL.md" ]; then + skill_name="$(basename "$skill_dir")" + [ "$skill_name" = "gstack" ] && continue + target="$skills_dir/$skill_name" + if [ -L "$target" ] || [ ! -e "$target" ]; then + _link_or_copy "$skill_dir" "$target" + linked+=("$skill_name") + fi + fi + done + if [ ${#linked[@]} -gt 0 ]; then + echo " linked skills: ${linked[*]}" + fi +} + # 4. Install for Claude (default) SKILLS_BASENAME="$(basename "$INSTALL_SKILLS_DIR")" SKILLS_PARENT_BASENAME="$(basename "$(dirname "$INSTALL_SKILLS_DIR")")" @@ -1193,6 +1302,16 @@ if [ "$INSTALL_OPENCODE" -eq 1 ]; then echo " opencode skills: $OPENCODE_SKILLS" fi +# 6d. Install for Antigravity (agy) +if [ "$INSTALL_AGY" -eq 1 ]; then + mkdir -p "$AGY_SKILLS" + create_agy_runtime_root "$SOURCE_GSTACK_DIR" "$AGY_GSTACK" + link_agy_skill_dirs "$SOURCE_GSTACK_DIR" "$AGY_SKILLS" + echo "gstack ready (agy)." + echo " browse: $BROWSE_BIN" + echo " agy skills: $AGY_SKILLS" +fi + # 7. Create .agents/ sidecar symlinks for the real Codex skill target. # The root Codex skill ends up pointing at $SOURCE_GSTACK_DIR/.agents/skills/gstack, # so the runtime assets must live there for both global and repo-local installs. diff --git a/test/host-config.test.ts b/test/host-config.test.ts index ab0d2cc88..d09e62190 100644 --- a/test/host-config.test.ts +++ b/test/host-config.test.ts @@ -22,6 +22,7 @@ import { slate, cursor, openclaw, + agy, } from '../hosts/index'; import { HOST_PATHS } from '../scripts/resolvers/types'; @@ -53,6 +54,7 @@ describe('hosts/index.ts', () => { expect(slate.name).toBe('slate'); expect(cursor.name).toBe('cursor'); expect(openclaw.name).toBe('openclaw'); + expect(agy.name).toBe('agy'); }); test('getHostConfig returns correct config', () => { @@ -536,3 +538,31 @@ describe('host config correctness', () => { } }); }); + +// ─── agy specific tests ───────────────────────────────────── + +describe('agy specific config', () => { + test('agy has correct global root path', () => { + expect(agy.globalRoot).toBe('.gemini/config/skills/gstack'); + }); + + test('agy has correct local skill root', () => { + expect(agy.localSkillRoot).toBe('.agents/skills/gstack'); + }); + + test('agy runtime root includes all expected sidecars', () => { + expect(agy.runtimeRoot.globalSymlinks).toContain('bin'); + expect(agy.runtimeRoot.globalSymlinks).toContain('browse/dist'); + expect(agy.runtimeRoot.globalSymlinks).toContain('ETHOS.md'); + }); + + test('agy skill generation produces at least one SKILL.md', () => { + const agySkillDir = path.join(ROOT, '.agy', 'skills'); + if (fs.existsSync(agySkillDir)) { + const skills = fs.readdirSync(agySkillDir, { withFileTypes: true }); + const hasSkillMd = skills.some(d => d.isDirectory() && + fs.existsSync(path.join(agySkillDir, d.name, 'SKILL.md'))); + expect(hasSkillMd).toBe(true); + } + }); +});