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
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Design Spec: Antigravity CLI Support for Agentlytics

## Goal
Add support for the **Antigravity CLI** to Agentlytics, allowing users to track, analyze, and view their terminal-based agent sessions alongside their IDE-based sessions.

## Context
Antigravity CLI (released May 2026) is a terminal-native agentic assistant from Google DeepMind. It stores its session data in a structured format in the user's home directory, distinct from the Antigravity IDE.

## Proposed Changes

### 1. New Editor Adapter: `editors/antigravity-cli.js`
A new adapter will be created to handle Antigravity CLI data. It will implement the standard `agentlytics` editor interface:
- `name`: `'antigravity-cli'`
- `labels`: `{ 'antigravity-cli': 'Antigravity CLI' }`
- `getChats()`: Scans for sessions.
- `getMessages(chat)`: Loads messages for a specific session.
- `getArtifacts(folder)`: Scans for session-related artifacts.

### 2. Session Discovery (`getChats`)
The adapter will:
1. Locate the app data directory: `~/.gemini/antigravity-cli`.
2. Read the `history.jsonl` file. Each line represents a session:
- `conversationId`: Unique ID.
- `display`: Initial user prompt (title).
- `workspace`: Local project folder.
- `timestamp`: Creation time.
3. Normalize this data into the `agentlytics` chat format.

### 3. Message Parsing (`getMessages`)
For a given session, the adapter will:
1. Read the `transcript.jsonl` from `~/.gemini/antigravity-cli/brain/<conversationId>/.system_generated/logs/transcript.jsonl`.
2. Iterate through the JSON log steps and map them:
- `USER_INPUT` / `USER_EXPLICIT` → `role: 'user'`
- `PLANNER_RESPONSE` → `role: 'assistant'` (including `tool_calls`)
- `TOOL_EXECUTION` / `RUN_COMMAND` / etc. → `role: 'tool'`
- `SYSTEM` / `ERROR_MESSAGE` → `role: 'system'`
3. Extract metadata like model name and token counts if available in the log steps.

### 4. Integration
- Register the new adapter in `editors/index.js`.
- Add "Antigravity CLI" to the supported editors list in `README.md`.

## Data Mapping

| CLI Log Field | Agentlytics Message Field |
|---------------|---------------------------|
| `content` | `content` |
| `tool_calls` | `_toolCalls` |
| `created_at` | `timestamp` |
| `source` | (used to determine role) |

## Verification Plan

### Manual Verification
1. Run `npx agentlytics --collect` to ensure the new sessions are indexed without errors.
2. Start the Agentlytics dashboard and verify that "Antigravity CLI" appears in the editor list.
3. Open an Antigravity CLI session and verify that the conversation history, tool calls, and project context are correctly displayed.
142 changes: 142 additions & 0 deletions editors/antigravity-cli.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
const fs = require('fs');
const path = require('path');
const os = require('os');
const { scanArtifacts, parseMcpConfigFile } = require('./base');

const name = 'antigravity-cli';
const labels = { 'antigravity-cli': 'Antigravity CLI' };

function getChats() {
const historyPath = path.join(os.homedir(), '.gemini', 'antigravity-cli', 'history.jsonl');
if (!fs.existsSync(historyPath)) {
return [];
}

try {
const content = fs.readFileSync(historyPath, 'utf-8');
const lines = content.trim().split('\n');
const chats = [];

for (const line of lines) {
if (!line.trim()) continue;
try {
const data = JSON.parse(line);
if (data.conversationId) {
const transcriptPath = path.join(os.homedir(), '.gemini', 'antigravity-cli', 'brain', data.conversationId, '.system_generated', 'logs', 'transcript.jsonl');
let bubbleCount = 0;
try {
if (fs.existsSync(transcriptPath)) {
const transcriptContent = fs.readFileSync(transcriptPath, 'utf-8');
bubbleCount = transcriptContent.trim().split('\n').length;
}
} catch {}

chats.push({
source: 'antigravity-cli',
composerId: data.conversationId,
name: data.display || 'Untitled Chat',
createdAt: data.timestamp,
lastUpdatedAt: data.timestamp,
folder: data.workspace || null,
mode: 'chat',
bubbleCount,
encrypted: false
});
}
} catch (e) {
// Skip invalid JSON lines
}
}
// Return unique chats by composerId, taking the latest one
const uniqueChats = new Map();
for (const chat of chats) {
uniqueChats.set(chat.composerId, chat);
}
return Array.from(uniqueChats.values()).reverse();
} catch (e) {
return [];
}
}

