AG X v2.0.3 - Antigravity fork with multi-provider support
Features: - Welcome screen on first run (provider choice before LS starts) - 15+ AI providers (Google Gemini, OpenAI, Anthropic, DeepSeek, Ollama, etc.) - Provider config syncs to endpoints.json for translation proxy - Built-in Node.js translation proxy for non-native backends - Auto-update support, tray integration, URI scheme handler
This commit is contained in:
269
dist/utils.js
vendored
Normal file
269
dist/utils.js
vendored
Normal file
@@ -0,0 +1,269 @@
|
||||
"use strict";
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || (function () {
|
||||
var ownKeys = function(o) {
|
||||
ownKeys = Object.getOwnPropertyNames || function (o) {
|
||||
var ar = [];
|
||||
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
||||
return ar;
|
||||
};
|
||||
return ownKeys(o);
|
||||
};
|
||||
return function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
})();
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.SleepBlocker = exports.showOrCreateWindow = exports.showQuitConfirmation = void 0;
|
||||
exports.setShowQuitConfirmation = setShowQuitConfirmation;
|
||||
exports.isMacOS = isMacOS;
|
||||
exports.createWindow = createWindow;
|
||||
exports.getNodeWrapperPaths = getNodeWrapperPaths;
|
||||
exports.setupNodeWrapper = setupNodeWrapper;
|
||||
const electron_1 = require("electron");
|
||||
const constants_1 = require("./constants");
|
||||
const keybindings_1 = require("./keybindings");
|
||||
const path_1 = __importDefault(require("path"));
|
||||
const fs = __importStar(require("fs"));
|
||||
const paths_1 = require("./paths");
|
||||
const loadingOverlay_1 = require("./loadingOverlay");
|
||||
exports.showQuitConfirmation = false;
|
||||
function setShowQuitConfirmation(value) {
|
||||
exports.showQuitConfirmation = value;
|
||||
}
|
||||
function isMacOS() {
|
||||
return process.platform === 'darwin';
|
||||
}
|
||||
/**
|
||||
* Reads the user's theme preference from the settings file.
|
||||
*/
|
||||
function getThemeMode() {
|
||||
try {
|
||||
const filePath = (0, paths_1.getSettingsPbPath)();
|
||||
if (!fs.existsSync(filePath)) {
|
||||
return 'DARK';
|
||||
}
|
||||
const content = fs.readFileSync(filePath, 'utf-8');
|
||||
const config = JSON.parse(content);
|
||||
const themeMode = config?.userSettings?.themeMode;
|
||||
if (themeMode && themeMode.includes('INHERIT')) {
|
||||
return electron_1.nativeTheme.shouldUseDarkColors ? 'DARK' : 'LIGHT';
|
||||
}
|
||||
if (themeMode && themeMode.includes('LIGHT')) {
|
||||
return 'LIGHT';
|
||||
}
|
||||
return 'DARK';
|
||||
}
|
||||
catch (e) {
|
||||
console.error('Error reading theme mode:', e);
|
||||
return 'DARK';
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Ensures the app is visible in the dock for MacOS with the icon set.
|
||||
* When refocusing the app after being hidden in the dock, the icon is sometimes lost.
|
||||
* This ensures the icon is always visible.
|
||||
*/
|
||||
function ensureAppIsInDock() {
|
||||
void electron_1.app.dock?.show();
|
||||
if (isMacOS() && electron_1.app.dock) {
|
||||
const iconPath = path_1.default.join(__dirname, '..', 'icon.png');
|
||||
electron_1.app.dock.setIcon(electron_1.nativeImage.createFromPath(iconPath));
|
||||
}
|
||||
}
|
||||
// ---------------------------------------------------------------------------
|
||||
// Window Management
|
||||
// ---------------------------------------------------------------------------
|
||||
/**
|
||||
* Creates and returns a new BrowserWindow pointed at `url`.
|
||||
* Uses a hidden title bar with native traffic lights on macOS.
|
||||
* Node integration is disabled and context isolation is enabled for security.
|
||||
*/
|
||||
function createWindow(url) {
|
||||
ensureAppIsInDock();
|
||||
const theme = getThemeMode().toUpperCase();
|
||||
const isLight = theme.includes('LIGHT');
|
||||
const backgroundColor = isLight ? '#FAFAFA' : '#131313';
|
||||
const foregroundColor = isLight ? '#383A42' : '#FAFAFA';
|
||||
const win = new electron_1.BrowserWindow({
|
||||
width: 1400,
|
||||
height: 900,
|
||||
title: electron_1.app.getName(),
|
||||
icon: path_1.default.join(__dirname, '..', 'icon.png'),
|
||||
titleBarStyle: 'hidden',
|
||||
titleBarOverlay: isMacOS()
|
||||
? false
|
||||
: {
|
||||
color: backgroundColor,
|
||||
symbolColor: foregroundColor,
|
||||
height: 30,
|
||||
},
|
||||
backgroundColor,
|
||||
trafficLightPosition: { x: 12, y: 12 },
|
||||
webPreferences: {
|
||||
nodeIntegration: false,
|
||||
contextIsolation: true,
|
||||
preload: path_1.default.join(__dirname, 'preload.js'),
|
||||
},
|
||||
});
|
||||
win.webContents.setWindowOpenHandler((details) => {
|
||||
void electron_1.shell.openExternal(details.url);
|
||||
return { action: 'deny' };
|
||||
});
|
||||
(0, loadingOverlay_1.attachLoadingOverlay)(win, foregroundColor, backgroundColor);
|
||||
(0, keybindings_1.registerKeybindings)(win, {
|
||||
createNewWindow: () => {
|
||||
void createWindow(url);
|
||||
},
|
||||
onQuitRequested: () => {
|
||||
exports.showQuitConfirmation = true;
|
||||
electron_1.app.quit();
|
||||
},
|
||||
});
|
||||
void win.loadURL(url);
|
||||
return win;
|
||||
}
|
||||
/**
|
||||
* Focuses a window if it exists, or creates a new one.
|
||||
*/
|
||||
const showOrCreateWindow = (port) => {
|
||||
const wins = electron_1.BrowserWindow.getAllWindows();
|
||||
if (wins.length > 0) {
|
||||
wins[0].show();
|
||||
wins[0].focus();
|
||||
}
|
||||
else {
|
||||
createWindow(`${constants_1.WINDOW_ORIGIN}:${port}/`);
|
||||
}
|
||||
};
|
||||
exports.showOrCreateWindow = showOrCreateWindow;
|
||||
/**
|
||||
* Manages the power save blocker to keep the computer awake.
|
||||
*/
|
||||
class SleepBlocker {
|
||||
constructor() {
|
||||
this.currentBlockerId = null;
|
||||
}
|
||||
static getInstance() {
|
||||
if (!SleepBlocker.instance) {
|
||||
SleepBlocker.instance = new SleepBlocker();
|
||||
}
|
||||
return SleepBlocker.instance;
|
||||
}
|
||||
shouldKeepComputerAwake(keep) {
|
||||
if (keep) {
|
||||
if (this.currentBlockerId === null) {
|
||||
this.currentBlockerId = electron_1.powerSaveBlocker.start('prevent-display-sleep');
|
||||
console.log('Power save blocker started:', this.currentBlockerId);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (this.currentBlockerId !== null) {
|
||||
electron_1.powerSaveBlocker.stop(this.currentBlockerId);
|
||||
console.log('Power save blocker stopped:', this.currentBlockerId);
|
||||
this.currentBlockerId = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
exports.SleepBlocker = SleepBlocker;
|
||||
function getNodeWrapperPaths(envPath, os, isPackaged, userDataPath, baseDir) {
|
||||
const delimiter = os === 'win32' ? ';' : ':';
|
||||
if (!isPackaged) {
|
||||
const devBinPath = path_1.default.join(baseDir, '..', 'node_modules', '.bin');
|
||||
return {
|
||||
newEnvPath: `${devBinPath}${delimiter}${envPath || ''}`,
|
||||
nodeWrapperPath: undefined,
|
||||
binPath: undefined,
|
||||
};
|
||||
}
|
||||
const binPath = path_1.default.join(userDataPath, 'bin');
|
||||
const nodeWrapperPath = path_1.default.join(binPath, os === 'win32' ? 'agy-node.cmd' : 'agy-node');
|
||||
return {
|
||||
newEnvPath: `${binPath}${delimiter}${envPath || ''}`,
|
||||
nodeWrapperPath,
|
||||
binPath,
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Sets up a wrapper script for Node.js that runs Electron as Node.
|
||||
* This allows running standard Node scripts using the Electron binary.
|
||||
*/
|
||||
function setupNodeWrapper(env) {
|
||||
const userDataPath = electron_1.app.isPackaged ? electron_1.app.getPath('userData') : '';
|
||||
// Windows environment variables are case-insensitive, but when copying process.env
|
||||
// into a plain object, we might get 'Path' instead of 'PATH'. We need to find
|
||||
// the actual key used to avoid creating case-duplicate keys (e.g. 'Path' and 'PATH')
|
||||
// which can confuse child_process.spawn on Windows.
|
||||
const isWindows = process.platform === 'win32';
|
||||
const pathKey = isWindows
|
||||
? Object.keys(env).find((k) => k.toUpperCase() === 'PATH') || 'PATH'
|
||||
: 'PATH';
|
||||
const { newEnvPath, nodeWrapperPath, binPath } = getNodeWrapperPaths(env[pathKey], process.platform, electron_1.app.isPackaged, userDataPath, __dirname);
|
||||
env[pathKey] = newEnvPath;
|
||||
// In non-packaged dev mode, we don't create a wrapper and it'll just use machine node
|
||||
if (!nodeWrapperPath || !binPath) {
|
||||
return;
|
||||
}
|
||||
if (!fs.existsSync(binPath)) {
|
||||
fs.mkdirSync(binPath, { recursive: true });
|
||||
}
|
||||
let nodeWrapperContent = '';
|
||||
switch (process.platform) {
|
||||
case 'win32':
|
||||
nodeWrapperContent = `@echo off\nset ELECTRON_RUN_AS_NODE=1\n"${process.execPath}" %*\n`;
|
||||
break;
|
||||
case 'darwin': {
|
||||
// Use the Helper app instead of the main executable to prevent macOS
|
||||
// from bouncing a new Dock icon when this script is executed. The Helper
|
||||
// has LSUIElement=true in its Info.plist, running it invisibly.
|
||||
const appName = path_1.default.basename(process.execPath);
|
||||
let electronBinary = process.execPath;
|
||||
const helperPath = path_1.default.join(path_1.default.dirname(process.execPath), '..', 'Frameworks', `${appName} Helper.app`, 'Contents', 'MacOS', `${appName} Helper`);
|
||||
if (fs.existsSync(helperPath)) {
|
||||
electronBinary = helperPath;
|
||||
}
|
||||
nodeWrapperContent = `#!/bin/sh\nELECTRON_RUN_AS_NODE=1 exec "${electronBinary}" "$@"\n`;
|
||||
break;
|
||||
}
|
||||
default: // linux, etc.
|
||||
nodeWrapperContent = `#!/bin/sh\nELECTRON_RUN_AS_NODE=1 exec "${process.execPath}" "$@"\n`;
|
||||
break;
|
||||
}
|
||||
try {
|
||||
const existingContent = fs.existsSync(nodeWrapperPath)
|
||||
? fs.readFileSync(nodeWrapperPath, 'utf-8')
|
||||
: '';
|
||||
if (existingContent !== nodeWrapperContent) {
|
||||
fs.writeFileSync(nodeWrapperPath, nodeWrapperContent);
|
||||
if (process.platform !== 'win32') {
|
||||
fs.chmodSync(nodeWrapperPath, 0o755);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
console.error(`Failed to create node wrapper: ${err}`);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user