From c4c9f686bcea13342fe54770bc6ff3e2882f1ca4 Mon Sep 17 00:00:00 2001 From: Mattias Petersson Date: Sun, 12 Jul 2026 16:46:46 +0200 Subject: [PATCH] feat(setup): add agy host support to setup and plugin installation --- .gitignore | 1 + setup | 124 ++++++++++++++++++++++++++++++++++-- test/gen-skill-docs.test.ts | 26 ++++++-- 3 files changed, 143 insertions(+), 8 deletions(-) diff --git a/.gitignore b/.gitignore index 5196c0d05..50ec63d50 100644 --- a/.gitignore +++ b/.gitignore @@ -23,6 +23,7 @@ bin/gstack-global-discover* .hermes/ .gbrain/ .gbrain-source +.gemini/ .context/ extension/.auth.json # xterm assets are vendored from npm at build time; not source-of-truth. diff --git a/setup b/setup index 275236cd3..c96a31181 100755 --- a/setup +++ b/setup @@ -24,6 +24,9 @@ FACTORY_SKILLS="$HOME/.factory/skills" FACTORY_GSTACK="$FACTORY_SKILLS/gstack" OPENCODE_SKILLS="$HOME/.config/opencode/skills" OPENCODE_GSTACK="$OPENCODE_SKILLS/gstack" +AGY_PLUGINS="$HOME/.gemini/config/plugins" +AGY_GSTACK="$AGY_PLUGINS/gstack" +AGY_SKILLS="$AGY_GSTACK/skills" IS_WINDOWS=0 case "$(uname -s)" in @@ -85,7 +88,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 +104,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 +139,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 +203,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 +225,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 +482,16 @@ if [ "$INSTALL_OPENCODE" -eq 1 ] && [ "$NEEDS_BUILD" -eq 0 ]; then ) fi +# 1e. Generate .gemini/ Agy skill docs +if [ "$INSTALL_AGY" -eq 1 ] && [ "$NEEDS_BUILD" -eq 0 ]; then + log "Generating .gemini/ 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 +989,88 @@ link_opencode_skill_dirs() { fi } +create_agy_runtime_root() { + local gstack_dir="$1" + local agy_gstack="$2" + local agy_dir="$gstack_dir/.gemini/config/plugins/gstack/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" "$agy_gstack/skills" + + # Create plugin.json for agy plugin structure + echo '{"name": "gstack", "version": "1.58.5.0", "description": "gstack skills for Google Antigravity", "author": {"name": "gstack"}, "license": "MIT"}' > "$agy_gstack/plugin.json" + + 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 + 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/.gemini/config/plugins/gstack/skills" + local linked=() + + if [ ! -d "$agy_dir" ]; then + echo " Generating .gemini/ skill docs..." + ( cd "$gstack_dir" && bun run gen:skill-docs --host agy ) + fi + + if [ ! -d "$agy_dir" ]; then + echo " warning: .gemini/config/plugins/gstack/skills/ generation failed — run 'bun run gen:skill-docs --host agy' manually" >&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 +1292,23 @@ if [ "$INSTALL_OPENCODE" -eq 1 ]; then echo " opencode skills: $OPENCODE_SKILLS" fi +# 6d. Install for Agy +if [ "$INSTALL_AGY" -eq 1 ]; then + AGY_GSTACK_SOURCE="$SOURCE_GSTACK_DIR/.gemini/config/plugins/gstack" + AGY_SKILLS_SOURCE="$AGY_GSTACK_SOURCE/skills" + mkdir -p "$AGY_SKILLS_SOURCE" + create_agy_runtime_root "$SOURCE_GSTACK_DIR" "$AGY_GSTACK_SOURCE" + link_agy_skill_dirs "$SOURCE_GSTACK_DIR" "$AGY_SKILLS_SOURCE" + # Register/import the plugin into Agy + if command -v agy >/dev/null 2>&1; then + echo "Registering gstack plugin with Agy..." + agy plugin install "$AGY_GSTACK_SOURCE" >/dev/null 2>&1 || agy plugin import "$AGY_GSTACK_SOURCE" --force >/dev/null 2>&1 || true + fi + 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/gen-skill-docs.test.ts b/test/gen-skill-docs.test.ts index 2fb783ffd..d4ec1f035 100644 --- a/test/gen-skill-docs.test.ts +++ b/test/gen-skill-docs.test.ts @@ -2372,16 +2372,17 @@ describe('setup script validation', () => { expect(claudeSection).toContain('link_claude_root_skill_alias "$SOURCE_GSTACK_DIR" "$INSTALL_SKILLS_DIR"'); }); - test('setup supports --host auto|claude|codex|kiro|opencode', () => { + test('setup supports --host auto|claude|codex|kiro|opencode|agy', () => { expect(setupContent).toContain('--host'); - expect(setupContent).toContain('claude|codex|kiro|factory|opencode|auto'); + expect(setupContent).toContain('claude|codex|kiro|factory|opencode|agy|auto'); }); - test('auto mode detects claude, codex, kiro, and opencode binaries', () => { + test('auto mode detects claude, codex, kiro, opencode, and agy binaries', () => { expect(setupContent).toContain('command -v claude'); expect(setupContent).toContain('command -v codex'); expect(setupContent).toContain('command -v kiro-cli'); expect(setupContent).toContain('command -v opencode'); + expect(setupContent).toContain('command -v agy'); }); // T1: Sidecar skip guard — prevents .agents/skills/gstack from being linked as a skill @@ -2423,6 +2424,23 @@ describe('setup script validation', () => { expect(setupContent).toContain('dx-hall-of-fame.md'); }); + test('setup supports --host agy with install section and agy plugin path vars', () => { + expect(setupContent).toContain('INSTALL_AGY='); + expect(setupContent).toContain('AGY_PLUGINS="$HOME/.gemini/config/plugins"'); + expect(setupContent).toContain('AGY_GSTACK="$AGY_PLUGINS/gstack"'); + expect(setupContent).toContain('AGY_SKILLS="$AGY_GSTACK/skills"'); + }); + + test('setup installs agy plugin into a nested gstack runtime root and validates registration', () => { + expect(setupContent).toContain('create_agy_runtime_root'); + expect(setupContent).toContain('.gemini/config/plugins/gstack'); + expect(setupContent).toContain('review/specialists'); + expect(setupContent).toContain('qa/templates'); + expect(setupContent).toContain('qa/references'); + expect(setupContent).toContain('dx-hall-of-fame.md'); + expect(setupContent).toContain('agy plugin install'); + }); + test('create_agents_sidecar links runtime assets', () => { // Sidecar must link bin, browse, review, qa const fnStart = setupContent.indexOf('create_agents_sidecar()'); @@ -3062,7 +3080,7 @@ describe('plan-mode-info resolver (handshake-replacement)', () => { // Non-Claude hosts render to hostSubdirs (.agents/, .openclaw/, etc). The // plan-mode-info resolver has no host-scoping — all hosts get the new // section, none get the old handshake. Scan all candidate host dirs. - const hostDirs = ['.agents', '.openclaw', '.opencode', '.factory', '.hermes', '.kiro', '.cursor', '.slate']; + const hostDirs = ['.agents', '.openclaw', '.opencode', '.factory', '.hermes', '.kiro', '.cursor', '.slate', '.gemini/config/plugins/gstack']; let checked = 0; for (const host of hostDirs) { const skillsRoot = path.join(ROOT, host, 'skills');