From a2b6437271d7a2e29162ca5e70e7891f7b761618 Mon Sep 17 00:00:00 2001 From: Anand Hegde Date: Wed, 20 May 2026 08:51:49 +0530 Subject: [PATCH] feat: add Antigravity CLI support --- ...26-05-20-antigravity-cli-support-design.md | 57 +++++++ editors/antigravity-cli.js | 142 ++++++++++++++++++ editors/index.js | 5 +- sanity-check.js | 17 +++ 4 files changed, 220 insertions(+), 1 deletion(-) create mode 100644 docs/superpowers/specs/2026-05-20-antigravity-cli-support-design.md create mode 100644 editors/antigravity-cli.js create mode 100644 sanity-check.js diff --git a/docs/superpowers/specs/2026-05-20-antigravity-cli-support-design.md b/docs/superpowers/specs/2026-05-20-antigravity-cli-support-design.md new file mode 100644 index 0000000..8db673a --- /dev/null +++ b/docs/superpowers/specs/2026-05-20-antigravity-cli-support-design.md @@ -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//.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. diff --git a/editors/antigravity-cli.js b/editors/antigravity-cli.js new file mode 100644 index 0000000..d141152 --- /dev/null +++ b/editors/antigravity-cli.js @@ -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 }; + diff --git a/editors/index.js b/editors/index.js index e53bfbb..fe61fe2 100644 --- a/editors/index.js +++ b/editors/index.js @@ -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 = {}; diff --git a/sanity-check.js b/sanity-check.js new file mode 100644 index 0000000..c9c279c --- /dev/null +++ b/sanity-check.js @@ -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.');