Skip to content
Draft
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
23 changes: 23 additions & 0 deletions .github/workflows/sensor-gemini.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
name: Gemini CLI parser platforms
on:
pull_request:
paths: ['Sensor/**', '.github/workflows/sensor-gemini.yml']
push:
branches: [main]
paths: ['Sensor/**', '.github/workflows/sensor-gemini.yml']
permissions:
contents: read
jobs:
parser:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v7
- uses: astral-sh/setup-uv@v7
- run: uv sync --extra dev --python 3.12
working-directory: Sensor
- run: uv run pytest tests/test_gemini_parser.py -q
working-directory: Sensor
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ See **[docs/REPRODUCIBILITY.md](docs/REPRODUCIBILITY.md)** for the full evaluati

Component documentation:

ADR Sensor also captures [Gemini CLI session journals](Sensor/README.md#gemini-cli)
on macOS, Linux, and Windows, including tool results and nested subagent sessions.

- [Discovery/README.md](Discovery/README.md): endpoint inventory, probes, and the fingerprint catalog
- [Sensor/README.md](Sensor/README.md): telemetry collection and unified schema
- [Detection/README.md](Detection/README.md): ADR-Bench, detector baselines, MCP infrastructure
Expand Down
55 changes: 54 additions & 1 deletion Sensor/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ ADR Sensor is a Python library that collects telemetry from AI coding agents to
| **GitHub Copilot CLI** | `copilot` | JSONL (`~/.copilot/session-state/`) | macOS, Linux, Windows |
| **Warp Terminal** | `warp` | SQLite (`warp.sqlite`) | macOS, Windows |
| **opencode** | `opencode` | SQLite (`opencode.db`) or JSON tree | macOS, Linux |
| **Gemini CLI** | `gemini` | JSONL journals + legacy JSON chats | macOS, Linux, Windows |

### Claude Desktop Agent Mode

Expand Down Expand Up @@ -92,13 +93,62 @@ known built-in and contains an underscore is recorded as `tool_type: "mcp_tool"`
its `server_name` populated.


### Gemini CLI

The `gemini` source reads current `chats/**/*.jsonl` journals and legacy
`chats/*.json` conversation snapshots. It captures user/assistant text, tool
arguments and results, recorded status and approval requests, model and token
usage, and nested subagent sessions. Start timestamps remain stable when sessions
resume; `--save-sessions` refreshes changed snapshots in place.

| Host | Default scan root (per-user home) |
| ---- | -------------------------------- |
| macOS / Linux | `~/.gemini/tmp/` |
| Windows | `%USERPROFILE%\.gemini\tmp\` |
| macOS Seatbelt sandbox | `~/.cache/.gemini/tmp/` (also scanned on macOS) |

`GEMINI_CLI_HOME` overrides the **parent home directory**, so the path becomes
`$GEMINI_CLI_HOME/.gemini/tmp`, not `$GEMINI_CLI_HOME/tmp`. Project directories
can be hashes or readable identifiers. Project paths come from `.project_root`
or `projects.json`; a project hash alone is not a filesystem path. A custom
acquired root can be supplied through `GeminiParser(base_path=Path("/capture/tmp"))`.
WSL/container sessions belong to their own filesystem and home.

ADR consolidates repeated journal messages by ID, so tool progress updates do
not duplicate messages or token totals. Earlier activity survives rewind and
checkpoint records. `session_context.history_scope` is `all_recorded_branches`;
this is recorded activity, not a reconstruction of only the model's current
context. Source message metadata, typed content, tool IDs, and recorded thought
summaries remain in `session_context`. The parser adds no redaction or truncation;
upstream output limits and deleted files cannot be recovered. Unknown or malformed
records do not abort other sessions, and malformed-record counts are reported.

Only CLI chat records are covered. Prompt-only `logs.json`, editor chat storage,
shell history, unsaved sessions, and files outside these roots are not collected.
The default lookback is 14 days by file modification time; `--all-history`
includes older files. Explicit `mcp_`/qualified tool names identify MCP calls;
server attribution is left empty when the recorded name is ambiguous.

```bash
uv run adr-sensor --source gemini --no-save
uv run adr-sensor --source gemini --save-sessions --all-history
```

Contracts verified against upstream
[record types](https://github.com/google-gemini/gemini-cli/blob/9c1b0a610534d6f8120964cf2672c07807d8fc90/packages/core/src/services/chatRecordingTypes.ts),
[journal writer](https://github.com/google-gemini/gemini-cli/blob/9c1b0a610534d6f8120964cf2672c07807d8fc90/packages/core/src/services/chatRecordingService.ts),
[storage paths](https://github.com/google-gemini/gemini-cli/blob/9c1b0a610534d6f8120964cf2672c07807d8fc90/packages/core/src/config/storage.ts),
and [supported platforms](https://geminicli.com/docs/get-started/installation/).
Tests use synthetic records matching these contracts and run on all three hosts;
they do not require a Gemini account.

## Architecture

```
┌─────────────────────────────────────────────────────────────────┐
│ AI Agent Logs │
│ Claude, Cursor, Cline, Codex, Copilot CLI, Warp │
Claude Desktop, opencode
│ Claude Desktop, opencode, Gemini CLI
└───────────────────────────────┬─────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
Expand Down Expand Up @@ -155,6 +205,7 @@ adr-sensor --source codex
adr-sensor --source copilot
adr-sensor --source claude_desktop
adr-sensor --source opencode
adr-sensor --source gemini

# Save individual session files (incremental)
adr-sensor --save-sessions
Expand Down Expand Up @@ -375,6 +426,7 @@ cannot run on the current platform are skipped rather than failing.
| ----------------- | -------------------------- | ----------------------------------------------------------------- |
| `CODEX_HOME` | Codex parser | Codex data root containing `sessions/` and optional `state_*.sqlite` catalogs (default `~/.codex`) |
| `COPILOT_HOME` | Copilot parser | Copilot CLI data root containing `session-state/` (default `~/.copilot`) |
| `GEMINI_CLI_HOME` | Gemini parser | Parent home containing `.gemini/tmp/`; on macOS also `.cache/.gemini/tmp/` |
| `XDG_CACHE_HOME` | `AgentObserver` | Base for `--save-sessions` output (`$XDG_CACHE_HOME/adr_sensor`, default `~/.cache/adr_sensor`) |
| `XDG_DATA_HOME` | opencode parser | Overrides the opencode data directory (default `~/.local/share/opencode`) |
| `OPENCODE_DB` | opencode parser | Overrides the opencode SQLite filename or path (`:memory:` is ignored) |
Expand Down Expand Up @@ -427,6 +479,7 @@ adr-sensor/
│ │ ├── claude_desktop_parser.py
│ │ ├── codex_parser.py
│ │ ├── copilot_parser.py
│ │ ├── gemini_parser.py
│ │ ├── opencode_parser.py
│ │ └── warp_parser.py
│ ├── schemas/
Expand Down
2 changes: 1 addition & 1 deletion Sensor/adr_sensor/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

Security observability library for AI coding agents. Collects telemetry from
Claude Code, Cursor, Cline, OpenAI Codex CLI, GitHub Copilot CLI, Warp
Terminal, opencode, and Claude Desktop Agent Mode (including Dispatch
Terminal, opencode, Gemini CLI, and Claude Desktop Agent Mode (including Dispatch
sessions) to enable threat detection and security monitoring.

Usage:
Expand Down
1 change: 1 addition & 0 deletions Sensor/adr_sensor/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ def main():
adr-sensor --source claude_desktop Ingest Claude Desktop agent-mode logs only (macOS/Windows)
adr-sensor --source copilot Ingest GitHub Copilot CLI logs only
adr-sensor --source opencode Ingest opencode logs only
adr-sensor --source gemini Ingest Gemini CLI chat sessions
adr-sensor --save-sessions Save individual session files
adr-sensor --output-format jsonl Export as JSONL
adr-sensor --all-history Include all logs (not just last 2 weeks)
Expand Down
7 changes: 5 additions & 2 deletions Sensor/adr_sensor/observer.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
from .parsers.codex_parser import CodexParser
from .parsers.copilot_parser import CopilotParser
from .parsers.cursor_parser import CursorParser
from .parsers.gemini_parser import GeminiParser
from .parsers.opencode_parser import OpencodeParser
from .parsers.warp_parser import WarpParser
from .schemas.agent_event_schema import AgentEvent
Expand Down Expand Up @@ -61,6 +62,7 @@ class AgentObserver:
("codex", "Codex"),
("copilot", "GitHub Copilot CLI"),
("opencode", "opencode"),
("gemini", "Gemini CLI"),
)

#: Sources that only produce logs on some operating systems. A source absent
Expand All @@ -69,7 +71,7 @@ class AgentObserver:
"claude_desktop": ("Darwin", "Windows"),
}

