@ascenda-one/gemini-hooks
Gemini CLI agent hooks adapter for Ascenda AI workload telemetry. Metadata only — prompt text, file contents and command output never leave the machine.
Install
npx @ascenda-one/gemini-hooks setup
This pairs (printing a 6-digit code to confirm in the Ascenda app), installs a
self-contained hook bundle to ~/.ascenda/bin, records the pairing under
tools.gemini_cli in ~/.ascenda/credentials.json, and registers the hooks
in this project's .gemini/settings.json. Restart Gemini CLI and events flow — no
shell exports, no settings file to hand-edit. That matters more than it
sounds: a GUI-launched editor never sees a shell rc file, so an id that lives
only in an export line silently stops telemetry the next time the app is
opened from the Dock.
npx @ascenda-one/gemini-hooks status # exits non-zero if anything is unwired
npx @ascenda-one/gemini-hooks doctor # can the hooks run, has one run, and does the screen saver hear them
npx @ascenda-one/gemini-hooks uninstall # removes hooks, the binary and this pairing
| Option | |
|---|---|
--api-base-url <url> | ingest host (default https://api.ascenda.one) |
--local [port] | shorthand for a local dev server (default 4477) |
--tool-installation-id <id> / --token <t> | reuse an existing pairing instead of creating one |
--scope user|project | register in ~/.gemini/settings.json for every project (default), or in this project only |
--project-dir <path> | project root for --scope project (default cwd) |
--no-pair | install without pairing: the screen saver and live view work, nothing is sent |
--dry-run | print what would change, write nothing |
examples/ holds the equivalent hand-written config for anyone who would
rather not run setup.
Configure
Nothing is required after setup. A hook resolves its identity from, in
order: the environment, its own entry in ~/.ascenda/credentials.json, and
— when exactly one cli_agent token is on disk — the token store. A hook that
still cannot name its installation records the skipped send in the journal
status reads, rather than exiting silently.
| Variable | |
|---|---|
ASCENDA_TOOL_INSTALLATION_ID | overrides the pairing setup recorded |
ASCENDA_EVENT_WRITE_TOKEN | overrides the stored token |
ASCENDA_API_BASE_URL | overrides the ingest host |
ASCENDA_EVENT_LOG_FILE | local JSONL log of every event, or off |
Without a pairing, every event is written to ~/.ascenda/events.jsonl as
not_sent, so you can see exactly what would be transmitted, or read the
events with your own tools, without connecting anything.
examples/local-event-reader shows how.
setup --event-log <path> moves the file, setup --event-log off stops it,
and the variable wins over both. Every payload carries the UTC offset
and an idempotency key; both come from the shared sender in tool-kit, so
this adapter holds only its mapping.
Privacy defaults
Metadata-only. Does not send prompts, responses, code, file paths, command text, repository names or branch names. Correction detection runs locally on prompt text; only the classification is transmitted.
setup prints the full list before it pairs — every family this adapter sends,
the refusals, and how to turn it off. That block is generated from the
adapter's own declaration in src/setup.ts, and a guard test
(packages/tool-kit/tests/setupDisclosure.test.mjs) runs the mapper and fails
the build when the declaration claims a family the mapper does not send, or
omits one it does.
Event mapping
BeforeModel/AfterModel are deliberately unregistered: they fire per LLM round trip and would multiply event volume. See the mapping doc.
Test locally
No Gemini CLI install required:
./scripts/replay-agent-hooks.sh gemini
Hook invocations always exit 0: telemetry must never block your work.