mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-03 15:27:56 +01:00
v1.91.67.0 feat(hosts): Antigravity CLI (agy) host (#3089)
* feat(hosts): Antigravity CLI (agy) host Adds Google's Antigravity CLI as an experimental host through defineHost(): skills in ~/.gemini/antigravity-cli/skills/gstack-*, a runtime root with .source-path for /gstack-upgrade, names equal to their gstack-<skill> directories, name+description frontmatter with the 1024-char limit, the Gemini overlay, and a preamble glossary mapping Claude tool names to Antigravity's documented tools (run_command, view_file, write_to_file, replace_file_content, grep_search, find_by_name, invoke_subagent, ask_question). Setup: ./setup --host agy (alias antigravity), --host auto on the agy binary or ~/.gemini/antigravity-cli, never a Gemini-CLI-only ~/.gemini. Registry tier, --status known roots, uninstall and team-init cover it. Gemini CLI's ~/.gemini/skills and settings are never touched. Co-authored-by: Shagun Prasad <109907086+0xshae@users.noreply.github.com> Co-authored-by: ManchalaSashank <134257068+ManchalaSashank@users.noreply.github.com> Co-authored-by: VoIP Expert <5742479+voipexpert@users.noreply.github.com> Co-authored-by: Mattias Petersson <93470851+KiDDarn@users.noreply.github.com> Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * test(agy): golden refresh after main merge; agy is recorded as an installed render host * v1.91.67.0: CHANGELOG, version --------- Co-authored-by: Shagun Prasad <109907086+0xshae@users.noreply.github.com> Co-authored-by: ManchalaSashank <134257068+ManchalaSashank@users.noreply.github.com> Co-authored-by: VoIP Expert <5742479+voipexpert@users.noreply.github.com> Co-authored-by: Mattias Petersson <93470851+KiDDarn@users.noreply.github.com> Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
This commit is contained in:
22 files changed
+1644
-32
No files matched your search
@@ -35,6 +35,7 @@ lib/cso/images/gstack-cso-preparation
|
|||||||
.slate/
|
.slate/
|
||||||
.cursor/
|
.cursor/
|
||||||
.copilot/
|
.copilot/
|
||||||
|
.agy/
|
||||||
.openclaw/
|
.openclaw/
|
||||||
.hermes/
|
.hermes/
|
||||||
.gbrain/
|
.gbrain/
|
||||||
|
|||||||
@@ -1,5 +1,36 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## [1.91.67.0] - 2026-10-08
|
||||||
|
|
||||||
|
**Antigravity CLI is now a gstack host.**
|
||||||
|
|
||||||
|
Eight contributors sent PRs asking for Antigravity support. This release builds one host on the current host system, using paths and tool names checked against Google's Antigravity docs on 2026-10-08.
|
||||||
|
|
||||||
|
### What this means for you
|
||||||
|
|
||||||
|
Run `./setup --host agy` (or `--host antigravity`). Every skill installs into `~/.gemini/antigravity-cli/skills/`, and you invoke each one as `/gstack-<skill>`. `--host auto` picks Antigravity up when `agy` is on your PATH or `~/.gemini/antigravity-cli` exists.
|
||||||
|
|
||||||
|
Skills are rendered with Antigravity's own tool names (`run_command`, `view_file`, `ask_question`, `invoke_subagent` and the rest) and the Gemini model profile. The host is experimental until a real Antigravity session certifies it.
|
||||||
|
|
||||||
|
### Behavior changes you may notice
|
||||||
|
|
||||||
|
- **Gemini CLI's files are left alone.** Gemini CLI shares `~/.gemini`, but gstack never touches `~/.gemini/skills` or Gemini CLI's settings. A machine with only Gemini CLI is never detected as Antigravity.
|
||||||
|
- **Not covered yet:** the Antigravity 2.0 app and the IDE, which read `~/.gemini/config/skills`.
|
||||||
|
|
||||||
|
### Itemized changes
|
||||||
|
|
||||||
|
#### Added
|
||||||
|
- The `agy` host (alias `antigravity`):
|
||||||
|
- Frontmatter is limited to `name` and `description`, and the build fails if a description exceeds 1,024 characters.
|
||||||
|
- A tool glossary, the Gemini overlay, and an `AGENTS.md` rewrite.
|
||||||
|
- Advisory-only safety skills, with Review Army suppressed, as on Copilot.
|
||||||
|
- Setup, `--status`, uninstall and team-init cover the host. Its render is kept under "render only installed hosts", and setup fails if no skills were linked.
|
||||||
|
|
||||||
|
#### For contributors
|
||||||
|
- docs/ADDING_A_HOST.md records how each of the nine Antigravity PRs was used, or why it wasn't.
|
||||||
|
- Built on work by @0xshae (#2134), @ManchalaSashank (#2893), @voipexpert (#2726) and @KiDDarn (#2244).
|
||||||
|
- Still to verify on a real install: symlinked skill directories, the `/gstack-*` commands appearing, `ask_question` option handling, partial `view_file` reads on long skills, and upgrading an existing install.
|
||||||
|
|
||||||
## [1.91.66.0] - 2026-10-08
|
## [1.91.66.0] - 2026-10-08
|
||||||
|
|
||||||
**/autoplan no longer asks permission to read its own files after a background reviewer finishes.**
|
**/autoplan no longer asks permission to read its own files after a background reviewer finishes.**
|
||||||
|
|||||||
@@ -126,8 +126,8 @@ The same `gen:skill-docs` run writes two more generated files from `lib/`:
|
|||||||
and commit both; never edit the generated file
|
and commit both; never edit the generated file
|
||||||
(`test/design-checklist-sync.test.ts` fails on drift).
|
(`test/design-checklist-sync.test.ts` fails on drift).
|
||||||
|
|
||||||
Generation uses each host's `defaultModel` (`claude` for existing hosts, `gpt`
|
Generation uses each host's `defaultModel` (`claude` for most hosts, `gpt`
|
||||||
for Codex) unless `--model` is explicit. Codex installs additionally read the
|
for Codex, `gemini` for Antigravity's `agy`) unless `--model` is explicit. Codex installs additionally read the
|
||||||
top-level model from `${CODEX_HOME:-~/.codex}/config.toml`; rerun
|
top-level model from `${CODEX_HOME:-~/.codex}/config.toml`; rerun
|
||||||
`./setup --host codex` after changing that model. Note: `bun run build` and a
|
`./setup --host codex` after changing that model. Note: `bun run build` and a
|
||||||
bare `gen:skill-docs --host codex` render the host default (gpt) — if your
|
bare `gen:skill-docs --host codex` render the host default (gpt) — if your
|
||||||
|
|||||||
@@ -143,6 +143,7 @@ installs and passes the conformance tests but has no certification run yet;
|
|||||||
| Factory Droid | experimental | `--host factory` → `~/.factory/skills/gstack-*/` | ask for `gstack-office-hours` | advisory, not blocked | Codex CLI, signed in | `./setup --host factory` |
|
| Factory Droid | experimental | `--host factory` → `~/.factory/skills/gstack-*/` | ask for `gstack-office-hours` | advisory, not blocked | Codex CLI, signed in | `./setup --host factory` |
|
||||||
| Kiro | experimental | `--host kiro` → `~/.kiro/skills/gstack-*/` | ask for `gstack-office-hours` | advisory, not blocked | Codex CLI, signed in | `./setup --host kiro` |
|
| Kiro | experimental | `--host kiro` → `~/.kiro/skills/gstack-*/` | ask for `gstack-office-hours` | advisory, not blocked | Codex CLI, signed in | `./setup --host kiro` |
|
||||||
| GitHub Copilot CLI | experimental | `--host copilot` → `~/.copilot/skills/gstack-*/` | `/gstack-office-hours` | advisory, not blocked | Codex CLI, signed in | `./setup --host copilot` |
|
| GitHub Copilot CLI | experimental | `--host copilot` → `~/.copilot/skills/gstack-*/` | `/gstack-office-hours` | advisory, not blocked | Codex CLI, signed in | `./setup --host copilot` |
|
||||||
|
| Antigravity CLI | experimental | `--host agy` (or `--host antigravity`) → `~/.gemini/antigravity-cli/skills/gstack-*/` | `/gstack-office-hours` | advisory, not blocked | Codex CLI, signed in | `./setup --host agy` |
|
||||||
| Slate | instruction-only | `--host slate` (points at the Claude install; Slate reads `.claude/skills`) | `/office-hours` via the Claude install | advisory, not blocked | — | `./setup --host claude` |
|
| Slate | instruction-only | `--host slate` (points at the Claude install; Slate reads `.claude/skills`) | `/office-hours` via the Claude install | advisory, not blocked | — | `./setup --host claude` |
|
||||||
| OpenClaw | instruction-only | `--host openclaw` (prints the digest path; ACP spawns Claude Code — [docs/OPENCLAW.md](docs/OPENCLAW.md)) | "Load gstack. Run /review" | advisory, not blocked | — | re-copy the digest after upgrades |
|
| OpenClaw | instruction-only | `--host openclaw` (prints the digest path; ACP spawns Claude Code — [docs/OPENCLAW.md](docs/OPENCLAW.md)) | "Load gstack. Run /review" | advisory, not blocked | — | re-copy the digest after upgrades |
|
||||||
| Hermes | instruction-only | `--host hermes` (prints the digest path and `gen:skill-docs --host hermes`) | copy the digest, or render skills yourself | advisory, not blocked | — | re-copy the digest after upgrades |
|
| Hermes | instruction-only | `--host hermes` (prints the digest path and `gen:skill-docs --host hermes`) | copy the digest, or render skills yourself | advisory, not blocked | — | re-copy the digest after upgrades |
|
||||||
@@ -153,6 +154,13 @@ Copilot invokes gstack skills by their prefixed names (`/gstack-review`) because
|
|||||||
`/freeze` only advise there, and `COPILOT_HOME` other than `~/.copilot` is not
|
`/freeze` only advise there, and `COPILOT_HOME` other than `~/.copilot` is not
|
||||||
supported yet (setup refuses and changes nothing).
|
supported yet (setup refuses and changes nothing).
|
||||||
|
|
||||||
|
Antigravity CLI also invokes skills by prefixed name (`/gstack-review`). Setup
|
||||||
|
writes only `~/.gemini/antigravity-cli/skills/gstack*`: Gemini CLI's
|
||||||
|
`~/.gemini/skills` and settings are never touched, and `--host auto` selects
|
||||||
|
Antigravity only when `agy` is on `PATH` or `~/.gemini/antigravity-cli` exists,
|
||||||
|
never for a Gemini-CLI-only `~/.gemini`. The Antigravity 2.0 app and IDE read
|
||||||
|
`~/.gemini/config/skills`, which this host does not install.
|
||||||
|
|
||||||
Outside reviews require the selected CLI to be installed and authenticated: Claude Code when using gstack in Codex, or Codex on other harnesses. External harnesses discover these commands as `/gstack-claude-code` and `/gstack-codex`; each harness omits its own wrapper. Explicit provider requests keep that provider. The existing `codex_reviews` setting controls automatic outside reviews where supported, regardless of the provider selected.
|
Outside reviews require the selected CLI to be installed and authenticated: Claude Code when using gstack in Codex, or Codex on other harnesses. External harnesses discover these commands as `/gstack-claude-code` and `/gstack-codex`; each harness omits its own wrapper. Explicit provider requests keep that provider. The existing `codex_reviews` setting controls automatic outside reviews where supported, regardless of the provider selected.
|
||||||
|
|
||||||
`/claude` has been renamed to `/claude-code`. Re-run `./setup --host <name>` to migrate managed installations, including other harnesses sharing the checkout. Setup preserves the previous installation if replacement generation or installation fails and prints repair instructions.
|
`/claude` has been renamed to `/claude-code`. Re-run `./setup --host <name>` to migrate managed installations, including other harnesses sharing the checkout. Setup preserves the previous installation if replacement generation or installation fails and prints repair instructions.
|
||||||
@@ -562,6 +570,7 @@ rm -rf ~/.kiro/skills/gstack* 2>/dev/null
|
|||||||
rm -rf ~/.openclaw/skills/gstack* 2>/dev/null
|
rm -rf ~/.openclaw/skills/gstack* 2>/dev/null
|
||||||
rm -rf ~/.cursor/skills/gstack* 2>/dev/null
|
rm -rf ~/.cursor/skills/gstack* 2>/dev/null
|
||||||
rm -rf ~/.config/opencode/skills/gstack* 2>/dev/null
|
rm -rf ~/.config/opencode/skills/gstack* 2>/dev/null
|
||||||
|
rm -rf ~/.gemini/antigravity-cli/skills/gstack* 2>/dev/null
|
||||||
|
|
||||||
# 6. Remove temp files
|
# 6. Remove temp files
|
||||||
rm -f /tmp/gstack-* 2>/dev/null
|
rm -f /tmp/gstack-* 2>/dev/null
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# gstack digest v1.91.66.0 — regenerate/re-copy after upgrading gstack
|
# gstack digest v1.91.67.0 — regenerate/re-copy after upgrading gstack
|
||||||
|
|
||||||
Behavioral rules from gstack (https://github.com/garrytan/gstack), compressed
|
Behavioral rules from gstack (https://github.com/garrytan/gstack), compressed
|
||||||
for agent hosts without a full skill install. The full skills add workflows,
|
for agent hosts without a full skill install. The full skills add workflows,
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
# host:render-dir for every host whose render lives in its own checkout dir.
|
# host:render-dir for every host whose render lives in its own checkout dir.
|
||||||
# Claude renders into the tracked tree. test/host-renders.test.ts keeps this
|
# Claude renders into the tracked tree. test/host-renders.test.ts keeps this
|
||||||
# equal to hostSubdir in hosts/*.ts.
|
# equal to hostSubdir in hosts/*.ts.
|
||||||
GSTACK_HOST_RENDER_DIRS="codex:.agents kiro:.kiro factory:.factory opencode:.opencode cursor:.cursor copilot:.copilot slate:.slate openclaw:.openclaw hermes:.hermes gbrain:.gbrain"
|
GSTACK_HOST_RENDER_DIRS="codex:.agents kiro:.kiro factory:.factory opencode:.opencode cursor:.cursor copilot:.copilot agy:.agy slate:.slate openclaw:.openclaw hermes:.hermes gbrain:.gbrain"
|
||||||
|
|
||||||
gstack_render_hosts_file() { printf '%s/.gstack-installed-hosts\n' "$1"; }
|
gstack_render_hosts_file() { printf '%s/.gstack-installed-hosts\n' "$1"; }
|
||||||
|
|
||||||
|
|||||||
@@ -160,7 +160,7 @@ gstack_install_render_for() {
|
|||||||
gstack_host_tier() {
|
gstack_host_tier() {
|
||||||
case "$1" in
|
case "$1" in
|
||||||
claude) echo full ;;
|
claude) echo full ;;
|
||||||
codex|kiro|factory|opencode|cursor|copilot) echo experimental ;;
|
codex|kiro|factory|opencode|cursor|copilot|agy) echo experimental ;;
|
||||||
slate|openclaw|hermes|gbrain) echo instruction-only ;;
|
slate|openclaw|hermes|gbrain) echo instruction-only ;;
|
||||||
*) echo unknown ;;
|
*) echo unknown ;;
|
||||||
esac
|
esac
|
||||||
@@ -231,6 +231,7 @@ gstack_install_known_roots() {
|
|||||||
printf 'opencode\tglobal\t%s\t%s\n' "$HOME/.config/opencode/skills" "$HOME/.config/opencode/skills/gstack"
|
printf 'opencode\tglobal\t%s\t%s\n' "$HOME/.config/opencode/skills" "$HOME/.config/opencode/skills/gstack"
|
||||||
printf 'cursor\tglobal\t%s\t%s\n' "$HOME/.cursor/skills" "$HOME/.cursor/skills/gstack"
|
printf 'cursor\tglobal\t%s\t%s\n' "$HOME/.cursor/skills" "$HOME/.cursor/skills/gstack"
|
||||||
printf 'copilot\tglobal\t%s\t%s\n' "$HOME/.copilot/skills" "$HOME/.copilot/skills/gstack"
|
printf 'copilot\tglobal\t%s\t%s\n' "$HOME/.copilot/skills" "$HOME/.copilot/skills/gstack"
|
||||||
|
printf 'agy\tglobal\t%s\t%s\n' "$HOME/.gemini/antigravity-cli/skills" "$HOME/.gemini/antigravity-cli/skills/gstack"
|
||||||
proj="$(git rev-parse --show-toplevel 2>/dev/null || true)"
|
proj="$(git rev-parse --show-toplevel 2>/dev/null || true)"
|
||||||
if [ -n "$proj" ] && [ "$proj" != "$HOME" ]; then
|
if [ -n "$proj" ] && [ "$proj" != "$HOME" ]; then
|
||||||
printf 'claude\tproject\t%s\t%s\n' "$proj/.claude/skills" "$proj/.claude/skills/gstack"
|
printf 'claude\tproject\t%s\t%s\n' "$proj/.claude/skills" "$proj/.claude/skills/gstack"
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ else
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
_GS=""
|
_GS=""
|
||||||
for _D in "${GSTACK_ROOT:-}" "$HOME/.claude/skills/gstack" "$HOME/.codex/skills/gstack" "$HOME/.factory/skills/gstack" "$HOME/.kiro/skills/gstack" "$HOME/.config/opencode/skills/gstack" "$HOME/.slate/skills/gstack" "$HOME/.cursor/skills/gstack" "$HOME/.openclaw/skills/gstack" "$HOME/.hermes/skills/gstack" "$HOME/.gbrain/skills/gstack" "$HOME/.copilot/skills/gstack" "$HOME/.gstack/repos/gstack"; do
|
for _D in "${GSTACK_ROOT:-}" "$HOME/.claude/skills/gstack" "$HOME/.codex/skills/gstack" "$HOME/.factory/skills/gstack" "$HOME/.kiro/skills/gstack" "$HOME/.config/opencode/skills/gstack" "$HOME/.slate/skills/gstack" "$HOME/.cursor/skills/gstack" "$HOME/.openclaw/skills/gstack" "$HOME/.hermes/skills/gstack" "$HOME/.gbrain/skills/gstack" "$HOME/.copilot/skills/gstack" "$HOME/.gemini/antigravity-cli/skills/gstack" "$HOME/.gstack/repos/gstack"; do
|
||||||
[ -z "$_GS" ] && [ -n "$_D" ] && [ -d "$_D/bin" ] && _GS="$_D"
|
[ -z "$_GS" ] && [ -n "$_D" ] && [ -d "$_D/bin" ] && _GS="$_D"
|
||||||
done
|
done
|
||||||
[ -n "$_GS" ] && echo "GSTACK_OK: $_GS" || echo "GSTACK_MISSING"
|
[ -n "$_GS" ] && echo "GSTACK_OK: $_GS" || echo "GSTACK_MISSING"
|
||||||
@@ -146,7 +146,7 @@ const home = process.env.HOME || process.env.USERPROFILE || os.homedir();
|
|||||||
const roots = [process.env.GSTACK_ROOT || ''].concat([
|
const roots = [process.env.GSTACK_ROOT || ''].concat([
|
||||||
'.claude/skills/gstack', '.codex/skills/gstack', '.factory/skills/gstack', '.kiro/skills/gstack',
|
'.claude/skills/gstack', '.codex/skills/gstack', '.factory/skills/gstack', '.kiro/skills/gstack',
|
||||||
'.config/opencode/skills/gstack', '.slate/skills/gstack', '.cursor/skills/gstack', '.openclaw/skills/gstack',
|
'.config/opencode/skills/gstack', '.slate/skills/gstack', '.cursor/skills/gstack', '.openclaw/skills/gstack',
|
||||||
'.hermes/skills/gstack', '.gbrain/skills/gstack', '.copilot/skills/gstack', '.gstack/repos/gstack',
|
'.hermes/skills/gstack', '.gbrain/skills/gstack', '.copilot/skills/gstack', '.gemini/antigravity-cli/skills/gstack', '.gstack/repos/gstack',
|
||||||
].map((rel) => path.join(home, rel)));
|
].map((rel) => path.join(home, rel)));
|
||||||
const gstackRoot = roots.find((root) => {
|
const gstackRoot = roots.find((root) => {
|
||||||
try { return root !== '' && fs.statSync(path.join(root, 'bin')).isDirectory(); } catch { return false; }
|
try { return root !== '' && fs.statSync(path.join(root, 'bin')).isDirectory(); } catch { return false; }
|
||||||
|
|||||||
@@ -23,6 +23,7 @@
|
|||||||
# ~/.cursor/skills/gstack* — Cursor skill install + per-skill symlinks
|
# ~/.cursor/skills/gstack* — Cursor skill install + per-skill symlinks
|
||||||
# ~/.config/opencode/skills/gstack* — OpenCode skill install + per-skill symlinks
|
# ~/.config/opencode/skills/gstack* — OpenCode skill install + per-skill symlinks
|
||||||
# ~/.copilot/skills/gstack* — GitHub Copilot CLI skill install + per-skill symlinks
|
# ~/.copilot/skills/gstack* — GitHub Copilot CLI skill install + per-skill symlinks
|
||||||
|
# ~/.gemini/antigravity-cli/skills/gstack* — Antigravity CLI skill install + per-skill symlinks
|
||||||
# installs.tsv rows — install registry rows for removed installs
|
# installs.tsv rows — install registry rows for removed installs
|
||||||
# ~/.gstack/ — global state (config, analytics, sessions, projects,
|
# ~/.gstack/ — global state (config, analytics, sessions, projects,
|
||||||
# repos, installation-id, browse error logs)
|
# repos, installation-id, browse error logs)
|
||||||
@@ -135,6 +136,7 @@ if [ "$FORCE" -eq 0 ]; then
|
|||||||
[ -d "$HOME/.cursor/skills" ] && echo " ~/.cursor/skills/gstack*"
|
[ -d "$HOME/.cursor/skills" ] && echo " ~/.cursor/skills/gstack*"
|
||||||
[ -d "$HOME/.config/opencode/skills" ] && echo " ~/.config/opencode/skills/gstack*"
|
[ -d "$HOME/.config/opencode/skills" ] && echo " ~/.config/opencode/skills/gstack*"
|
||||||
[ -d "$HOME/.copilot/skills" ] && echo " ~/.copilot/skills/gstack*"
|
[ -d "$HOME/.copilot/skills" ] && echo " ~/.copilot/skills/gstack*"
|
||||||
|
[ -d "$HOME/.gemini/antigravity-cli/skills" ] && echo " ~/.gemini/antigravity-cli/skills/gstack*"
|
||||||
[ "$KEEP_STATE" -eq 0 ] && [ -d "$STATE_DIR" ] && echo " $STATE_DIR"
|
[ "$KEEP_STATE" -eq 0 ] && [ -d "$STATE_DIR" ] && echo " $STATE_DIR"
|
||||||
[ -n "$_LEAVE_ROOT" ] && echo " (kept) $_LEAVE_ROOT: state root selected by $RESOLVED_VAR; left in place"
|
[ -n "$_LEAVE_ROOT" ] && echo " (kept) $_LEAVE_ROOT: state root selected by $RESOLVED_VAR; left in place"
|
||||||
|
|
||||||
@@ -512,6 +514,22 @@ if [ -d "$COPILOT_SKILLS" ]; then
|
|||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# ─── Remove Antigravity CLI skills ──────────────────────────
|
||||||
|
# Same provenance gate. Only gstack* entries under antigravity-cli/skills go:
|
||||||
|
# the rest of ~/.gemini belongs to Antigravity and Gemini CLI.
|
||||||
|
ANTIGRAVITY_SKILLS="$HOME/.gemini/antigravity-cli/skills"
|
||||||
|
if [ -d "$ANTIGRAVITY_SKILLS" ]; then
|
||||||
|
for _ITEM in "$ANTIGRAVITY_SKILLS"/gstack*; do
|
||||||
|
[ -e "$_ITEM" ] || [ -L "$_ITEM" ] || continue
|
||||||
|
if _cursor_item_is_gstack_managed "$_ITEM"; then
|
||||||
|
rm -rf "$_ITEM"
|
||||||
|
REMOVED+=("agy/$(basename "$_ITEM")")
|
||||||
|
else
|
||||||
|
_SKIPPED_DIRS+=("$_ITEM")
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
# ─── Reconcile the install registry ─────────────────────────
|
# ─── Reconcile the install registry ─────────────────────────
|
||||||
# A registry that survives (--keep-state, or a state root selected by an env
|
# A registry that survives (--keep-state, or a state root selected by an env
|
||||||
# var and left in place) must forget the installs removed above, or the next
|
# var and left in place) must forget the installs removed above, or the next
|
||||||
|
|||||||
+69
-3
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
gstack uses a declarative host config system. Each supported AI coding agent
|
gstack uses a declarative host config system. Each supported AI coding agent
|
||||||
(Claude, Codex, Factory, Kiro, OpenCode, Slate, Cursor, OpenClaw, Hermes,
|
(Claude, Codex, Factory, Kiro, OpenCode, Slate, Cursor, OpenClaw, Hermes,
|
||||||
GBrain) is defined as a typed TypeScript config object built by the
|
GBrain, Copilot, Antigravity) is defined as a typed TypeScript config object built by the
|
||||||
`defineHost()` factory. Rendering a new host means creating one file and
|
`defineHost()` factory. Rendering a new host means creating one file and
|
||||||
re-exporting it: the generator, tests, and validation pick it up. Installing
|
re-exporting it: the generator, tests, and validation pick it up. Installing
|
||||||
it is not free: setup still has one install arm per installable host (see
|
it is not free: setup still has one install arm per installable host (see
|
||||||
@@ -76,13 +76,13 @@ Inventory of every setup side effect, by phase (line numbers are approximate):
|
|||||||
|---|---|---|
|
|---|---|---|
|
||||||
| Build | `browse/dist`, `design/dist`, `make-pdf/dist`, `bin/gstack-cso-*` in the source checkout | source |
|
| Build | `browse/dist`, `design/dist`, `make-pdf/dist`, `bin/gstack-cso-*` in the source checkout | source |
|
||||||
| Hosts record | `.gstack-installed-hosts` in the source checkout: every host setup installed from it, additive (#1694) | source |
|
| Hosts record | `.gstack-installed-hosts` in the source checkout: every host setup installed from it, additive (#1694) | source |
|
||||||
| Generation | renders for the recorded hosts only (`.agents/`, `.kiro/`, `.factory/`, `.opencode/`, `.cursor/`, `.copilot/`) in the source checkout; `gstack-patch-names` rewrites Claude `name:` fields in place | source |
|
| Generation | renders for the recorded hosts only (`.agents/`, `.kiro/`, `.factory/`, `.opencode/`, `.cursor/`, `.copilot/`, `.agy/`) in the source checkout; `gstack-patch-names` rewrites Claude `name:` fields in place | source |
|
||||||
| Render prune | renders of hosts the record does not name: links into the checkout removed, generated files moved to `$GSTACK_STATE_ROOT/backups/host-renders/<ts>-<id>/`, other files kept | source / state root |
|
| Render prune | renders of hosts the record does not name: links into the checkout removed, generated files moved to `$GSTACK_STATE_ROOT/backups/host-renders/<ts>-<id>/`, other files kept | source / state root |
|
||||||
| Chromium | Playwright cache (`~/.cache/ms-playwright`), lock under the state root | machine |
|
| Chromium | Playwright cache (`~/.cache/ms-playwright`), lock under the state root | machine |
|
||||||
| Config | `skill_prefix`, `timeline_stop_hook`, team-mode keys in `$GSTACK_STATE_ROOT/config.yaml` | state root |
|
| Config | `skill_prefix`, `timeline_stop_hook`, team-mode keys in `$GSTACK_STATE_ROOT/config.yaml` | state root |
|
||||||
| Claude arm | `~/.claude/skills/gstack` link, one dir per skill with a `SKILL.md` link, alias copies | selected host |
|
| Claude arm | `~/.claude/skills/gstack` link, one dir per skill with a `SKILL.md` link, alias copies | selected host |
|
||||||
| Codex arm | `${CODEX_HOME:-~/.codex}/skills/gstack` runtime root, `gstack-*` links into `.agents/skills`, `.agents/skills/gstack` sidecar | selected host |
|
| Codex arm | `${CODEX_HOME:-~/.codex}/skills/gstack` runtime root, `gstack-*` links into `.agents/skills`, `.agents/skills/gstack` sidecar | selected host |
|
||||||
| Kiro, Factory, OpenCode, Cursor, Copilot arms | the host's `skills/gstack` runtime root (built beside the live one and swapped in) and `gstack-*` links | selected host |
|
| Kiro, Factory, OpenCode, Cursor, Copilot, Antigravity arms | the host's `skills/gstack` runtime root (built beside the live one and swapped in) and `gstack-*` links | selected host |
|
||||||
| OpenCode commands | `~/.config/opencode/commands/gstack-*.md` with a managed marker (#2629); user command files are never touched | selected host |
|
| OpenCode commands | `~/.config/opencode/commands/gstack-*.md` with a managed marker (#2629); user command files are never touched | selected host |
|
||||||
| Registry | one row per activated install in `installs.tsv` | state root |
|
| Registry | one row per activated install in `installs.tsv` | state root |
|
||||||
| Migrations | whatever each `v*.sh` repairs; marker in the state root | state root |
|
| Migrations | whatever each `v*.sh` repairs; marker in the state root | state root |
|
||||||
@@ -211,6 +211,70 @@ answer `ask_user` itself, plan mode blocks mutating shell commands, symlinked
|
|||||||
skill dirs need Copilot CLI 1.0.62 or later, and `COPILOT_HOME` other than
|
skill dirs need Copilot CLI 1.0.62 or later, and `COPILOT_HOME` other than
|
||||||
`~/.copilot` is refused for now.
|
`~/.copilot` is refused for now.
|
||||||
|
|
||||||
|
## Antigravity CLI
|
||||||
|
|
||||||
|
Shipped as `experimental` (`hosts/agy.ts`, `./setup --host agy`, alias
|
||||||
|
`--host antigravity`) for Google's Antigravity CLI. The host is named after the
|
||||||
|
`agy` binary: with the longer name, the per-fence runtime prelude was 408 bytes
|
||||||
|
against its 400-byte budget (the global root is long); `agy` renders at
|
||||||
|
exactly 400, so the next byte added to that prelude must come with a budget
|
||||||
|
change. Verified against Google's docs on 2026-10-08:
|
||||||
|
|
||||||
|
| Fact | Value | Source |
|
||||||
|
|---|---|---|
|
||||||
|
| Global skills | `~/.gemini/antigravity-cli/skills/<skill>/SKILL.md` | [Agent skills](https://antigravity.google/docs/skills/) (CLI section), [Plugins & skills](https://antigravity.google/docs/cli/plugins/), [Migrating from Gemini CLI](https://antigravity.google/docs/cli/gcli-migration/) |
|
||||||
|
| Workspace skills | `.agents/skills/<skill>/SKILL.md` | same pages |
|
||||||
|
| Frontmatter | `name` (optional, defaults to the directory), `description` (required) | [Agent skills](https://antigravity.google/docs/skills/) |
|
||||||
|
| Description limit | 1024 characters (Agent Skills format) | [What are Agent Skills?](https://cloud.google.com/discover/ai-agent-skills) |
|
||||||
|
| Tool names | `run_command`, `view_file`, `write_to_file`, `replace_file_content`, `multi_replace_file_content`, `grep_search`, `find_by_name`, `invoke_subagent`, `ask_question` | [Hooks: supported tools](https://antigravity.google/docs/hooks/) |
|
||||||
|
| Plan mode | `agy --mode=plan` / `/plan`, no exit-plan tool | [Execution modes](https://antigravity.google/docs/cli/modes/) |
|
||||||
|
| Binary | `agy`, installed to `~/.local/bin/agy`; config in `~/.gemini/antigravity-cli/` | [Installation](https://antigravity.google/docs/cli/install/), [Settings](https://antigravity.google/docs/cli/settings/) |
|
||||||
|
|
||||||
|
What the host does: skills in `~/.gemini/antigravity-cli/skills/gstack-*`,
|
||||||
|
runtime root `~/.gemini/antigravity-cli/skills/gstack` with `.source-path` for
|
||||||
|
`/gstack-upgrade`, `name:` equal to the `gstack-<skill>` directory (the CLI
|
||||||
|
turns each name into a slash command, and has `/plan`, `/diff`, `/skills` and
|
||||||
|
other built-ins), frontmatter limited to `name` and `description`, the Gemini
|
||||||
|
model overlay, and a one-paragraph tool glossary ahead of the preamble's STATUS
|
||||||
|
rules. The glossary also tells the agent to keep reading when `view_file`
|
||||||
|
returns only part of a long SKILL.md: users report a first read that stops at
|
||||||
|
800 lines ([forum](https://discuss.ai.google.dev/t/make-the-800-line-forced-first-read-in-view-file-configurable-or-opt-out/172257)),
|
||||||
|
which Google's docs do not state, so the rendered text names no number.
|
||||||
|
|
||||||
|
**Shared `~/.gemini`.** Antigravity CLI and Gemini CLI both live under
|
||||||
|
`~/.gemini`. Gemini CLI reads `~/.gemini/skills` (and `~/.agents/skills`);
|
||||||
|
Antigravity CLI reads `~/.gemini/antigravity-cli/skills`. Setup writes only
|
||||||
|
gstack entries in the latter, uninstall removes only gstack-managed entries
|
||||||
|
there, and `--host auto` selects Antigravity only for `agy` on `PATH` or an
|
||||||
|
existing `~/.gemini/antigravity-cli`, never for a bare `~/.gemini`.
|
||||||
|
`test/setup-install-registry.test.ts` holds both directions. Gemini CLI as a
|
||||||
|
host is not supported. Two overlaps remain: workspace `.agents/skills` is also
|
||||||
|
where a project-local Codex install puts its Codex-flavored `gstack-*`
|
||||||
|
skills, which Antigravity will list in that repo, and the Antigravity 2.0 app
|
||||||
|
and IDE read `~/.gemini/config/skills`, which this host does not install
|
||||||
|
(Google's codelab says that directory also reaches the CLI; the CLI docs do
|
||||||
|
not, so setup follows the CLI docs).
|
||||||
|
|
||||||
|
Disposition of the community attempts:
|
||||||
|
|
||||||
|
| PR | Decision | Why |
|
||||||
|
|---|---|---|
|
||||||
|
| #2134 (@0xshae) | Selectively reused, with credit | Kept: `agy` as the host name and binary, `.agents/skills` as the workspace root, and a `.agy` render directory separate from Codex's `.agents`. Not kept: the hand-written config (predates `defineHost()`), `~/.gemini/config/skills` as the CLI root, and the setup arm without the ownership gates. |
|
||||||
|
| #2893 (@ManchalaSashank) | Selectively reused, with credit | Kept: `agy`/`antigravity` as host name and alias in setup and generation, the Gemini overlay, uninstall coverage. Not kept: `~/.gemini/config/skills`, auto-detect on a bare `~/.gemini` (Gemini CLI's directory), `.gemini` as the render directory, and the repo-local `.gemini/skills` sidecar. |
|
||||||
|
| #2726 (@voipexpert) | Selectively reused, with credit | Kept: Antigravity's tool names from its own docs, and that `manage_task` (background processes) is not a to-do tool. Not kept: blanket tool-name rewrites (a preamble glossary replaces them) and suppressing outside reviews, which run through the shell. |
|
||||||
|
| #2244 (@KiDDarn) | Selectively reused, with credit | Kept: fail the install when no skill was linked. Not kept: the `~/.gemini/config/plugins` plugin layout (CLI plugins live under `~/.gemini/antigravity-cli/plugins` and are installed by `agy plugin install`), the benchmark provider work, and extra frontmatter fields. |
|
||||||
|
| #413 (@jnMetaCode) | Rejected | Installs to `~/.gemini/skills` and detects `gemini`: that is Gemini CLI. A comment there (@doogiehowzer) reported the partial `view_file` read the glossary now handles. |
|
||||||
|
| #2079 (@FACUTUCCI10) | Rejected | Gemini CLI paths (`~/.gemini/skills`). |
|
||||||
|
| #2187 (@NotArsal) | Rejected | Duplicate of #2134 without setup. |
|
||||||
|
| #1490 (@7H0M45-4N70NY) | Rejected | `~/.antigravity/skills`, which no Antigravity surface reads. |
|
||||||
|
| #565 (@ChetanTekur) | Rejected | Gemini CLI `GEMINI.md` support; Gemini CLI is out of scope. |
|
||||||
|
|
||||||
|
Unverified without a real Antigravity CLI (why it is not `full`): that the
|
||||||
|
CLI follows symlinked skill directories, that it does not import the runtime
|
||||||
|
root's extra markdown files (`ETHOS.md`, `review/*.md`) as skills, the
|
||||||
|
partial-read behavior of `view_file` on long skills, `ask_question` option
|
||||||
|
handling, and an upgrade from an existing install.
|
||||||
|
|
||||||
## Instruction-only hosts
|
## Instruction-only hosts
|
||||||
|
|
||||||
OpenClaw, Hermes, Slate and GBrain have no install arm. Hermes stays
|
OpenClaw, Hermes, Slate and GBrain have no install arm. Hermes stays
|
||||||
@@ -234,6 +298,8 @@ hosts/
|
|||||||
├── openclaw.ts # OpenClaw
|
├── openclaw.ts # OpenClaw
|
||||||
├── hermes.ts # Hermes (Nous Research)
|
├── hermes.ts # Hermes (Nous Research)
|
||||||
├── gbrain.ts # GBrain
|
├── gbrain.ts # GBrain
|
||||||
|
├── copilot.ts # GitHub Copilot CLI
|
||||||
|
├── agy.ts # Google Antigravity CLI
|
||||||
└── index.ts # Registry: imports all, derives Host type
|
└── index.ts # Registry: imports all, derives Host type
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
import { defineHost, GBRAIN_RESOLVERS, preambleToolGlossary } from './define-host';
|
||||||
|
|
||||||
|
// Google Antigravity CLI, named after its `agy` binary: the longer name pushed
|
||||||
|
// the runtime prelude past PRELUDE_BYTE_BUDGET. Rewritten on defineHost() from
|
||||||
|
// the community attempts; disposition and doc sources in docs/ADDING_A_HOST.md
|
||||||
|
// "Antigravity CLI".
|
||||||
|
// Paths: the CLI reads global skills from ~/.gemini/antigravity-cli/skills and
|
||||||
|
// workspace skills from .agents/skills (antigravity.google/docs/skills, CLI
|
||||||
|
// section). Tool names: antigravity.google/docs/hooks "Supported tools".
|
||||||
|
const ANTIGRAVITY_TOOL_GLOSSARY = '**Antigravity tool names:** `AskUserQuestion` means your `ask_question` tool (options as choices); there is no `mcp__*__AskUserQuestion` variant. The Bash tool means `run_command`, the Read tool `view_file`, the Write tool `write_to_file`, the Edit tool `replace_file_content` (`multi_replace_file_content` for several edits in one file), Grep `grep_search`, Glob `find_by_name`, and the Agent tool `invoke_subagent`. There is no Skill tool: load a skill by reading its SKILL.md with `view_file`. If a step says to call `ExitPlanMode`, tell the user the plan is ready and wait. `view_file` can return only part of a long file: when a SKILL.md or a file it tells you to read ends before its last line, read the remaining line ranges before acting on it.';
|
||||||
|
|
||||||
|
const agy = defineHost({
|
||||||
|
name: 'agy',
|
||||||
|
displayName: 'Antigravity CLI',
|
||||||
|
cliAliases: ['antigravity'],
|
||||||
|
defaultModel: 'gemini',
|
||||||
|
tier: 'experimental',
|
||||||
|
capabilities: { toolExecution: true, questions: 'native', planMode: false, delegation: true, browser: true, safetyHooks: 'advisory' },
|
||||||
|
|
||||||
|
globalRoot: '.gemini/antigravity-cli/skills/gstack',
|
||||||
|
localSkillRoot: '.agents/skills/gstack',
|
||||||
|
// Render output only. .agents is Codex's render tree, and .gemini is Gemini
|
||||||
|
// CLI's workspace directory.
|
||||||
|
hostSubdir: '.agy',
|
||||||
|
|
||||||
|
// Antigravity reads name and description only; name defaults to the
|
||||||
|
// directory, and the CLI turns each name into a slash command, so names
|
||||||
|
// match their gstack-<skill> directories instead of shadowing /plan-style
|
||||||
|
// built-ins. 1024 is the Agent Skills description limit.
|
||||||
|
frontmatter: {
|
||||||
|
mode: 'allowlist',
|
||||||
|
keepFields: ['name', 'description'],
|
||||||
|
descriptionLimit: 1024,
|
||||||
|
descriptionLimitBehavior: 'error',
|
||||||
|
nameMatchesDirectory: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Literal ~/.gemini/antigravity-cli paths (the Copilot model) so skills
|
||||||
|
// without the preamble still resolve; .source-path (written by setup) lets
|
||||||
|
// /gstack-upgrade find the checkout behind the runtime root.
|
||||||
|
pathRewrites: [
|
||||||
|
{ from: 'if [ -d "$HOME/.claude/skills/gstack/.git" ]', to: 'if [ -d "$(cat "$HOME/.gemini/antigravity-cli/skills/gstack/.source-path" 2>/dev/null)/.git" ]' },
|
||||||
|
{ from: 'INSTALL_DIR="$HOME/.claude/skills/gstack"', to: 'INSTALL_DIR="$(cat "$HOME/.gemini/antigravity-cli/skills/gstack/.source-path")"' },
|
||||||
|
{ from: '$HOME/.claude/skills/gstack', to: '$HOME/.gemini/antigravity-cli/skills/gstack' },
|
||||||
|
{ from: '~/.claude/skills/gstack', to: '~/.gemini/antigravity-cli/skills/gstack' },
|
||||||
|
{ from: '.claude/skills/gstack', to: '.agents/skills/gstack' },
|
||||||
|
{ from: '.claude/skills/review', to: '.agents/skills/gstack/review' },
|
||||||
|
{ from: '.claude/skills', to: '.agents/skills' },
|
||||||
|
{ from: 'CLAUDE.md', to: 'AGENTS.md' },
|
||||||
|
],
|
||||||
|
toolRewrites: preambleToolGlossary(ANTIGRAVITY_TOOL_GLOSSARY),
|
||||||
|
|
||||||
|
suppressedResolvers: ['REVIEW_ARMY', ...GBRAIN_RESOLVERS],
|
||||||
|
|
||||||
|
runtimeRoot: {
|
||||||
|
globalSymlinks: ['bin', 'lib', 'browse/dist', 'browse/bin', 'design/dist', 'make-pdf/dist', 'freeze/bin', 'careful/bin', 'gstack-upgrade', 'ETHOS.md'],
|
||||||
|
globalFiles: {
|
||||||
|
'review': ['checklist.md', 'design-checklist.md', 'greptile-triage.md', 'TODOS-format.md'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default agy;
|
||||||
+4
-3
@@ -17,9 +17,10 @@ import openclaw from './openclaw';
|
|||||||
import hermes from './hermes';
|
import hermes from './hermes';
|
||||||
import gbrain from './gbrain';
|
import gbrain from './gbrain';
|
||||||
import copilot from './copilot';
|
import copilot from './copilot';
|
||||||
|
import agy from './agy';
|
||||||
|
|
||||||
/** All registered host configs. Add new hosts here. */
|
/** All registered host configs. Add new hosts here. */
|
||||||
export const ALL_HOST_CONFIGS: HostConfig[] = [claude, codex, factory, kiro, opencode, slate, cursor, openclaw, hermes, gbrain, copilot];
|
export const ALL_HOST_CONFIGS: HostConfig[] = [claude, codex, factory, kiro, opencode, slate, cursor, openclaw, hermes, gbrain, copilot, agy];
|
||||||
|
|
||||||
/** Map from host name to config. */
|
/** Map from host name to config. */
|
||||||
export const HOST_CONFIG_MAP: Record<string, HostConfig> = Object.fromEntries(
|
export const HOST_CONFIG_MAP: Record<string, HostConfig> = Object.fromEntries(
|
||||||
@@ -43,7 +44,7 @@ export function getHostConfig(name: string): HostConfig {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolve a host name from a CLI argument, handling aliases.
|
* Resolve a host name from a CLI argument, handling aliases.
|
||||||
* e.g., 'agents' → 'codex', 'droid' → 'factory'
|
* e.g., 'agents' → 'codex', 'droid' → 'factory', 'antigravity' → 'agy'
|
||||||
*/
|
*/
|
||||||
export function resolveHostArg(arg: string): string {
|
export function resolveHostArg(arg: string): string {
|
||||||
// Direct name match
|
// Direct name match
|
||||||
@@ -66,4 +67,4 @@ export function getExternalHosts(): HostConfig[] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Re-export individual configs for direct import
|
// Re-export individual configs for direct import
|
||||||
export { claude, codex, factory, kiro, opencode, slate, cursor, openclaw, hermes, gbrain, copilot };
|
export { claude, codex, factory, kiro, opencode, slate, cursor, openclaw, hermes, gbrain, copilot, agy };
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "gstack",
|
"name": "gstack",
|
||||||
"version": "1.91.66",
|
"version": "1.91.67",
|
||||||
"description": "Garry's Stack — Claude Code skills + fast headless browser. One repo, one install, entire AI engineering workflow.",
|
"description": "Garry's Stack — Claude Code skills + fast headless browser. One repo, one install, entire AI engineering workflow.",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ Usage: ./setup [options]
|
|||||||
Options:
|
Options:
|
||||||
--host <name> Install for one host. Default: claude. Hosts by tier:
|
--host <name> Install for one host. Default: claude. Hosts by tier:
|
||||||
full: claude
|
full: claude
|
||||||
experimental: codex, kiro, factory, opencode, cursor, copilot
|
experimental: codex, kiro, factory, opencode, cursor, copilot, agy
|
||||||
instruction-only: slate, openclaw, hermes, gbrain
|
instruction-only: slate, openclaw, hermes, gbrain
|
||||||
auto: every installed agent that setup detects
|
auto: every installed agent that setup detects
|
||||||
An explicit --host never changes another host's install.
|
An explicit --host never changes another host's install.
|
||||||
@@ -193,7 +193,7 @@ _setup_unknown_option() {
|
|||||||
}
|
}
|
||||||
while [ $# -gt 0 ]; do
|
while [ $# -gt 0 ]; do
|
||||||
case "$1" in
|
case "$1" in
|
||||||
--host) [ -z "$2" ] && echo "Missing value for --host (expected claude, codex, kiro, factory, opencode, cursor, copilot, slate, openclaw, hermes, gbrain, or auto)" >&2 && exit 1; HOST="$2"; HOST_FLAG_SET=1; shift 2 ;;
|
--host) [ -z "$2" ] && echo "Missing value for --host (expected claude, codex, kiro, factory, opencode, cursor, copilot, agy, slate, openclaw, hermes, gbrain, or auto)" >&2 && exit 1; HOST="$2"; HOST_FLAG_SET=1; shift 2 ;;
|
||||||
--host=*) HOST="${1#--host=}"; HOST_FLAG_SET=1; shift ;;
|
--host=*) HOST="${1#--host=}"; HOST_FLAG_SET=1; shift ;;
|
||||||
--model) [ -z "$2" ] && echo "Missing value for --model" >&2 && exit 1; MODEL_OVERRIDE="$2"; MODEL_OVERRIDE_SET=1; shift 2 ;;
|
--model) [ -z "$2" ] && echo "Missing value for --model" >&2 && exit 1; MODEL_OVERRIDE="$2"; MODEL_OVERRIDE_SET=1; shift 2 ;;
|
||||||
--model=*) MODEL_OVERRIDE="${1#--model=}"; MODEL_OVERRIDE_SET=1; shift ;;
|
--model=*) MODEL_OVERRIDE="${1#--model=}"; MODEL_OVERRIDE_SET=1; shift ;;
|
||||||
@@ -234,8 +234,9 @@ print_instruction_tier() {
|
|||||||
echo ""
|
echo ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[ "$HOST" = "antigravity" ] && HOST="agy" # Antigravity CLI is named after its binary
|
||||||
case "$HOST" in
|
case "$HOST" in
|
||||||
claude|codex|kiro|factory|opencode|cursor|copilot|auto) ;;
|
claude|codex|kiro|factory|opencode|cursor|copilot|agy|auto) ;;
|
||||||
slate)
|
slate)
|
||||||
echo ""
|
echo ""
|
||||||
echo "Slate is not yet a first-class install target (docs/designs/SLATE_HOST.md —"
|
echo "Slate is not yet a first-class install target (docs/designs/SLATE_HOST.md —"
|
||||||
@@ -287,7 +288,7 @@ case "$HOST" in
|
|||||||
echo ""
|
echo ""
|
||||||
exit 0 ;;
|
exit 0 ;;
|
||||||
*)
|
*)
|
||||||
echo "Unknown --host value: $HOST — nothing was installed or changed. Expected one of: claude (default), codex, kiro, factory, opencode, cursor, copilot, slate, openclaw, hermes, gbrain, or auto. Tiers: ./setup --help" >&2; exit 1 ;;
|
echo "Unknown --host value: $HOST — nothing was installed or changed. Expected one of: claude (default), codex, kiro, factory, opencode, cursor, copilot, agy (alias antigravity), slate, openclaw, hermes, gbrain, or auto. Tiers: ./setup --help" >&2; exit 1 ;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
# Bun versions come from one sourced file (floor and tested; E1).
|
# Bun versions come from one sourced file (floor and tested; E1).
|
||||||
@@ -481,6 +482,8 @@ CURSOR_GSTACK="$CURSOR_SKILLS/gstack"
|
|||||||
KIRO_SKILLS="$HOME/.kiro/skills"
|
KIRO_SKILLS="$HOME/.kiro/skills"
|
||||||
COPILOT_SKILLS="$HOME/.copilot/skills"
|
COPILOT_SKILLS="$HOME/.copilot/skills"
|
||||||
COPILOT_GSTACK="$COPILOT_SKILLS/gstack"
|
COPILOT_GSTACK="$COPILOT_SKILLS/gstack"
|
||||||
|
AGY_SKILLS="$HOME/.gemini/antigravity-cli/skills"
|
||||||
|
AGY_GSTACK="$AGY_SKILLS/gstack"
|
||||||
|
|
||||||
SKILLS_BASENAME="$(basename "$INSTALL_SKILLS_DIR")"
|
SKILLS_BASENAME="$(basename "$INSTALL_SKILLS_DIR")"
|
||||||
SKILLS_PARENT_BASENAME="$(basename "$(dirname "$INSTALL_SKILLS_DIR")")"
|
SKILLS_PARENT_BASENAME="$(basename "$(dirname "$INSTALL_SKILLS_DIR")")"
|
||||||
@@ -1299,6 +1302,7 @@ INSTALL_FACTORY=0
|
|||||||
INSTALL_OPENCODE=0
|
INSTALL_OPENCODE=0
|
||||||
INSTALL_CURSOR=0
|
INSTALL_CURSOR=0
|
||||||
INSTALL_COPILOT=0
|
INSTALL_COPILOT=0
|
||||||
|
INSTALL_AGY=0
|
||||||
if [ "$HOST" = "auto" ]; then
|
if [ "$HOST" = "auto" ]; then
|
||||||
command -v claude >/dev/null 2>&1 && INSTALL_CLAUDE=1
|
command -v claude >/dev/null 2>&1 && INSTALL_CLAUDE=1
|
||||||
command -v codex >/dev/null 2>&1 && INSTALL_CODEX=1
|
command -v codex >/dev/null 2>&1 && INSTALL_CODEX=1
|
||||||
@@ -1310,8 +1314,13 @@ if [ "$HOST" = "auto" ]; then
|
|||||||
command -v cursor >/dev/null 2>&1 && INSTALL_CURSOR=1
|
command -v cursor >/dev/null 2>&1 && INSTALL_CURSOR=1
|
||||||
[ -d "$HOME/.cursor" ] && INSTALL_CURSOR=1
|
[ -d "$HOME/.cursor" ] && INSTALL_CURSOR=1
|
||||||
command -v copilot >/dev/null 2>&1 && INSTALL_COPILOT=1
|
command -v copilot >/dev/null 2>&1 && INSTALL_COPILOT=1
|
||||||
|
# agy's installer puts the binary in ~/.local/bin, which may not be on PATH;
|
||||||
|
# ~/.gemini/antigravity-cli is agy's own config dir. A bare ~/.gemini is
|
||||||
|
# Gemini CLI's and never selects Antigravity.
|
||||||
|
command -v agy >/dev/null 2>&1 && INSTALL_AGY=1
|
||||||
|
[ -d "$HOME/.gemini/antigravity-cli" ] && INSTALL_AGY=1
|
||||||
# If none found, default to claude
|
# 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 ] && [ "$INSTALL_CURSOR" -eq 0 ] && [ "$INSTALL_COPILOT" -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_CURSOR" -eq 0 ] && [ "$INSTALL_COPILOT" -eq 0 ] && [ "$INSTALL_AGY" -eq 0 ]; then
|
||||||
INSTALL_CLAUDE=1
|
INSTALL_CLAUDE=1
|
||||||
fi
|
fi
|
||||||
elif [ "$HOST" = "claude" ]; then
|
elif [ "$HOST" = "claude" ]; then
|
||||||
@@ -1328,13 +1337,15 @@ elif [ "$HOST" = "cursor" ]; then
|
|||||||
INSTALL_CURSOR=1
|
INSTALL_CURSOR=1
|
||||||
elif [ "$HOST" = "copilot" ]; then
|
elif [ "$HOST" = "copilot" ]; then
|
||||||
INSTALL_COPILOT=1
|
INSTALL_COPILOT=1
|
||||||
|
elif [ "$HOST" = "agy" ]; then
|
||||||
|
INSTALL_AGY=1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# A host that passes --host validation but sets no INSTALL_* flag would
|
# A host that passes --host validation but sets no INSTALL_* flag would
|
||||||
# silently configure nothing and exit 0 (the #2361 slate failure class).
|
# silently configure nothing and exit 0 (the #2361 slate failure class).
|
||||||
# Fail loudly if a future host lands in the accept-list without a dispatch arm.
|
# Fail loudly if a future host lands in the accept-list without a dispatch arm.
|
||||||
if [ "$HOST" != "auto" ] && [ "$INSTALL_CLAUDE" -eq 0 ] && [ "$INSTALL_CODEX" -eq 0 ] && [ "$INSTALL_KIRO" -eq 0 ] && [ "$INSTALL_FACTORY" -eq 0 ] && [ "$INSTALL_OPENCODE" -eq 0 ] && [ "$INSTALL_CURSOR" -eq 0 ] && [ "$INSTALL_COPILOT" -eq 0 ]; then
|
if [ "$HOST" != "auto" ] && [ "$INSTALL_CLAUDE" -eq 0 ] && [ "$INSTALL_CODEX" -eq 0 ] && [ "$INSTALL_KIRO" -eq 0 ] && [ "$INSTALL_FACTORY" -eq 0 ] && [ "$INSTALL_OPENCODE" -eq 0 ] && [ "$INSTALL_CURSOR" -eq 0 ] && [ "$INSTALL_COPILOT" -eq 0 ] && [ "$INSTALL_AGY" -eq 0 ]; then
|
||||||
echo "Error: no install arm exists for host '$HOST' — it passed --host validation but sets no INSTALL_* flag, so setup would configure nothing and exit 0. This is a setup bug. Valid install targets: claude, codex, kiro, factory, opencode, cursor, copilot (informational: slate, openclaw, hermes, gbrain)." >&2
|
echo "Error: no install arm exists for host '$HOST' — it passed --host validation but sets no INSTALL_* flag, so setup would configure nothing and exit 0. This is a setup bug. Valid install targets: claude, codex, kiro, factory, opencode, cursor, copilot, agy (informational: slate, openclaw, hermes, gbrain)." >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -1729,6 +1740,7 @@ _RENDER_HOSTS_SELECTED=""
|
|||||||
[ "$INSTALL_OPENCODE" -eq 1 ] && _RENDER_HOSTS_SELECTED="$_RENDER_HOSTS_SELECTED opencode"
|
[ "$INSTALL_OPENCODE" -eq 1 ] && _RENDER_HOSTS_SELECTED="$_RENDER_HOSTS_SELECTED opencode"
|
||||||
[ "$INSTALL_CURSOR" -eq 1 ] && _RENDER_HOSTS_SELECTED="$_RENDER_HOSTS_SELECTED cursor"
|
[ "$INSTALL_CURSOR" -eq 1 ] && _RENDER_HOSTS_SELECTED="$_RENDER_HOSTS_SELECTED cursor"
|
||||||
[ "$INSTALL_COPILOT" -eq 1 ] && _RENDER_HOSTS_SELECTED="$_RENDER_HOSTS_SELECTED copilot"
|
[ "$INSTALL_COPILOT" -eq 1 ] && _RENDER_HOSTS_SELECTED="$_RENDER_HOSTS_SELECTED copilot"
|
||||||
|
[ "$INSTALL_AGY" -eq 1 ] && _RENDER_HOSTS_SELECTED="$_RENDER_HOSTS_SELECTED agy"
|
||||||
# shellcheck disable=SC2086 # the host list splits on purpose
|
# shellcheck disable=SC2086 # the host list splits on purpose
|
||||||
if ! gstack_render_hosts_add "$SOURCE_GSTACK_DIR" $_RENDER_HOSTS_SELECTED; then
|
if ! gstack_render_hosts_add "$SOURCE_GSTACK_DIR" $_RENDER_HOSTS_SELECTED; then
|
||||||
echo " warning: could not write $(gstack_render_hosts_file "$SOURCE_GSTACK_DIR"); this build renders every host and nothing is pruned" >&2
|
echo " warning: could not write $(gstack_render_hosts_file "$SOURCE_GSTACK_DIR"); this build renders every host and nothing is pruned" >&2
|
||||||
@@ -1994,6 +2006,16 @@ if [ "$INSTALL_COPILOT" -eq 1 ] && [ "$NEEDS_BUILD" -eq 0 ]; then
|
|||||||
)
|
)
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# 1g. Generate .agy/ Antigravity CLI 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 ${_DISABLED_CSV:+"--disabled-skills=$_DISABLED_CSV"}
|
||||||
|
)
|
||||||
|
fi
|
||||||
|
|
||||||
# 2. Ensure Playwright's Chromium is available
|
# 2. Ensure Playwright's Chromium is available
|
||||||
_PLAYWRIGHT_PLATFORM_OVERRIDE=""
|
_PLAYWRIGHT_PLATFORM_OVERRIDE=""
|
||||||
_PLAYWRIGHT_UNSUPPORTED_ARCH=""
|
_PLAYWRIGHT_UNSUPPORTED_ARCH=""
|
||||||
@@ -3213,6 +3235,83 @@ link_copilot_skill_dirs() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ─── Helper: ~/.gemini/antigravity-cli/skills/gstack runtime root ────────────
|
||||||
|
# Same shape as the Copilot root: root SKILL.md + runtime assets, with the
|
||||||
|
# .source-path _link_runtime_dists writes for /gstack-upgrade. Antigravity CLI
|
||||||
|
# and Gemini CLI share ~/.gemini; setup writes only gstack entries under
|
||||||
|
# ~/.gemini/antigravity-cli/skills, never Gemini CLI's ~/.gemini/skills.
|
||||||
|
# Built through _activate_runtime_root.
|
||||||
|
create_agy_runtime_root() {
|
||||||
|
local gstack_dir="$1"
|
||||||
|
local agy_gstack="$2"
|
||||||
|
local agy_dir="$gstack_dir/.agy/skills"
|
||||||
|
local asset f
|
||||||
|
|
||||||
|
mkdir -p "$agy_gstack" "$agy_gstack/browse" "$agy_gstack/gstack-upgrade" "$agy_gstack/review"
|
||||||
|
if [ -f "$agy_dir/gstack/SKILL.md" ]; then
|
||||||
|
_copy_skill_md "$agy_dir/gstack/SKILL.md" "$agy_gstack/SKILL.md"
|
||||||
|
fi
|
||||||
|
for asset in bin lib browse/dist browse/bin; do
|
||||||
|
if [ -d "$gstack_dir/$asset" ]; then
|
||||||
|
_link_or_copy "$gstack_dir/$asset" "$agy_gstack/$asset"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
_link_runtime_dists "$gstack_dir" "$agy_gstack"
|
||||||
|
if [ -f "$agy_dir/gstack-upgrade/SKILL.md" ]; then
|
||||||
|
_copy_skill_md "$agy_dir/gstack-upgrade/SKILL.md" "$agy_gstack/gstack-upgrade/SKILL.md"
|
||||||
|
fi
|
||||||
|
_copy_runtime_skill_refs "$agy_dir" "$agy_gstack"
|
||||||
|
if [ -f "$gstack_dir/ETHOS.md" ]; then
|
||||||
|
_link_or_copy "$gstack_dir/ETHOS.md" "$agy_gstack/ETHOS.md"
|
||||||
|
fi
|
||||||
|
if [ -f "$gstack_dir/supabase/config.sh" ]; then
|
||||||
|
mkdir -p "$agy_gstack/supabase"
|
||||||
|
_link_or_copy "$gstack_dir/supabase/config.sh" "$agy_gstack/supabase/config.sh"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Frontmatter names already equal the gstack-<skill> directories
|
||||||
|
# (nameMatchesDirectory), so no name patching. Fails when nothing was linked.
|
||||||
|
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_cmd run gen:skill-docs --host agy ${_DISABLED_CSV:+"--disabled-skills=$_DISABLED_CSV"} )
|
||||||
|
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
|
||||||
|
|
||||||
|
for skill_dir in "$agy_dir"/gstack*/; do
|
||||||
|
if [ -f "$skill_dir/SKILL.md" ]; then
|
||||||
|
skill_name="$(basename "$skill_dir")"
|
||||||
|
[ "$skill_name" = "gstack" ] && continue
|
||||||
|
case "${_DISABLED_SKILLS:- }" in *" ${skill_name#gstack-} "*) _remove_disabled_host_entry "$skills_dir/$skill_name"; continue ;; esac
|
||||||
|
target="$skills_dir/$skill_name"
|
||||||
|
if [ "$IS_WINDOWS" -eq 1 ] || [ -L "$target" ] || [ ! -e "$target" ]; then
|
||||||
|
if _owned_for_windows_refresh "$target"; then
|
||||||
|
_link_or_copy "$skill_dir" "$target"
|
||||||
|
linked+=("$skill_name")
|
||||||
|
else
|
||||||
|
echo " left in place (existing dir not gstack-managed — no generated banner): $target" >&2
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
if [ ${#linked[@]} -gt 0 ]; then
|
||||||
|
echo " linked skills: ${linked[*]}"
|
||||||
|
fi
|
||||||
|
if ! ls "$skills_dir"/gstack-*/SKILL.md >/dev/null 2>&1; then
|
||||||
|
echo " error: no gstack skills are installed in $skills_dir" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
# 4. Install for Claude (default)
|
# 4. Install for Claude (default)
|
||||||
# _claude_scope SKILLS_DIR — "global" for the user's Claude skills dir, else
|
# _claude_scope SKILLS_DIR — "global" for the user's Claude skills dir, else
|
||||||
# "project <root>".
|
# "project <root>".
|
||||||
@@ -3608,6 +3707,25 @@ if [ "$INSTALL_COPILOT" -eq 1 ]; then
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# 6f. Install for Antigravity CLI (agy)
|
||||||
|
if [ "$INSTALL_AGY" -eq 1 ]; then
|
||||||
|
if _sidecar_root_user_owned "$AGY_GSTACK"; then
|
||||||
|
echo " left in place (existing dir not gstack-managed — no generated banner): $AGY_GSTACK" >&2
|
||||||
|
_setup_row agy global "$AGY_SKILLS" - "$SETUP_VERSION" skipped "left alone: $AGY_GSTACK is not gstack-managed (no generated banner)"
|
||||||
|
else
|
||||||
|
_setup_arm_begin agy global "$AGY_SKILLS"
|
||||||
|
mkdir -p "$AGY_SKILLS"
|
||||||
|
_prune_stale_generated "$SOURCE_GSTACK_DIR" "$SOURCE_GSTACK_DIR/.agy/skills" "$AGY_SKILLS"
|
||||||
|
link_agy_skill_dirs "$SOURCE_GSTACK_DIR" "$AGY_SKILLS"
|
||||||
|
_activate_runtime_root create_agy_runtime_root "$SOURCE_GSTACK_DIR" "$AGY_GSTACK"
|
||||||
|
_setup_arm_publish agy global - "$AGY_SKILLS" "$AGY_GSTACK" -
|
||||||
|
echo "gstack ready (agy)."
|
||||||
|
echo " browse: $BROWSE_BIN"
|
||||||
|
_browser_hint
|
||||||
|
echo " agy skills: $AGY_SKILLS (invoke as /gstack-<skill>)"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
# 7. Create .agents/ sidecar symlinks for the real Codex skill target.
|
# 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,
|
# 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.
|
# so the runtime assets must live there for both global and repo-local installs.
|
||||||
|
|||||||
+1195
File diff suppressed because it is too large.
Load diff
@@ -3597,7 +3597,7 @@ describe('setup script validation', () => {
|
|||||||
expect(setupContent).toContain('--host');
|
expect(setupContent).toContain('--host');
|
||||||
// #2361: slate moved OUT of the install accept-list (it was accepted but
|
// #2361: slate moved OUT of the install accept-list (it was accepted but
|
||||||
// never dispatched — a silent exit-0 no-op) into an informational arm.
|
// never dispatched — a silent exit-0 no-op) into an informational arm.
|
||||||
expect(setupContent).toContain('claude|codex|kiro|factory|opencode|cursor|copilot|auto');
|
expect(setupContent).toContain('claude|codex|kiro|factory|opencode|cursor|copilot|agy|auto');
|
||||||
expect(setupContent).toMatch(/^ {2}slate\)/m);
|
expect(setupContent).toMatch(/^ {2}slate\)/m);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -23,9 +23,11 @@ import {
|
|||||||
slate,
|
slate,
|
||||||
cursor,
|
cursor,
|
||||||
openclaw,
|
openclaw,
|
||||||
|
agy,
|
||||||
} from '../hosts/index';
|
} from '../hosts/index';
|
||||||
import { HOST_PATHS } from '../scripts/resolvers/types';
|
import { HOST_PATHS } from '../scripts/resolvers/types';
|
||||||
import { RESOLVERS } from '../scripts/resolvers';
|
import { RESOLVERS } from '../scripts/resolvers';
|
||||||
|
import { expectTokens } from './helpers/prompt-structure';
|
||||||
import { runGeneration } from '../scripts/gen-skill-docs';
|
import { runGeneration } from '../scripts/gen-skill-docs';
|
||||||
|
|
||||||
const ROOT = path.resolve(import.meta.dir, '..');
|
const ROOT = path.resolve(import.meta.dir, '..');
|
||||||
@@ -34,8 +36,8 @@ const RESOLVER_NAMES = new Set(Object.keys(RESOLVERS));
|
|||||||
// ─── hosts/index.ts ─────────────────────────────────────────
|
// ─── hosts/index.ts ─────────────────────────────────────────
|
||||||
|
|
||||||
describe('hosts/index.ts', () => {
|
describe('hosts/index.ts', () => {
|
||||||
test('ALL_HOST_CONFIGS has 11 hosts', () => {
|
test('ALL_HOST_CONFIGS has 12 hosts', () => {
|
||||||
expect(ALL_HOST_CONFIGS.length).toBe(11);
|
expect(ALL_HOST_CONFIGS.length).toBe(12);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('ALL_HOST_NAMES matches config names', () => {
|
test('ALL_HOST_NAMES matches config names', () => {
|
||||||
@@ -57,6 +59,7 @@ describe('hosts/index.ts', () => {
|
|||||||
expect(slate.name).toBe('slate');
|
expect(slate.name).toBe('slate');
|
||||||
expect(cursor.name).toBe('cursor');
|
expect(cursor.name).toBe('cursor');
|
||||||
expect(openclaw.name).toBe('openclaw');
|
expect(openclaw.name).toBe('openclaw');
|
||||||
|
expect(agy.name).toBe('agy');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('getHostConfig returns correct config', () => {
|
test('getHostConfig returns correct config', () => {
|
||||||
@@ -78,6 +81,7 @@ describe('hosts/index.ts', () => {
|
|||||||
test('resolveHostArg resolves aliases', () => {
|
test('resolveHostArg resolves aliases', () => {
|
||||||
expect(resolveHostArg('agents')).toBe('codex');
|
expect(resolveHostArg('agents')).toBe('codex');
|
||||||
expect(resolveHostArg('droid')).toBe('factory');
|
expect(resolveHostArg('droid')).toBe('factory');
|
||||||
|
expect(resolveHostArg('antigravity')).toBe('agy');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('resolveHostArg throws on unknown alias', () => {
|
test('resolveHostArg throws on unknown alias', () => {
|
||||||
@@ -445,7 +449,7 @@ describe('golden-file regression', () => {
|
|||||||
const GOLDEN_OUT = fs.mkdtempSync(path.join(os.tmpdir(), 'gstack-golden-out-'));
|
const GOLDEN_OUT = fs.mkdtempSync(path.join(os.tmpdir(), 'gstack-golden-out-'));
|
||||||
|
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
for (const host of ['codex', 'factory']) {
|
for (const host of ['codex', 'factory', 'agy']) {
|
||||||
const result = Bun.spawnSync(
|
const result = Bun.spawnSync(
|
||||||
['bun', 'run', 'scripts/gen-skill-docs.ts', '--host', host, '--out-dir', GOLDEN_OUT],
|
['bun', 'run', 'scripts/gen-skill-docs.ts', '--host', host, '--out-dir', GOLDEN_OUT],
|
||||||
{ cwd: ROOT, timeout: 120_000 },
|
{ cwd: ROOT, timeout: 120_000 },
|
||||||
@@ -498,14 +502,21 @@ describe('golden-file regression', () => {
|
|||||||
const current = fs.readFileSync(path.join(GOLDEN_OUT, '.factory', 'skills', 'gstack-ship', 'SKILL.md'), 'utf-8');
|
const current = fs.readFileSync(path.join(GOLDEN_OUT, '.factory', 'skills', 'gstack-ship', 'SKILL.md'), 'utf-8');
|
||||||
expect(current).toBe(golden);
|
expect(current).toBe(golden);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('Antigravity ship skill matches golden baseline', () => {
|
||||||
|
const golden = fs.readFileSync(path.join(GOLDEN_DIR, 'agy-ship-SKILL.md'), 'utf-8');
|
||||||
|
const current = fs.readFileSync(path.join(GOLDEN_OUT, '.agy', 'skills', 'gstack-ship', 'SKILL.md'), 'utf-8');
|
||||||
|
expect(current).toBe(golden);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// ─── Individual host config correctness ─────────────────────
|
// ─── Individual host config correctness ─────────────────────
|
||||||
|
|
||||||
describe('host config correctness', () => {
|
describe('host config correctness', () => {
|
||||||
test('Codex host renders with generic GPT overlay while existing hosts retain Claude overlay', () => {
|
test('Codex renders the GPT overlay, Antigravity the Gemini overlay, every other host the Claude overlay', () => {
|
||||||
expect(codex.defaultModel).toBe('gpt');
|
expect(codex.defaultModel).toBe('gpt');
|
||||||
for (const host of ALL_HOST_CONFIGS.filter(h => h.name !== 'codex')) {
|
expect(agy.defaultModel).toBe('gemini');
|
||||||
|
for (const host of ALL_HOST_CONFIGS.filter(h => h.name !== 'codex' && h.name !== 'agy')) {
|
||||||
expect(host.defaultModel).toBe('claude');
|
expect(host.defaultModel).toBe('claude');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -756,6 +767,49 @@ describe('Copilot host render (#393)', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('Antigravity CLI host render', () => {
|
||||||
|
const OUT = fs.mkdtempSync(path.join(os.tmpdir(), 'gstack-agy-'));
|
||||||
|
beforeAll(() => {
|
||||||
|
const r = Bun.spawnSync(['bun', 'run', 'scripts/gen-skill-docs.ts', '--host', 'agy', '--out-dir', OUT], { cwd: ROOT, timeout: 120_000 });
|
||||||
|
if (r.exitCode !== 0) throw new Error(r.stderr.toString());
|
||||||
|
});
|
||||||
|
afterAll(() => fs.rmSync(OUT, { recursive: true, force: true }));
|
||||||
|
const dir = path.join(OUT, '.agy', 'skills');
|
||||||
|
const read = (skill: string) => fs.readFileSync(path.join(dir, skill, 'SKILL.md'), 'utf8');
|
||||||
|
|
||||||
|
test('paths name the Antigravity CLI install, never Claude, Codex or Gemini CLI skill dirs', () => {
|
||||||
|
for (const skill of ['gstack-review', 'gstack-ship', 'gstack-careful', 'gstack-upgrade']) {
|
||||||
|
const md = read(skill);
|
||||||
|
expect(md).not.toContain('~/.claude/skills/gstack');
|
||||||
|
expect(md).not.toContain('$HOME/.claude/skills/gstack');
|
||||||
|
expect(md).not.toContain('.codex/skills');
|
||||||
|
expect(md).not.toMatch(/\.gemini\/(?:config\/)?skills/);
|
||||||
|
}
|
||||||
|
expect(read('gstack-review')).toContain('_r=~/.gemini/antigravity-cli/skills/gstack');
|
||||||
|
expect(read('gstack-upgrade')).toContain('$HOME/.gemini/antigravity-cli/skills/gstack/.source-path');
|
||||||
|
expect(read('gstack-upgrade')).toContain('./setup --host agy --refresh-registered');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('the tool-name glossary names the documented tools ahead of the preamble STATUS rules', () => {
|
||||||
|
const md = read('gstack-review');
|
||||||
|
const glossary = md.indexOf('**Antigravity tool names:**');
|
||||||
|
expect(glossary).toBeGreaterThan(-1);
|
||||||
|
expect(glossary).toBeLessThan(md.indexOf('Read the echoed `KEY: value` STATUS lines'));
|
||||||
|
const line = md.slice(glossary, md.indexOf('\n', glossary));
|
||||||
|
expectTokens(line, ['`ask_question`', '`run_command`', '`view_file`', '`write_to_file`', '`replace_file_content`', '`grep_search`', '`find_by_name`', '`invoke_subagent`']);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('frontmatter keeps only name and description, and every name equals its directory', () => {
|
||||||
|
const names = fs.readdirSync(dir).filter(d => fs.existsSync(path.join(dir, d, 'SKILL.md')));
|
||||||
|
expect(names.length).toBeGreaterThan(20);
|
||||||
|
for (const name of names) {
|
||||||
|
const fm = read(name).split('\n---\n')[0];
|
||||||
|
expect(fm.match(/^name:\s*(\S+)/m)![1]).toBe(name);
|
||||||
|
expect(fm.split('\n').filter(l => /^[a-z-]+:/.test(l)).map(l => l.split(':')[0])).toEqual(['name', 'description']);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('host renders name the host\'s own tools and identities (#2626, #2015, #2825, #2338)', () => {
|
describe('host renders name the host\'s own tools and identities (#2626, #2015, #2825, #2338)', () => {
|
||||||
const OUT = fs.mkdtempSync(path.join(os.tmpdir(), 'gstack-host-tools-'));
|
const OUT = fs.mkdtempSync(path.join(os.tmpdir(), 'gstack-host-tools-'));
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
|
|||||||
@@ -29,9 +29,10 @@ const DISCOVERY: Record<string, string> = {
|
|||||||
opencode: '.config/opencode/skills',
|
opencode: '.config/opencode/skills',
|
||||||
cursor: '.cursor/skills',
|
cursor: '.cursor/skills',
|
||||||
copilot: '.copilot/skills',
|
copilot: '.copilot/skills',
|
||||||
|
agy: '.gemini/antigravity-cli/skills',
|
||||||
};
|
};
|
||||||
/** Every directory any host (or a shared agents layout) reads. */
|
/** Every directory any host (or a shared agents layout) reads; .gemini/skills is Gemini CLI's. */
|
||||||
const ALL_DISCOVERY_DIRS = [...Object.values(DISCOVERY), '.agents/skills', '.copilot/skills', '.hermes/skills'];
|
const ALL_DISCOVERY_DIRS = [...Object.values(DISCOVERY), '.agents/skills', '.copilot/skills', '.hermes/skills', '.gemini/skills'];
|
||||||
|
|
||||||
const installable = ALL_HOST_CONFIGS.filter(c => c.tier !== 'instruction-only').map(c => c.name);
|
const installable = ALL_HOST_CONFIGS.filter(c => c.tier !== 'instruction-only').map(c => c.name);
|
||||||
const instructionOnly = ALL_HOST_CONFIGS.filter(c => c.tier === 'instruction-only').map(c => c.name);
|
const instructionOnly = ALL_HOST_CONFIGS.filter(c => c.tier === 'instruction-only').map(c => c.name);
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import { runGeneration } from '../scripts/gen-skill-docs';
|
|||||||
import { cleanupFixtures, cleanupSeed, makeFixture, makeSource, put, runSetup, type Fixture } from './helpers/install-fixture';
|
import { cleanupFixtures, cleanupSeed, makeFixture, makeSource, put, runSetup, type Fixture } from './helpers/install-fixture';
|
||||||
|
|
||||||
const ROOT = path.resolve(import.meta.dir, '..');
|
const ROOT = path.resolve(import.meta.dir, '..');
|
||||||
const OTHER_DIRS = ['.agents', '.factory', '.kiro', '.opencode', '.cursor', '.copilot', '.slate', '.openclaw', '.hermes', '.gbrain'];
|
const OTHER_DIRS = ['.agents', '.factory', '.kiro', '.opencode', '.cursor', '.copilot', '.agy', '.slate', '.openclaw', '.hermes', '.gbrain'];
|
||||||
|
|
||||||
afterEach(cleanupFixtures);
|
afterEach(cleanupFixtures);
|
||||||
afterAll(cleanupSeed);
|
afterAll(cleanupSeed);
|
||||||
@@ -68,7 +68,7 @@ describe.skipIf(process.platform === 'win32')('setup prunes unrecorded host rend
|
|||||||
expect(fs.existsSync(path.join(f.dir, 'outside'))).toBe(true);
|
expect(fs.existsSync(path.join(f.dir, 'outside'))).toBe(true);
|
||||||
expect(fs.existsSync(path.join(src, '.cursor/skills/gstack/bin'))).toBe(false);
|
expect(fs.existsSync(path.join(src, '.cursor/skills/gstack/bin'))).toBe(false);
|
||||||
expect(fs.existsSync(path.join(src, 'bin/gstack-config'))).toBe(true);
|
expect(fs.existsSync(path.join(src, 'bin/gstack-config'))).toBe(true);
|
||||||
for (const dir of ['.agents', '.opencode', '.copilot', '.slate', '.openclaw', '.hermes', '.gbrain']) expect(fs.existsSync(path.join(src, dir)), dir).toBe(false);
|
for (const dir of ['.agents', '.opencode', '.copilot', '.agy', '.slate', '.openclaw', '.hermes', '.gbrain']) expect(fs.existsSync(path.join(src, dir)), dir).toBe(false);
|
||||||
|
|
||||||
expect(r.stdout).toContain('kept .factory/notes.md: not proven generated (no gstack banner or byte match)');
|
expect(r.stdout).toContain('kept .factory/notes.md: not proven generated (no gstack banner or byte match)');
|
||||||
expect(r.stdout).toContain('kept .kiro/skills/my-skill/SKILL.md: not proven generated');
|
expect(r.stdout).toContain('kept .kiro/skills/my-skill/SKILL.md: not proven generated');
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ const ROOT_BUILDERS: Record<string, string> = {
|
|||||||
opencode: 'create_opencode_runtime_root',
|
opencode: 'create_opencode_runtime_root',
|
||||||
cursor: 'create_cursor_runtime_root',
|
cursor: 'create_cursor_runtime_root',
|
||||||
copilot: 'create_copilot_runtime_root',
|
copilot: 'create_copilot_runtime_root',
|
||||||
|
agy: 'create_agy_runtime_root',
|
||||||
};
|
};
|
||||||
|
|
||||||
// Deferred, with the reason recorded in TODOS.md: /ship's measure loop runs
|
// Deferred, with the reason recorded in TODOS.md: /ship's measure loop runs
|
||||||
|
|||||||
@@ -185,6 +185,59 @@ describe.skipIf(process.platform === 'win32')('disabled_skills (#1206)', () => {
|
|||||||
}, 120_000);
|
}, 120_000);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe.skipIf(process.platform === 'win32')('Antigravity CLI shares ~/.gemini with Gemini CLI', () => {
|
||||||
|
const geminiCliFiles = (home: string) => ({
|
||||||
|
settings: readFileSync(join(home, '.gemini/settings.json'), 'utf8'),
|
||||||
|
skill: readFileSync(join(home, '.gemini/skills/my-skill/SKILL.md'), 'utf8'),
|
||||||
|
});
|
||||||
|
|
||||||
|
test.skipIf(Bun.which('agy') !== null)('auto-detect ignores a Gemini-CLI-only ~/.gemini and selects agy by its own config dir', () => {
|
||||||
|
const f = makeFixture();
|
||||||
|
const src = makeSource(f, join(f.home, 'gstack'));
|
||||||
|
put(join(f.home, '.gemini/settings.json'), '{"theme":"mine"}\n');
|
||||||
|
put(join(f.home, '.gemini/skills/my-skill/SKILL.md'), '---\nname: my-skill\ndescription: mine\n---\n');
|
||||||
|
const before = geminiCliFiles(f.home);
|
||||||
|
|
||||||
|
const geminiOnly = runSetup(f, join(src, 'setup'), ['--host', 'auto']);
|
||||||
|
expect(geminiOnly.status, geminiOnly.stdout + geminiOnly.stderr).toBe(0);
|
||||||
|
expect(existsSync(join(f.home, '.gemini/antigravity-cli'))).toBe(false);
|
||||||
|
expect(registryRows(f).map(row => row[0])).not.toContain('agy');
|
||||||
|
|
||||||
|
mkdirSync(join(f.home, '.gemini/antigravity-cli'), { recursive: true });
|
||||||
|
const withAgy = runSetup(f, join(src, 'setup'), ['--host', 'auto']);
|
||||||
|
expect(withAgy.status, withAgy.stdout + withAgy.stderr).toBe(0);
|
||||||
|
expect(existsSync(join(f.home, '.gemini/antigravity-cli/skills/gstack-review/SKILL.md'))).toBe(true);
|
||||||
|
expect(registryRows(f).map(row => row[0])).toContain('agy');
|
||||||
|
expect(geminiCliFiles(f.home)).toEqual(before);
|
||||||
|
expect(readdirSync(join(f.home, '.gemini/skills'))).toEqual(['my-skill']);
|
||||||
|
}, 120_000);
|
||||||
|
|
||||||
|
test('--host antigravity (alias) installs under antigravity-cli/skills only; uninstall removes only gstack entries', () => {
|
||||||
|
const f = makeFixture();
|
||||||
|
const src = makeSource(f, join(f.home, 'gstack'));
|
||||||
|
put(join(f.home, '.gemini/settings.json'), '{"theme":"mine"}\n');
|
||||||
|
put(join(f.home, '.gemini/skills/my-skill/SKILL.md'), '---\nname: my-skill\ndescription: mine\n---\n');
|
||||||
|
put(join(f.home, '.gemini/antigravity-cli/settings.json'), '{"agentMode":"plan"}\n');
|
||||||
|
put(join(f.home, '.gemini/antigravity-cli/skills/gstack-notes/SKILL.md'), '---\nname: gstack-notes\ndescription: my notes\n---\n');
|
||||||
|
const before = geminiCliFiles(f.home);
|
||||||
|
|
||||||
|
const r = runSetup(f, join(src, 'setup'), ['--host', 'antigravity']);
|
||||||
|
expect(r.status, r.stdout + r.stderr).toBe(0);
|
||||||
|
expect(registryRows(f).map(row => [row[0], row[3]])).toEqual([['agy', join(f.home, '.gemini/antigravity-cli/skills')]]);
|
||||||
|
expect(readFileSync(join(f.home, '.gemini/antigravity-cli/skills/gstack/.source-path'), 'utf8').trim()).toBe(src);
|
||||||
|
expect(readFileSync(join(src, '.gstack-installed-hosts'), 'utf8').split('\n')).toContain('agy');
|
||||||
|
expect(existsSync(join(src, '.agy/skills/gstack-review/SKILL.md'))).toBe(true);
|
||||||
|
expect(geminiCliFiles(f.home)).toEqual(before);
|
||||||
|
|
||||||
|
const un = spawnSync('bash', [join(src, 'bin/gstack-uninstall'), '--force', '--keep-state'], { cwd: f.home, env: f.env, encoding: 'utf8', timeout: 30_000 });
|
||||||
|
expect(un.status, un.stderr).toBe(0);
|
||||||
|
expect(readdirSync(join(f.home, '.gemini/antigravity-cli/skills'))).toEqual(['gstack-notes']);
|
||||||
|
expect(readFileSync(join(f.home, '.gemini/antigravity-cli/settings.json'), 'utf8')).toBe('{"agentMode":"plan"}\n');
|
||||||
|
expect(geminiCliFiles(f.home)).toEqual(before);
|
||||||
|
expect(registryRows(f)).toEqual([]);
|
||||||
|
}, 120_000);
|
||||||
|
});
|
||||||
|
|
||||||
describe('install registry helper', () => {
|
describe('install registry helper', () => {
|
||||||
const helper = join(ROOT, 'bin/gstack-install-registry.sh');
|
const helper = join(ROOT, 'bin/gstack-install-registry.sh');
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user