Compare commits
5 Commits
36
CHANGELOG.md
Normal file
36
CHANGELOG.md
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
# AG X Changelog
|
||||||
|
|
||||||
|
## v2.0.5 (2026-05-23)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- **Critical**: Translation proxy now uses system Node.js instead of Electron binary
|
||||||
|
- Previous: Electron's `process.execPath` would start a GUI process instead of CLI Node
|
||||||
|
- Now: Auto-detects system Node via `which node` fallback to `/usr/bin/node`
|
||||||
|
- **Critical**: Removed duplicate proxy start (was running proxy twice, causing port conflicts)
|
||||||
|
- `main.js` no longer starts `apiProxy` — `ensureProxyStarted()` in `languageServer.js` handles it
|
||||||
|
- **Critical**: Added port 48080 availability check before spawning proxy
|
||||||
|
- Prevents duplicate proxy processes from leftover/orphaned instances
|
||||||
|
- **Fixed**: Welcome screen `welcome:choice` handler had double `resolve()` call
|
||||||
|
- Added `return` after Google Gemini handler to prevent fallthrough
|
||||||
|
- **Fixed**: `settings.html` close button used deprecated `remote.getCurrentWindow()`
|
||||||
|
- Now uses `ipcRenderer.send('provider:close-settings')` exclusively
|
||||||
|
- **Fixed**: `translationProxy.applyConfig()` now appends `/v1` for `openai-compat` backends
|
||||||
|
- **Fixed**: Proxy process is no longer `detached:true` with `unref()` — properly tracked as child
|
||||||
|
- **Fixed**: SingletonLock cleanup on startup to prevent stale lock issues
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Proxy stdout/stderr now piped to parent for proper logging
|
||||||
|
- `syncProviderToEndpoints()` runs on every startup (not just first run)
|
||||||
|
- Version bumped to 2.0.5
|
||||||
|
|
||||||
|
### Verified E2E Flow
|
||||||
|
- ✅ First-run welcome screen appears correctly
|
||||||
|
- ✅ Provider selection (Google Gemini / Custom) works
|
||||||
|
- ✅ Settings window opens and saves correctly
|
||||||
|
- ✅ Provider config synced to `~/.codex/endpoints.json` on every startup
|
||||||
|
- ✅ Translation proxy starts on port 48080 with correct backend config
|
||||||
|
- ✅ Language Server spawned with `--api_server_url http://127.0.0.1:48080`
|
||||||
|
- ✅ Proxy health/models/endpoints endpoints all respond correctly
|
||||||
|
- ✅ `--ag-reset` flag correctly re-triggers welcome screen
|
||||||
|
- ✅ Proxy survives LS restarts and port changes
|
||||||
|
|
||||||
130
README.md
130
README.md
@@ -1,43 +1,109 @@
|
|||||||
# AG X (Antigravity fork) v2.0.3
|
# AG X — Antigravity Fork
|
||||||
|
|
||||||
AI-Powered Code Intelligence — agentic desktop application with multi-provider AI support.
|
**AG X** is a fork of the Antigravity desktop application with enhanced multi-provider support. It allows you to use Google OAuth or any custom OpenAI-compatible API provider.
|
||||||
|
|
||||||
## What's New in v2.0.3
|

