Fix: Add Windows support for SQLite database access (Issue #23)

Replace CLI-based sqlite3 extraction with better-sqlite3 library:
- Create shared src/db/database.js module for cross-platform SQLite access
- Remove duplicate extractTokenFromDB functions from token-extractor.js and account-manager.js
- Add better-sqlite3 dependency with prebuilt Windows/Mac/Linux binaries
- Improve error handling with specific messages for common issues

Fixes #23

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Badri Narayanan S
2025-12-30 18:47:05 +05:30
parent ead7632eec
commit d05fb64e29
6 changed files with 527 additions and 52 deletions

View File

@@ -7,7 +7,6 @@
import { readFile, writeFile, mkdir, access } from 'fs/promises';
import { constants as fsConstants } from 'fs';
import { dirname } from 'path';
import { execSync } from 'child_process';
import {
ACCOUNT_CONFIG_PATH,
ANTIGRAVITY_DB_PATH,
@@ -20,6 +19,7 @@ import {
} from './constants.js';
import { refreshAccessToken } from './oauth.js';
import { formatDuration } from './utils/helpers.js';
import { getAuthStatus } from './db/database.js';
export class AccountManager {
#accounts = [];
@@ -92,7 +92,7 @@ export class AccountManager {
*/
async #loadDefaultAccount() {
try {
const authData = this.#extractTokenFromDB();
const authData = getAuthStatus();
if (authData?.apiKey) {
this.#accounts = [{
email: authData.email || 'default@antigravity',
@@ -115,22 +115,6 @@ export class AccountManager {
}
}
/**
* Extract token from Antigravity's SQLite database
*/
#extractTokenFromDB(dbPath = ANTIGRAVITY_DB_PATH) {
const result = execSync(
`sqlite3 "${dbPath}" "SELECT value FROM ItemTable WHERE key = 'antigravityAuthStatus';"`,
{ encoding: 'utf-8', timeout: 5000 }
);
if (!result || !result.trim()) {
throw new Error('No auth status found in database');
}
return JSON.parse(result.trim());
}
/**
* Get the number of accounts
* @returns {number} Number of configured accounts
@@ -453,7 +437,7 @@ export class AccountManager {
} else {
// Extract from database
const dbPath = account.dbPath || ANTIGRAVITY_DB_PATH;
const authData = this.#extractTokenFromDB(dbPath);
const authData = getAuthStatus(dbPath);
token = authData.apiKey;
}

93
src/db/database.js Normal file
View File

@@ -0,0 +1,93 @@
/**
* SQLite Database Access Module
* Provides cross-platform database operations for Antigravity state.
*
* Uses better-sqlite3 for:
* - Windows compatibility (no CLI dependency)
* - Native performance
* - Synchronous API (simple error handling)
*/
import Database from 'better-sqlite3';
import { ANTIGRAVITY_DB_PATH } from '../constants.js';
/**
* Query Antigravity database for authentication status
* @param {string} [dbPath] - Optional custom database path
* @returns {Object} Parsed auth data with apiKey, email, name, etc.
* @throws {Error} If database doesn't exist, query fails, or no auth status found
*/
export function getAuthStatus(dbPath = ANTIGRAVITY_DB_PATH) {
let db;
try {
// Open database in read-only mode
db = new Database(dbPath, {
readonly: true,
fileMustExist: true
});
// Prepare and execute query
const stmt = db.prepare(
"SELECT value FROM ItemTable WHERE key = 'antigravityAuthStatus'"
);
const row = stmt.get();
if (!row || !row.value) {
throw new Error('No auth status found in database');
}
// Parse JSON value
const authData = JSON.parse(row.value);
if (!authData.apiKey) {
throw new Error('Auth data missing apiKey field');
}
return authData;
} catch (error) {
// Enhance error messages for common issues
if (error.code === 'SQLITE_CANTOPEN') {
throw new Error(
`Database not found at ${dbPath}. ` +
'Make sure Antigravity is installed and you are logged in.'
);
}
// Re-throw with context if not already our error
if (error.message.includes('No auth status') || error.message.includes('missing apiKey')) {
throw error;
}
throw new Error(`Failed to read Antigravity database: ${error.message}`);
} finally {
// Always close database connection
if (db) {
db.close();
}
}
}
/**
* Check if database exists and is accessible
* @param {string} [dbPath] - Optional custom database path
* @returns {boolean} True if database exists and can be opened
*/
export function isDatabaseAccessible(dbPath = ANTIGRAVITY_DB_PATH) {
let db;
try {
db = new Database(dbPath, {
readonly: true,
fileMustExist: true
});
return true;
} catch {
return false;
} finally {
if (db) {
db.close();
}
}
}
export default {
getAuthStatus,
isDatabaseAccessible
};

View File

@@ -6,46 +6,16 @@
* so this approach doesn't require any manual intervention.
*/
import { execSync } from 'child_process';
import {
TOKEN_REFRESH_INTERVAL_MS,
ANTIGRAVITY_AUTH_PORT,
ANTIGRAVITY_DB_PATH
ANTIGRAVITY_AUTH_PORT
} from './constants.js';
import { getAuthStatus } from './db/database.js';
// Cache for the extracted token
let cachedToken = null;
let tokenExtractedAt = null;
/**
* Extract token from Antigravity's SQLite database
* This is the preferred method as the DB is auto-updated
*/
function extractTokenFromDB() {
try {
const result = execSync(
`sqlite3 "${ANTIGRAVITY_DB_PATH}" "SELECT value FROM ItemTable WHERE key = 'antigravityAuthStatus';"`,
{ encoding: 'utf-8', timeout: 5000 }
);
if (!result || !result.trim()) {
throw new Error('No auth status found in database');
}
const authData = JSON.parse(result.trim());
return {
apiKey: authData.apiKey,
name: authData.name,
email: authData.email,
// Include other fields we might need
...authData
};
} catch (error) {
console.error('[Token] Database extraction failed:', error.message);
throw error;
}
}
/**
* Extract the chat params from Antigravity's HTML page (fallback method)
*/
@@ -83,7 +53,7 @@ async function extractChatParams() {
async function getTokenData() {
// Try database first (preferred - always has fresh token)
try {
const dbData = extractTokenFromDB();
const dbData = getAuthStatus();
if (dbData?.apiKey) {
console.log('[Token] Got fresh token from SQLite database');
return dbData;