🌐

Gemini Cli

v0.1.0Apache 2.0

Bridge plugin that enables Han's hook ecosystem (validation, context injection, orchestration) to work with Gemini CLI by translating Gemini CLI events into Han hook invocations.

Installation

Install han binary (required for hooks to work):

curl -fsSL https://han.guru/install.sh | bash

Then install this plugin:

han plugin install gemini-cli

Overview

Bridge extension that brings Han's full plugin ecosystem to Gemini CLI — validation hooks, core guidelines, 400+ skills, and 25 agent disciplines.

What This Does

Han plugins define validation hooks, specialized skills, and agent disciplines that run during Claude Code sessions. This bridge makes the entire ecosystem work in Gemini CLI:

  1. Hooks — PreToolUse, PostToolUse, and Stop validation (biome, eslint, tsc, etc.) with parallel execution
  2. Guidelines — Core principles (professional honesty, no excuses, skill selection) injected via GEMINI.md
  3. Datetime — Current time injected on every agent turn via BeforeAgent
  4. Events — Unified JSONL logging with harness: "gemini-cli" for Browse UI visibility

Coverage Matrix

Claude Code HookGemini CLI EquivalentStatus
PostToolUseAfterToolImplemented (failures return decision:"deny" with the errors as reason, hiding the tool result)
PreToolUseBeforeToolImplemented
StopAfterAgentImplemented
SessionStartSessionStartImplemented
SessionEndSessionEndImplemented (advisory; runs SessionEnd Han hooks if any, then flushes the event log)
UserPromptSubmitBeforeAgentImplemented
PreCompactPreCompressImplemented
SubagentStart/Stop—Not available
Permission denialBeforeTool decision:"deny"Implemented
MCP tool events—Not available
—BeforeModel / AfterModelIntentionally unsupported (Han has no model-level hooks)
—BeforeToolSelectionIntentionally unsupported (Han has no tool-selection hooks)

Plugin discovery reads the current enabledPlugins boolean map (what han plugin install writes) as well as the legacy plugins object map from Claude Code settings, and resolves marketplace source paths relative to the marketplace root.

Validation Flow

AfterTool (Primary Validation Path)

When the agent edits a file:

Agent edits src/app.ts via write_file
  -> Gemini CLI fires AfterTool hook
  -> Bridge maps write_file -> Write (Claude Code name)
  -> Matches PostToolUse hooks (biome, eslint, tsc)
  -> Runs hooks in parallel
  -> On failure: returns decision:"deny" with validation errors as reason
  -> Tool result is hidden; the agent sees the errors instead and fixes them

AfterAgent (Stop Validation)

When the agent finishes a turn:

Agent signals completion
  -> Gemini CLI fires AfterAgent hook
  -> Bridge runs Stop hooks (full project lint, typecheck, tests)
  -> If failures: returns decision:"block" to force continuation
  -> Agent gets another turn to fix project-wide issues

BeforeTool (Pre-Execution Gates)

Before a tool executes:

Agent about to run a tool
  -> Gemini CLI fires BeforeTool hook
  -> Bridge matches PreToolUse hooks
  -> If any hook rejects: returns decision:"deny"
  -> Tool execution is blocked

Setup

Prerequisites

Han plugins must be installed:

bash
curl -fsSL https://han.guru/install.sh | bash
han plugin install --auto

Bun is required for running the bridge scripts.

Install the Extension

Use the install script to set up the Gemini CLI extension:

bash
# From the han repo
bash plugins/bridges/gemini-cli/install.sh

# Or clone and install
git clone --depth 1 https://github.com/TheBushidoCollective/han.git /tmp/han
bash /tmp/han/plugins/bridges/gemini-cli/install.sh /tmp/han/plugins/bridges/gemini-cli
rm -rf /tmp/han

The install script copies the extension to ~/.gemini/extensions/han/ and sets up the correct hooks configuration for Gemini CLI.

Verify installation:

bash
gemini extensions list
# Should show "han" extension

Note: The source repository stores Gemini CLI hooks in gemini-hooks.json (not hooks/hooks.json) to avoid conflicts with Claude Code's plugin validation. The install script places them at the correct hooks/hooks.json path that Gemini CLI expects.

Architecture