CONTENT_AWARE_INCREMENTAL_SOURCES = frozenset({"codex", "copilot"})
CONTENT_AWARE_INCREMENTAL_SOURCES = frozenset({"codex", "copilot", "gemini"})

def __init__(self, output_dir: Optional[Path] = None, max_age_days: Optional[int] = None):
"""Initialize the AgentObserver.
Expand All @@ -94,6 +96,7 @@ def __init__(self, output_dir: Optional[Path] = None, max_age_days: Optional[int
)

self.output_dir = output_dir if output_dir else Path("output")
self.gemini_parser = GeminiParser(max_age_days=max_age_days) if max_age_days is not None else GeminiParser()
self.output_dir.mkdir(exist_ok=True)

def _emit_error(self, error_payload: Dict[str, Any]) -> None:
Expand Down Expand Up @@ -129,7 +132,7 @@ def ingest_all(

Args:
source_filter: Which source to ingest. One of 'all', 'claude', 'cursor',
'claude_desktop', 'cline', 'warp', 'codex', 'copilot', 'opencode'.
'claude_desktop', 'cline', 'warp', 'codex', 'copilot', 'opencode', 'gemini'.

Returns:
Tuple of (agent_events, system_configs).
Expand Down
2 changes: 2 additions & 0 deletions Sensor/adr_sensor/parsers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from .codex_parser import CodexParser
from .copilot_parser import CopilotParser
from .cursor_parser import CursorParser
from .gemini_parser import GeminiParser
from .opencode_parser import OpencodeParser
from .warp_parser import WarpParser

Expand All @@ -20,6 +21,7 @@
"ClaudeParser",
"ClineParser",
"CodexParser",
"GeminiParser",
"CopilotParser",
"CursorParser",
"OpencodeParser",
Expand Down
Loading