function getMessages(chat) {
const transcriptPath = path.join(os.homedir(), '.gemini', 'antigravity-cli', 'brain', chat.composerId, '.system_generated', 'logs', 'transcript.jsonl');
if (!fs.existsSync(transcriptPath)) {
return [];
}

try {
const content = fs.readFileSync(transcriptPath, 'utf-8');
const lines = content.trim().split('\n');
const messages = [];

for (const line of lines) {
if (!line.trim()) continue;
try {
const step = JSON.parse(line);
let role = null;

if (step.source === 'USER_INPUT' || step.source === 'USER_EXPLICIT') {
role = 'user';
} else if (step.source === 'MODEL' && step.type === 'PLANNER_RESPONSE') {
role = 'assistant';
} else if (step.source === 'SYSTEM') {
role = 'system';
} else if ([
'TOOL_EXECUTION', 'RUN_COMMAND', 'VIEW_FILE', 'LIST_DIR', 'LIST_DIRECTORY',
'READ_URL_CONTENT', 'SEARCH_WEB', 'GENERATE_IMAGE', 'GREP_SEARCH',
'MULTI_REPLACE_FILE_CONTENT', 'REPLACE_FILE_CONTENT', 'WRITE_TO_FILE',
'MANAGE_TASK', 'SCHEDULE', 'ASK_PERMISSION', 'ASK_QUESTION',
'DEFINE_SUBAGENT', 'INVOKE_SUBAGENT', 'MANAGE_SUBAGENTS', 'SEND_MESSAGE',
'CODE_ACTION', 'GENERIC'
].includes(step.type) || step.type.endsWith('_RESULT')) {
role = 'tool';
}

if (role) {
const msg = {
role,
content: step.content || '',
timestamp: step.created_at
};

if (role === 'assistant') {
if (step.tool_calls) {
msg._toolCalls = step.tool_calls;
}
if (step.thinking) {
msg.thinking = step.thinking;
}
}

messages.push(msg);
}
} catch (e) {
// Skip invalid JSON lines
}
}
return messages;
} catch (e) {
return [];
}
}

function getArtifacts(folder) {
return scanArtifacts(folder, {
editor: name,
label: labels[name],
files: ['task.md', 'implementation_plan.md', 'walkthrough.md'],
dirs: ['.antigravitycli']
});
}

function getMCPServers() {
const configPath = path.join(os.homedir(), '.gemini', 'antigravity-cli', 'settings.json');
return parseMcpConfigFile(configPath, {
editor: name,
label: labels[name],
scope: 'global'
});
}

module.exports = { name, labels, getChats, getMessages, getArtifacts, getMCPServers };

5 changes: 4 additions & 1 deletion editors/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,11 @@ const commandcode = require('./commandcode');
const goose = require('./goose');
const kiro = require('./kiro');
const codebuff = require('./codebuff');
const antigravityCli = require('./antigravity-cli');


const editors = [cursor, windsurf, antigravity, claude, vscode, zed, opencode, codex, gemini, copilot, cursorAgent, commandcode, goose, kiro, codebuff, antigravityCli];

const editors = [cursor, windsurf, antigravity, claude, vscode, zed, opencode, codex, gemini, copilot, cursorAgent, commandcode, goose, kiro, codebuff];

// Build a unified source → display-label map from all editor modules
const editorLabels = {};
Expand Down
17 changes: 17 additions & 0 deletions sanity-check.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
const { editors } = require('./editors/index');
const antigravityCli = editors.find(e => e.name === 'antigravity-cli');

if (!antigravityCli) {
console.error('FAIL: antigravity-cli adapter not found in editors array');
process.exit(1);
}

const requiredFunctions = ['getChats', 'getMessages', 'getArtifacts', 'getMCPServers'];
for (const fn of requiredFunctions) {
if (typeof antigravityCli[fn] !== 'function') {
console.error(`FAIL: ${fn} is not a function`);
process.exit(1);
}
}

console.log('SUCCESS: antigravity-cli adapter is correctly registered and exported.');