|
||||||
|
|
||||||
### 🐛 Critical Fix: Provider Selection Flow
|
|
||||||
- **Welcome screen now shows BEFORE the Language Server starts** — on first run, the user picks a provider before the LS launches, preventing the Google OAuth prompt from appearing for non-Google providers.
|
|
||||||
- **Provider config syncs to `~/.codex/endpoints.json`** — the provider choice from the welcome screen and settings panel is properly synced to the endpoints config that the translation proxy reads.
|
|
||||||
- **"Another AI Provider" flow fixed** — when user picks a custom provider, the settings panel opens and the app waits for the user to configure and save their provider before starting the Language Server.
|
|
||||||
|
|
||||||
### Changes
|
|
||||||
| File | Change |
|
|
||||||
|---|---|
|
|
||||||
| `main.js` | Restructured startup: welcome screen before LS start |
|
|
||||||
| `main.js` | Added `syncProviderToEndpoints()` function |
|
|
||||||
| `main.js` | Google Gemini handler sets active endpoint in `endpoints.json` |
|
|
||||||
| `main.js` | Custom provider handler waits for settings save before continuing |
|
|
||||||
| `providerSettings.js` | Save handler syncs to `endpoints.json` and emits `provider:settings-saved` |
|
|
||||||
| `providerSettings.js` | Added close-settings IPC listener |
|
|
||||||
| `settings.html` | Save handler shows restart notification and auto-closes |
|
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
- 🔮 Google Gemini (OAuth) — zero config
|
|
||||||
- 🔌 15+ AI Providers (OpenAI, Anthropic, DeepSeek, Ollama, etc.)
|
|
||||||
- 🧠 Built-in translation proxy for non-native backends
|
|
||||||
- 🔄 Auto-update support
|
|
||||||
- 🖥️ System tray integration
|
|
||||||
|
|
||||||
## Installation
|
- 🔄 **Multi-Provider Support** — Switch between Google OAuth and custom API providers
|
||||||
|
- ⚡ **Custom Endpoints** — Configure your own OpenAI-compatible API endpoints
|
||||||
|
- 🌐 **Translation Proxy** — Built-in translation service proxy for multi-language support
|
||||||
|
- 🔧 **Provider Settings UI** — Easy-to-use settings page for managing providers
|
||||||
|
- 📦 **Debian Package** — Installable `.deb` package for Ubuntu/Debian
|
||||||
|
- 🖥️ **System Tray** — Minimize to system tray with status indicators
|
||||||
|
- 🔒 **Secure Storage** — Provider configurations stored locally and securely
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
### Install from .deb
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo dpkg -i ag-x_2.0.3_amd64.deb
|
sudo dpkg -i ag-x_2.0.3_amd64.deb
|
||||||
|
sudo apt-get install -f # fix any missing dependencies
|
||||||
```
|
```
|
||||||
|
|
||||||
## First Run
|
### First Run
|
||||||
On first launch, AG X shows a welcome screen where you choose:
|
|
||||||
1. **Google Gemini** — Sign in with Google (OAuth, no API key needed)
|
1. Launch AG X from your application menu or run `ag-x`
|
||||||
2. **Another AI Provider** — Pick from 15+ providers and enter your API key
|
2. On first launch, you'll see the **Welcome Screen**
|
||||||
|
3. Choose your provider:
|
||||||
|
- **Google OAuth** — Use the default Antigravity/Google authentication
|
||||||
|
- **Custom Provider** — Enter your own API endpoint URL and API key
|
||||||
|
4. Start using the app!
|
||||||
|
|
||||||
|
### Configure Provider
|
||||||
|
|
||||||
|
After initial setup, you can change your provider at any time:
|
||||||
|
- Click the AG X tray icon → **Settings**
|
||||||
|
- Or use the in-app provider settings
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
ag-x/
|
||||||
|
├── dist/ # Application source (compiled)
|
||||||
|
│ ├── main.js # Electron main process
|
||||||
|
│ ├── languageServer.js # Language Server proxy
|
||||||
|
│ ├── preload.js # Electron preload script
|
||||||
|
│ ├── providerSettings.js # Provider settings window
|
||||||
|
│ ├── provider/
|
||||||
|
│ │ ├── welcome.html # First-run welcome screen
|
||||||
|
│ │ └── settings.html # Provider settings page
|
||||||
|
│ ├── services/
|
||||||
|
│ │ ├── apiProxy.js # API request proxy
|
||||||
|
│ │ ├── providerService.js # Provider management
|
||||||
|
│ │ ├── translationProxy.js # Translation proxy
|
||||||
|
│ │ └── settingsService.js # Settings persistence
|
||||||
|
│ └── ...
|
||||||
|
├── releases/
|
||||||
|
│ └── ag-x_2.0.3_amd64.deb # Debian installer package
|
||||||
|
├── icon.png # Application icon
|
||||||
|
├── trayTemplate.png # Tray icon template
|
||||||
|
├── trayTemplate@2x.png # Tray icon template (Retina)
|
||||||
|
├── package.json # Node.js package manifest
|
||||||
|
├── CHANGELOG.md # Version history
|
||||||
|
└── README.md # This file
|
||||||
|
```
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
- Provider settings: `~/.ag-x/ag-x/provider_config.json`
|
|
||||||
- Proxy endpoints: `~/.codex/endpoints.json`
|
AG X stores configuration in:
|
||||||
- Menu → AI Provider Settings to change provider anytime
|
- `~/.ag-x/ag-x/provider_config.json` — Provider settings
|
||||||
|
- `~/.codex/endpoints.json` — Language Server endpoint configuration
|
||||||
|
|
||||||
|
### Custom Provider Format
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"providerType": "custom",
|
||||||
|
"apiEndpoint": "https://your-api.example.com/v1",
|
||||||
|
"apiKey": "your-api-key",
|
||||||
|
"models": ["gpt-4", "gpt-3.5-turbo"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Building from Source
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
- Node.js 18+
|
||||||
|
- npm 9+
|
||||||
|
|
||||||
|
### Build Steps
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install
|
||||||
|
npm run build
|
||||||
|
npm run package:linux
|
||||||
|
```
|
||||||
|
|
||||||
|
The `.deb` package will be in `releases/`.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Fork of Antigravity. See original project for license details.
|
||||||
|
|
||||||
|
## Links
|
||||||
|
|
||||||
|
- **Repository**: [https://github.rommark.dev/admin/antigravity-ai-providers](https://github.rommark.dev/admin/antigravity-ai-providers)
|
||||||
|
- **Original**: Antigravity Desktop App
|
||||||
|
|||||||
39
dist/languageServer.js
vendored
39
dist/languageServer.js
vendored
@@ -187,7 +187,7 @@ function setupNodeModules(env, modules) {
|
|||||||
*/
|
*/
|
||||||
const os = require('os');
|
const os = require('os');
|
||||||
function ensureProxyStarted() {
|
function ensureProxyStarted() {
|
||||||
return new Promise((resolve) => {
|
return new Promise(async (resolve) => {
|
||||||
if (_proxyProcess) {
|
if (_proxyProcess) {
|
||||||
resolve();
|
resolve();
|
||||||
return;
|
return;
|
||||||
@@ -234,16 +234,45 @@ function ensureProxyStarted() {
|
|||||||
models: modelList.map(m => ({ id: m, object: "model", created: 1700000000, owned_by: endpoint.name }))
|
models: modelList.map(m => ({ id: m, object: "model", created: 1700000000, owned_by: endpoint.name }))
|
||||||
};
|
};
|
||||||
fs.writeFileSync(activeConfigPath, JSON.stringify(pcfg, null, 2), 'utf8');
|
fs.writeFileSync(activeConfigPath, JSON.stringify(pcfg, null, 2), 'utf8');
|
||||||
|
// Check if proxy is already running on port 48080
|
||||||
|
const net = require('net');
|
||||||
|
const proxyPortTest = await new Promise((resolve) => {
|
||||||
|
const tester = net.createConnection(48080, '127.0.0.1');
|
||||||
|
tester.on('connect', () => { tester.destroy(); resolve(true); });
|
||||||
|
tester.on('error', () => { tester.destroy(); resolve(false); });
|
||||||
|
setTimeout(() => { tester.destroy(); resolve(false); }, 1000);
|
||||||
|
});
|
||||||
|
if (proxyPortTest) {
|
||||||
|
console.log('[AG X] Translation proxy already running on port 48080, skipping spawn');
|
||||||
|
resolve();
|
||||||
|
return;
|
||||||
|
}
|
||||||
console.log('[AG X] Starting built-in Node.js translation proxy for:', endpoint.name);
|
console.log('[AG X] Starting built-in Node.js translation proxy for:', endpoint.name);
|
||||||
// Use Electron's built-in Node.js to run our translation proxy
|
// Use Electron's built-in Node.js to run our translation proxy
|
||||||
const proxyScript = path_1.default.join(__dirname, 'services', 'translationProxy.js');
|
const proxyScript = path_1.default.join(__dirname, 'services', 'translationProxy.js');
|
||||||
const nodeBin = process.execPath;
|
// Use system node (not Electron's process.execPath which starts a GUI)
|
||||||
|
const nodeBin = process.execPath.includes('electron') || process.execPath.includes('AG-X')
|
||||||
|
? (require('child_process').execSync('which node 2>/dev/null || echo /usr/bin/node').toString().trim())
|
||||||
|
: process.execPath;
|
||||||
_proxyProcess = (0, child_process_1.spawn)(nodeBin, [proxyScript], {
|
_proxyProcess = (0, child_process_1.spawn)(nodeBin, [proxyScript], {
|
||||||
stdio: 'ignore',
|
stdio: ['ignore', 'pipe', 'pipe'],
|
||||||
detached: true,
|
detached: false,
|
||||||
env: { ...process.env }
|
env: { ...process.env }
|
||||||
});
|
});
|
||||||
_proxyProcess.unref();
|
_proxyProcess.stdout?.on('data', (d) => {
|
||||||
|
for (const line of d.toString().split('\n')) {
|
||||||
|
if (line.trim()) console.log('[Proxy]', line.trim());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
_proxyProcess.stderr?.on('data', (d) => {
|
||||||
|
for (const line of d.toString().split('\n')) {
|
||||||
|
if (line.trim()) console.log('[Proxy:err]', line.trim());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
_proxyProcess.on('exit', (code) => {
|
||||||
|
console.log('[AG X] Translation proxy exited with code:', code);
|
||||||
|
_proxyProcess = null;
|
||||||
|
});
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
resolve();
|
resolve();
|
||||||
}, 500);
|
}, 500);
|
||||||
|
|||||||
24
dist/main.js
vendored
24
dist/main.js
vendored
@@ -81,12 +81,15 @@ let apiProxy;
|
|||||||
const HEADLESS = process.env.ELECTRON_OZONE_PLATFORM_HINT === 'headless';
|
const HEADLESS = process.env.ELECTRON_OZONE_PLATFORM_HINT === 'headless';
|
||||||
// When set, skip LS startup and load this URL directly (for dev iteration).
|
// When set, skip LS startup and load this URL directly (for dev iteration).
|
||||||
const DEV_URL = process.env.DEV_URL;
|
const DEV_URL = process.env.DEV_URL;
|
||||||
|
const AG_RESET = process.argv.includes('--ag-reset');
|
||||||
if (HEADLESS) {
|
if (HEADLESS) {
|
||||||
electron_1.app.commandLine.appendSwitch('ozone-platform', 'headless');
|
electron_1.app.commandLine.appendSwitch('ozone-platform', 'headless');
|
||||||
electron_1.app.commandLine.appendSwitch('headless');
|
electron_1.app.commandLine.appendSwitch('headless');
|
||||||
electron_1.app.commandLine.appendSwitch('disable-gpu');
|
electron_1.app.commandLine.appendSwitch('disable-gpu');
|
||||||
electron_1.app.commandLine.appendSwitch('no-sandbox');
|
electron_1.app.commandLine.appendSwitch('no-sandbox');
|
||||||
}
|
}
|
||||||
|
// Always disable sandbox for Linux compatibility (SUID sandbox issues)
|
||||||
|
electron_1.app.commandLine.appendSwitch('no-sandbox');
|
||||||
if (!electron_1.app.commandLine.hasSwitch('remote-debugging-port')) {
|
if (!electron_1.app.commandLine.hasSwitch('remote-debugging-port')) {
|
||||||
electron_1.app.commandLine.appendSwitch('remote-debugging-port', '0');
|
electron_1.app.commandLine.appendSwitch('remote-debugging-port', '0');
|
||||||
}
|
}
|
||||||
@@ -153,12 +156,9 @@ electron_1.app
|
|||||||
providerService = new providerService_1.ProviderService(storageManager);
|
providerService = new providerService_1.ProviderService(storageManager);
|
||||||
console.log(`[Provider] Active provider: ${providerService.getActiveProvider()}`);
|
console.log(`[Provider] Active provider: ${providerService.getActiveProvider()}`);
|
||||||
|
|
||||||
// Start API proxy if a non-Gemini provider is active
|
// NOTE: Translation proxy is now started by ensureProxyStarted() in languageServer.js
|
||||||
if (providerService.needsProxy()) {
|
// which reads ~/.codex/endpoints.json (synced by syncProviderToEndpoints) and spawns
|
||||||
apiProxy = new apiProxy_1.ApiProxy(providerService);
|
// the Node.js translation proxy on port 48080. No duplicate proxy needed here.
|
||||||
apiProxy.start();
|
|
||||||
console.log(`[Provider] API proxy started for ${providerService.getActiveProvider()}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle deep link URL from command line arguments (All platforms)
|
// Handle deep link URL from command line arguments (All platforms)
|
||||||
const deepLinkFromArg = process.argv.find((arg) => arg.startsWith('ag-x://'));
|
const deepLinkFromArg = process.argv.find((arg) => arg.startsWith('ag-x://'));
|
||||||
@@ -218,17 +218,16 @@ electron_1.app
|
|||||||
const csrf = crypto.randomUUID();
|
const csrf = crypto.randomUUID();
|
||||||
console.log(`Starting app (v${electron_1.app.getVersion()}) with dynamic port…`);
|
console.log(`Starting app (v${electron_1.app.getVersion()}) with dynamic port…`);
|
||||||
// Check if first run BEFORE starting LS so we can configure provider first
|
// Check if first run BEFORE starting LS so we can configure provider first
|
||||||
const isFirstRun = !fs_1.existsSync(providerService.configPath);
|
const isFirstRun = !fs_1.existsSync(providerService.configPath) || AG_RESET;
|
||||||
if (isFirstRun && !HEADLESS) {
|
if (isFirstRun && !HEADLESS) {
|
||||||
console.log('[Welcome] First run detected — showing provider choice screen');
|
console.log('[Welcome] First run detected — showing provider choice screen');
|
||||||
await showWelcomeScreen('about:blank');
|
await showWelcomeScreen('about:blank');
|
||||||
console.log('[Welcome] User selected provider:', providerService.getActiveProvider());
|
console.log('[Welcome] User selected provider:', providerService.getActiveProvider());
|
||||||
// Sync to endpoints.json (for Google, the handler already set the endpoint;
|
}
|
||||||
// for custom, the settings save handler synced. This is a safety sync.)
|
// ALWAYS sync the active AG X provider to endpoints.json on every startup.
|
||||||
if (providerService.getActiveProvider() !== 'google_gemini') {
|
// This ensures the Language Server reads the correct provider config.
|
||||||
|
console.log('[Sync] Syncing active provider to endpoints.json on startup');
|
||||||
syncProviderToEndpoints(providerService);
|
syncProviderToEndpoints(providerService);
|
||||||
}
|
|
||||||
}
|
|
||||||
let handle;
|
let handle;
|
||||||
const targetPort = Number(process.env.JETSKI_LS_PORT) || constants_1.DYNAMIC_PORT;
|
const targetPort = Number(process.env.JETSKI_LS_PORT) || constants_1.DYNAMIC_PORT;
|
||||||
try {
|
try {
|
||||||
@@ -467,6 +466,7 @@ function showWelcomeScreen(mainUrl) {
|
|||||||
} catch(e) { console.error('[Welcome] Failed to set Google endpoint:', e); }
|
} catch(e) { console.error('[Welcome] Failed to set Google endpoint:', e); }
|
||||||
welcomeWin.close();
|
welcomeWin.close();
|
||||||
// Don't createWindow here — LS hasn't started yet, main window opens after LS starts
|
// Don't createWindow here — LS hasn't started yet, main window opens after LS starts
|
||||||
|
return; // Don't fall through to resolve() again
|
||||||
} else {
|
} else {
|
||||||
console.log('[Welcome] User chose custom provider — opening settings');
|
console.log('[Welcome] User chose custom provider — opening settings');
|
||||||
welcomeWin.close();
|
welcomeWin.close();
|
||||||
|
|||||||
4
dist/provider/settings.html
vendored
4
dist/provider/settings.html
vendored
@@ -585,9 +585,7 @@ async function saveSettings() {
|
|||||||
updateStatus();
|
updateStatus();
|
||||||
// Auto-close settings window after a brief delay
|
// Auto-close settings window after a brief delay
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const currentWindow = require('electron').remote?.getCurrentWindow();
|
// Close via IPC — providerSettings.js handles the actual close
|
||||||
if (currentWindow) currentWindow.close();
|
|
||||||
// Fallback: close via ipcRenderer
|
|
||||||
require('electron').ipcRenderer.send('provider:close-settings');
|
require('electron').ipcRenderer.send('provider:close-settings');
|
||||||
}, 1500);
|
}, 1500);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
5
dist/services/apiProxy.js
vendored
5
dist/services/apiProxy.js
vendored
@@ -70,7 +70,10 @@ class ApiProxy {
|
|||||||
console.log(`[ApiProxy] Starting built-in Node.js translation proxy on port ${this.port}`);
|
console.log(`[ApiProxy] Starting built-in Node.js translation proxy on port ${this.port}`);
|
||||||
// Use Electron's Node.js binary to run our translation proxy
|
// Use Electron's Node.js binary to run our translation proxy
|
||||||
const proxyScript = path.join(__dirname, 'translationProxy.js');
|
const proxyScript = path.join(__dirname, 'translationProxy.js');
|
||||||
const nodeBin = process.execPath;
|
// Use system node (not Electron's process.execPath which starts a GUI)
|
||||||
|
const nodeBin = process.execPath.includes('electron') || process.execPath.includes('AG-X')
|
||||||
|
? child_process.execSync('which node 2>/dev/null || echo /usr/bin/node').toString().trim()
|
||||||
|
: process.execPath;
|
||||||
this.proxyProcess = child_process.spawn(nodeBin, [proxyScript], {
|
this.proxyProcess = child_process.spawn(nodeBin, [proxyScript], {
|
||||||
stdio: ['ignore', 'pipe', 'pipe'],
|
stdio: ['ignore', 'pipe', 'pipe'],
|
||||||
detached: false,
|
detached: false,
|
||||||
|
|||||||
4
dist/services/translationProxy.js
vendored
4
dist/services/translationProxy.js
vendored
@@ -78,6 +78,10 @@ function applyConfig(cfg) {
|
|||||||
PORT = cfg.port || 48080;
|
PORT = cfg.port || 48080;
|
||||||
BACKEND = cfg.backend_type || "openai-compat";
|
BACKEND = cfg.backend_type || "openai-compat";
|
||||||
TARGET_URL = cfg.target_url || "http://localhost:11434/v1";
|
TARGET_URL = cfg.target_url || "http://localhost:11434/v1";
|
||||||
|
// Ensure /v1 suffix for openai-compat backends
|
||||||
|
if (BACKEND === "openai-compat" && !TARGET_URL.endsWith("/v1")) {
|
||||||
|
TARGET_URL = TARGET_URL.replace(/\/+$/, "") + "/v1";
|
||||||
|
}
|
||||||
API_KEY = cfg.api_key || "";
|
API_KEY = cfg.api_key || "";
|
||||||
OAUTH_PROVIDER = cfg.oauth_provider || "";
|
OAUTH_PROVIDER = cfg.oauth_provider || "";
|
||||||
REASONING_ENABLED = cfg.reasoning_enabled !== undefined ? cfg.reasoning_enabled : true;
|
REASONING_ENABLED = cfg.reasoning_enabled !== undefined ? cfg.reasoning_enabled : true;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "ag-x",
|
"name": "ag-x",
|
||||||
"productName": "AG X",
|
"productName": "AG X",
|
||||||
"version": "2.0.3",
|
"version": "2.0.5",
|
||||||
"description": "AG X - Agentic Desktop Application",
|
"description": "AG X - Agentic Desktop Application",
|
||||||
"homepage": "https://ag-x.dev",
|
"homepage": "https://ag-x.dev",
|
||||||
"author": {
|
"author": {
|
||||||
|
|||||||
BIN
releases/ag-x_2.0.5_amd64.deb
Normal file
BIN
releases/ag-x_2.0.5_amd64.deb
Normal file
Binary file not shown.
Reference in New Issue
Block a user