Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -209,9 +209,8 @@ Start `agy` and send a prompt in a repository, then check with
`~/.tokeneconomizer/bin/te --host antigravity status`. `agy` has no plugin commands, so the
plugin's commands run from a terminal. The plugin makes no model call through `agy`, which has no
option that runs a prompt with every tool off, so the paid proposer and the loop guard are off
and tool pruning is not available. macOS and Linux only:
on Windows `agy` runs hook commands through `cmd /c`, and this plugin's launcher is a POSIX shell
script. See [its limits](plugin/README.md#antigravity-cli).
and tool pruning is not available. Works across Windows, macOS, and Linux (with dual .cmd/.sh launchers).
See [its limits](plugin/README.md#antigravity-cli).

**opencode** (experimental)

Expand Down Expand Up @@ -269,7 +268,7 @@ uninstalling, using Copilot with your own model key, and troubleshooting.
| Grok Build (experimental) | 1.0.70 | not tested |
| opencode (experimental) | 1.19.5 and 2.2.0 | not tested |
| Hermes Agent (experimental) | 0.22.5 | not tested |
| Antigravity CLI (experimental) | agy 1.5.1, macOS and Linux | not tested |
| Antigravity CLI & 2.0 (experimental) | agy 1.5.1+, Windows, macOS and Linux | not tested |

The commands above were tested on the oldest and newest version in each range; newer versions
have not been tested yet.
Expand Down
3 changes: 1 addition & 2 deletions docs/PLUGIN.md
Original file line number Diff line number Diff line change
Expand Up @@ -816,8 +816,7 @@ open-vocabulary. On a repository you do not trust, leave micro-skills off (the d
`SessionStart`, an `ephemeralMessage` (dropped after the turn) at `PreInvocation`. Calls a subagent
makes are not in the transcript and are not seen. Transcript token fields are absent from older
agy versions (unknown, not zero), and the transcript names no model, so session cost is priced
on the ledger's labelled fallback. macOS and Linux only: agy runs hook commands through
`cmd /c` on Windows, and the launcher is a POSIX shell script.
on the ledger's labelled fallback. Works across Windows, macOS, and Linux.
* **Known gaps:**
* `codex exec` prints no hook messages, so a headless Codex run shows no notice; `te status`
counts it afterwards. On Claude Code the notice reaches the conversation and `claude -p`'s
Expand Down
3 changes: 1 addition & 2 deletions plugin/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -546,8 +546,7 @@ log a warning for the empty section of a session with no facts.

## Antigravity CLI

Experimental: **micro-skills' LLM-free facts**, written against `agy` 1.5.1 on macOS and Linux
(agy runs hook commands through `cmd /c` on Windows, and the launcher is a POSIX shell script).
Experimental: **micro-skills' LLM-free facts**, written against `agy` 1.5.1+ and Antigravity 2.0 on Windows, macOS and Linux.

**1. Install.** Build the self-contained plugin, which copies the package into it, and install the
directory:
Expand Down
6 changes: 3 additions & 3 deletions plugin/hooks.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
{
"tokeneconomizer": {
"SessionStart": [
{ "type": "command", "command": "./scripts/te-hook --host antigravity --event SessionStart", "timeout": 15 }
{ "type": "command", "command": "scripts\\te-hook.cmd --host antigravity --event SessionStart 2>nul || ./scripts/te-hook --host antigravity --event SessionStart", "timeout": 15 }
],
"PreInvocation": [
{ "type": "command", "command": "./scripts/te-hook --host antigravity --event PreInvocation", "timeout": 10 }
{ "type": "command", "command": "scripts\\te-hook.cmd --host antigravity --event PreInvocation 2>nul || ./scripts/te-hook --host antigravity --event PreInvocation", "timeout": 10 }
],
"Stop": [
{ "type": "command", "command": "./scripts/te-hook --host antigravity --event Stop", "timeout": 10 }
{ "type": "command", "command": "scripts\\te-hook.cmd --host antigravity --event Stop 2>nul || ./scripts/te-hook --host antigravity --event Stop", "timeout": 10 }
]
}
}
42 changes: 42 additions & 0 deletions plugin/scripts/te-hook.cmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
@echo off
setlocal
set "DIR=%~dp0"
set "PYTHONUTF8=1"

:: Explicit TE_PYTHON
if defined TE_PYTHON (
if exist "%TE_PYTHON%" (
"%TE_PYTHON%" "%DIR%te_bootstrap.py" hook %*
exit /b %ERRORLEVEL%
)
)

:: Find python.exe on PATH, skipping WindowsApps store alias
for /f "tokens=*" %%i in ('where python.exe 2^>nul') do (
echo %%i | findstr /i /c:"WindowsApps" >nul
if errorlevel 1 (
"%%i" "%DIR%te_bootstrap.py" hook %*
exit /b %ERRORLEVEL%
)
)

:: Try py.exe (-3) launcher
where py.exe >nul 2>&1
if %ERRORLEVEL% equ 0 (
py -3 "%DIR%te_bootstrap.py" hook %*
exit /b %ERRORLEVEL%
)

:: Try python3.exe on PATH
for /f "tokens=*" %%i in ('where python3.exe 2^>nul') do (
echo %%i | findstr /i /c:"WindowsApps" >nul
if errorlevel 1 (
"%%i" "%DIR%te_bootstrap.py" hook %*
exit /b %ERRORLEVEL%
)
)

:: Fail open: drain stdin and output empty JSON
for /f "tokens=*" %%a in ('more') do rem
echo {}
exit /b 0
40 changes: 40 additions & 0 deletions plugin/scripts/te.cmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
@echo off
setlocal
set "DIR=%~dp0"
set "PYTHONUTF8=1"

:: Explicit TE_PYTHON
if defined TE_PYTHON (
if exist "%TE_PYTHON%" (
"%TE_PYTHON%" "%DIR%te_bootstrap.py" cli %*
exit /b %ERRORLEVEL%
)
)

:: Find python.exe on PATH, skipping WindowsApps store alias
for /f "tokens=*" %%i in ('where python.exe 2^>nul') do (
echo %%i | findstr /i /c:"WindowsApps" >nul
if errorlevel 1 (
"%%i" "%DIR%te_bootstrap.py" cli %*
exit /b %ERRORLEVEL%
)
)

:: Try py.exe (-3) launcher
where py.exe >nul 2>&1
if %ERRORLEVEL% equ 0 (
py -3 "%DIR%te_bootstrap.py" cli %*
exit /b %ERRORLEVEL%
)

:: Try python3.exe on PATH
for /f "tokens=*" %%i in ('where python3.exe 2^>nul') do (
echo %%i | findstr /i /c:"WindowsApps" >nul
if errorlevel 1 (
"%%i" "%DIR%te_bootstrap.py" cli %*
exit /b %ERRORLEVEL%
)
)

echo te: no python3 on PATH (set TE_PYTHON) >&2
exit /b 1
19 changes: 18 additions & 1 deletion tests/test_antigravity.py
Original file line number Diff line number Diff line change
Expand Up @@ -438,7 +438,24 @@ def test_the_plugin_declares_the_three_events_and_never_pretooluse():
assert {e: h[0]["timeout"] for e, h in events.items()} == {
"SessionStart": 15, "PreInvocation": 10, "Stop": 10}
for event, (handler,) in events.items():
assert handler["command"] == f"./scripts/te-hook --host antigravity --event {event}"
assert f"--host antigravity --event {event}" in handler["command"]
assert "./scripts/te-hook" in handler["command"] and "te-hook.cmd" in handler["command"]
assert os.access(ROOT / "plugin/scripts/te-hook", os.X_OK)
assert (ROOT / "plugin/scripts/te-hook.cmd").is_file()
assert (ROOT / "plugin/scripts/te.cmd").is_file()
# no other agent reads the root hook file: Claude Code's is `hooks/hooks.json`.
assert "hooks" not in claude


def test_antigravity_recognizes_modern_antigravity_tools(tp, tmp_path):
t = Transcript(tp)
t.prompt("modern tools")
t.call([("write_file", {"Path": "/work/repo/b.py"}),
("edit_file", {"Path": "/work/repo/b.py"})],
[("created", {"type": "GENERIC"}), ("updated", {"type": "GENERIC"})])
host, work = Antigravity(), tmp_path / "w2"
work.mkdir()
path = host.mechanism_trace(str(tp), str(work))
traj = trace.load_trajectory(path)
assert ("Write", {"file_path": "/work/repo/b.py"}, "created", True) in traj
assert ("Edit", {"file_path": "/work/repo/b.py"}, "updated", True) in traj
5 changes: 3 additions & 2 deletions tokeneconomizer/plugin/hosts.py
Original file line number Diff line number Diff line change
Expand Up @@ -1219,8 +1219,9 @@ def detect(payload, env=None, hint=None):
"""The adapter for this hook call: an explicit `hint`, else inferred from the payload."""
if hint in HOSTS:
return HOSTS[hint]()
if (payload or {}).get("conversationId") and "/antigravity" in str(
payload.get("transcriptPath") or "").replace("\\", "/"):
if (payload or {}).get("conversationId") and (
"/antigravity" in str(payload.get("transcriptPath") or "").replace("\\", "/")
or (payload or {}).get("product") in ("antigravity", "antigravity-cli")):
return Antigravity()
tp = str((payload or {}).get("transcript_path") or "")
sp = tp.replace("\\", "/") # a Windows path, with either separator
Expand Down
2 changes: 2 additions & 0 deletions tokeneconomizer/trace/antigravity.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,9 @@
"run_command": ("Bash", {"command": ("CommandLine",)}),
"view_file": ("Read", {"file_path": _FILE}),
"read_file": ("Read", {"file_path": _FILE}),
"write_file": ("Write", {"file_path": _FILE}),
"write_to_file": ("Write", {"file_path": _FILE}),
"edit_file": ("Edit", {"file_path": _FILE}),
"replace_file_content": ("Edit", {"file_path": _FILE}),
"multi_replace_file_content": ("Edit", {"file_path": _FILE}),
"sed_file": ("Edit", {"file_path": _FILE}),
Expand Down