The real headache was that the command name gemini is scattered across cron, CI, Makefiles, and old notes—and I could not honestly account for every place it gets called from. Trying to replace them all on shutdown day, a few always slip through and fail quietly at 3 a.m. As an indie developer running an automated publishing pipeline, that "one that slipped through" goes unnoticed until the next morning.
So before rewriting every call site directly, I slipped in a thin compatibility shim that keeps the name gemini but routes the body to agy (the Antigravity CLI). That decoupled me from the deadline and let me move call sites over one at a time, calmly.
Why a shim instead of a one-shot replace
What is genuinely scary about migrating from gemini to agy is not the command disappearing—it is not holding a correct, complete list of every caller. grep only finds what is inside the repo; crontab, systemd timers, and scripts on another machine all escape it.
A shim absorbs that uncertainty over time. It keeps the old name while delegating to the new implementation, separating the migration deadline (June 18) from your own deadline to fix every caller. As long as you secure name compatibility before the command goes away, you can move the internals at your own pace.
Build the flag mapping table first
Before writing the shim, map only the flags you actually use. There is no need to cover every flag—porting ones you never call buys nothing. My pipeline used just these four.
-p / --prompt(prompt string) →agy run -p-m / --model(model selection) →agy run -m--json(machine-readable output) →agy run --format json-f / --file(input file) →agy run --input
What matters here is the output-format difference. The old CLI's --json and the new CLI's --format json can differ in key names and nesting. If anything downstream parses it, look at the real output once right after routing through the shim and confirm your jq paths still work. Skip this and you get the worst failure: the command succeeds while the downstream silently grabs nothing.
The compatibility shim itself (drop-in replacement)
Prepare an executable named gemini placed at the front of PATH. It is a thin layer that parses the arguments and re-passes them to agy.
#!/usr/bin/env bash
# /usr/local/bin/gemini ← place under the old command name, first on PATH
# Role: a compatibility shim that bridges old gemini calls to agy run
set -euo pipefail
# Resolve the real agy (fail loudly if missing)
AGY="$(command -v agy || true)"
if [ -z "$AGY" ]; then
echo "gemini-shim: agy not found. Please install the Antigravity CLI." >&2
exit 127
fi
args=()
while [ $# -gt 0 ]; do
case "$1" in
-p|--prompt) args+=( -p "$2" ); shift 2 ;;
-m|--model) args+=( -m "$2" ); shift 2 ;;
-f|--file) args+=( --input "$2" ); shift 2 ;;
--json) args+=( --format json ); shift ;;
-h|--help)
echo "gemini-shim → agy run compatibility layer. Unknown flags pass through." >&2
shift ;;
*) args+=( "$1" ); shift ;; # pass unknown args through as-is
esac
done
# Make migration visible: log shim-routed calls to surface remaining callers
echo "$(date -Iseconds) shim-call: $*" >> "${HOME}/.gemini-shim.log"
exec "$AGY" run "${args[@]}"The exec replacement passes the exit code and signals straight through to agy. Inserting another process in between can make CI misjudge the exit code or fail to deliver the timeout signal.
Use the log as a map of "remaining callers"
The hidden star of this shim is the last log line. By recording shim-routed calls, just a few days of operation naturally collects "where gemini actually gets called from." Even a crontab or another script grep never found will leave a footprint in the log the moment it runs.
# Tally the actual callers of gemini over the past week into a to-migrate list
sort "${HOME}/.gemini-shim.log" | awk '{$1=""; print}' | sort | uniq -c | sort -rnLooking at this tally, I noticed an old backup cron that did not exist anywhere in the repo. It was a caller static grep would never have found. Once you can measure migration progress by "how many callers remain," the pre-deadline anxiety drops a lot.
Judging when to remove the shim
The shim is a bridge, not a destination. Once the tally hits zero for several days straight, you can conclude every caller has moved to calling agy directly.
# If there have been zero shim-routed calls in the past 3 days, it is a removal candidate
RECENT=$(awk -v cut="$(date -d '3 days ago' -Iseconds)" '$1 > cut' "${HOME}/.gemini-shim.log" | wc -l)
[ "$RECENT" -eq 0 ] && echo "No shim-routed calls → you can remove /usr/local/bin/gemini"Migration is only complete when removal is included. Leave the shim in place and the call sites you should have fixed get preserved "because they still work," leaving you with permanent double maintenance. I deliberately set a deadline and pulled it out.
The first thing to do is write down just four or five flags you actually use and run the shim above within that scope. You do not need perfect compatibility. If you reach a state where "as long as the name lives, the body can move later" before the June 18 shutdown, you are freed from a deadline-driven migration. I hope this helps anyone with an automation-script migration coming up.