Gemini CLI Hook System
  |
  |-- SessionStart ──────────────> Plugin discovery + coordinator start
  |     hooks/hooks.json            -> systemMessage with capability summary
  |
  |-- BeforeAgent ───────────────> Datetime injection
  |     hooks/hooks.json            -> additionalContext with current time
  |
  |-- BeforeTool (matcher: *) ───> PreToolUse hooks
  |     hooks/hooks.json            -> decision:"deny" if validation fails
  |
  |-- AfterTool (matcher: write_file|replace) ──> PostToolUse hooks
  |     hooks/hooks.json            -> decision:"deny" + reason with validation errors
  |
  |-- AfterAgent ────────────────> Stop hooks (full project validation)
  |     hooks/hooks.json            -> decision:"block" if failures
  |
  |-- SessionEnd ────────────────> SessionEnd hooks (if any) + event log flush
  |     hooks/hooks.json            -> advisory, CLI does not wait
  |
  |-- GEMINI.md ─────────────────> Core guidelines context
  |     (loaded automatically)       (professional honesty, no excuses, etc.)
  |
  |-- JSONL event logger ────────> Browse UI visibility
        ~/.han/gemini-cli/projects/   (harness="gemini-cli")

Bridge Internals (src/):
  bridge.ts        Entry point (stdin JSON → handler → stdout JSON)
  discovery.ts     Read settings + marketplace + resolve plugin paths
  matcher.ts       Filter by tool name, file globs, dirsWith
  executor.ts      Spawn hook commands as parallel promises
  formatter.ts     Structure results as Gemini CLI JSON responses
  cache.ts         Content-hash caching (SHA-256)
  context.ts       Session context + datetime injection
  skills.ts        Skill discovery (counting)
  disciplines.ts   Discipline discovery (counting)
  events.ts        JSONL event logger (harness="gemini-cli")
  types.ts         Gemini CLI + Han type definitions

How Plugins Stay Compatible

Han plugins don't need modification to work with Gemini CLI. The bridge reads the same han-plugin.yml files that Claude Code uses:

yaml
# This config works in Claude Code, OpenCode, AND Gemini CLI
hooks:
  lint-async:
    event: PostToolUse
    command: "npx -y @biomejs/biome check --write ${HAN_FILES}"
    tool_filter: [Edit, Write, NotebookEdit]
    file_filter: ["**/*.{js,jsx,ts,tsx}"]
    dirs_with: ["biome.json"]

The difference is in who executes the hook:

  • Claude Code: Reads hooks.json, calls han hook run via shell
  • OpenCode: Bridge reads han-plugin.yml, runs command as JS promise
  • Gemini CLI: Bridge reads han-plugin.yml, runs command as child process, returns JSON

Same hook definition. Same validation. Different runtime.

Gemini CLI Hook Protocol

Gemini CLI hooks communicate via stdin/stdout JSON:

  • Input (stdin): { "tool_name": "write_file", "tool_input": { "path": "src/app.ts", ... } }
  • Output (stdout): { "decision": "allow" } or { "decision": "deny", "reason": "Validation errors..." }
  • Logging (stderr): All debug output goes to stderr (stdout is reserved for JSON)

Exit codes:

  • 0: Success — Gemini CLI parses stdout JSON
  • 2: Critical block — Gemini CLI aborts the action
  • Other: Warning — Gemini CLI continues unchanged

Event Logging

The bridge writes Han-format JSONL events to ~/.han/gemini-cli/projects/. Each event includes harness: "gemini-cli" to distinguish Gemini CLI sessions from Claude Code and OpenCode sessions.

On SessionStart, the bridge launches the Han coordinator in the background with han coordinator ensure --background. The coordinator discovers ~/.han/<harness>/projects on disk, watches it, and indexes each <sessionId>-han.jsonl into SQLite as a session attributed to that harness, which is what makes those sessions visible in the Browse UI. There is no registration step and no --watch-path flag. The coordinator enumerates those roots when it starts, so the very first session from a newly installed bridge is picked up at the next coordinator start or scan rather than live. Every session after that is live.

Remaining Gaps

  • MCP tool events: Gemini CLI doesn't fire AfterTool for MCP server tool calls. Validation only runs for built-in tools.
  • Subagent hooks: No Gemini CLI equivalent for SubagentStart/SubagentStop.
  • Persistent state: Each hook invocation is a separate process. Plugin discovery runs on every invocation (fast but not cached across invocations).
  • Session correlation across processes: Gemini CLI puts no session id on the hook payload, and nothing in this repo establishes that it exports GEMINI_SESSION_ID. The bridge reads that variable in exactly one place and memoizes the result, so a single process can no longer scatter its events across several ids. It cannot fix the cross-process case: Gemini CLI runs the bridge as a fresh process per hook event, so if the variable is unset, every hook event still becomes its own session. Closing this needs a real session id from the host.

Development

bash
# From the han repo root
cd plugins/bridges/gemini-cli

# The bridge uses TypeScript directly (Bun handles it)
# No build step required

License

Apache-2.0