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.
This commit is contained in:
Shagun Prasad 2026-07-16 23:06:40 +05:30
parent 112cfc5843
commit 535faf0e6f
No known key found for this signature in database
2 changed files with 153 additions and 4 deletions

127
setup
View File

@ -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.

View File

@ -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);
}
});
});