File size: 8,442 Bytes
f7b1814
 
 
5b11160
f7b1814
 
 
5b11160
 
 
 
 
 
 
 
 
 
 
f7b1814
 
5b11160
8695653
5b11160
 
 
 
f7b1814
 
5b11160
 
 
f7b1814
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5b11160
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f7b1814
 
5b11160
 
 
 
 
 
 
 
 
 
 
f7b1814
 
 
 
 
 
 
 
 
 
 
 
5b11160
 
f7b1814
 
5b11160
f7b1814
 
 
 
 
 
 
 
 
 
5b11160
f7b1814
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
import { execSync } from 'child_process';
import { platform, homedir } from 'os';
import { join } from 'path';
import { existsSync, readFileSync } from 'fs';

/**
 * Intelligent Version Detection for Antigravity
 *
 * Detects versions from the local Antigravity installation's product.json.
 * Two version values are tracked:
 *   - "version" field      → X-Client-Version header (API version gate)
 *   - "ideVersion" field   → User-Agent version string
 *
 * Detection priority (for each):
 *   1. Environment variable override
 *   2. product.json from local Antigravity app
 *   3. OS-specific detection (macOS plist / Windows exe)
 *   4. Hardcoded fallback
 */

// Fallback for User-Agent version (ideVersion in product.json)
const FALLBACK_USER_AGENT_VERSION = process.env.FALLBACK_ANTIGRAVITY_VERSION || '2.0.3';

// Fallback for X-Client-Version (top-level "version" in product.json)
// Can be overridden via ANTIGRAVITY_CLIENT_VERSION_FALLBACK env var
const FALLBACK_CLIENT_VERSION = process.env.ANTIGRAVITY_CLIENT_VERSION_FALLBACK || '1.110.0';

let cachedUserAgent = null;
let cachedClientVersion = null;
let cachedProductJson = undefined; // undefined = not yet attempted
let loggedVersionInfo = false;

/**
 * Compares two semver-ish version strings (X.Y.Z).
 * @returns {boolean} True if v1 > v2
 */
function isVersionHigher(v1, v2) {
    const parts1 = v1.split('.').map(Number);
    const parts2 = v2.split('.').map(Number);

    for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
        const p1 = parts1[i] || 0;
        const p2 = parts2[i] || 0;
        if (p1 > p2) return true;
        if (p1 < p2) return false;
    }
    return false;
}

/**
 * Returns platform-specific search paths for product.json.
 */
function getProductJsonPaths() {
    const os = platform();
    const paths = [];

    if (os === 'darwin') {
        paths.push('/Applications/Antigravity.app/Contents/Resources/app/product.json');
        paths.push(join(homedir(), 'Applications', 'Antigravity.app', 'Contents', 'Resources', 'app', 'product.json'));
    } else if (os === 'win32') {
        const localAppData = process.env.LOCALAPPDATA;
        const programFiles = process.env.ProgramFiles || 'C:\\Program Files';
        if (localAppData) {
            paths.push(join(localAppData, 'Programs', 'Antigravity', 'resources', 'app', 'product.json'));
        }
        paths.push(join(programFiles, 'Antigravity', 'resources', 'app', 'product.json'));
    } else {
        paths.push('/usr/share/antigravity/resources/app/product.json');
        paths.push('/opt/antigravity/resources/app/product.json');
        paths.push('/opt/Antigravity/resources/app/product.json');
        paths.push(join(homedir(), '.local', 'share', 'antigravity', 'resources', 'app', 'product.json'));
        paths.push('/snap/antigravity/current/resources/app/product.json');
    }

    return paths;
}

/**
 * Find and parse product.json from the local Antigravity installation.
 * Caches the result after first attempt.
 * @returns {Object|null} Parsed product.json or null
 */
function getProductJson() {
    if (cachedProductJson !== undefined) return cachedProductJson;

    for (const p of getProductJsonPaths()) {
        try {
            if (existsSync(p)) {
                const content = JSON.parse(readFileSync(p, 'utf8'));
                if (content && (content.version || content.ideVersion)) {
                    cachedProductJson = content;
                    return content;
                }
            }
        } catch (e) {
            // Continue to next path
        }
    }

    cachedProductJson = null;
    return null;
}

/**
 * Log version detection results once at startup (lazy import to avoid circular deps).
 */
function logVersionInfo(version, source) {
    if (loggedVersionInfo) return;
    loggedVersionInfo = true;

    import('./logger.js').then(({ logger }) => {
        if (source === 'fallback') {
            logger.warn(`X-Client-Version: using hardcoded fallback ${version} — product.json not found. Set ANTIGRAVITY_CLIENT_VERSION (exact version) or ANTIGRAVITY_CLIENT_VERSION_FALLBACK (fallback value) env var to override.`);
        } else {
            logger.debug(`X-Client-Version: ${version} (source: ${source})`);
        }
    }).catch(() => {});
}

/**
 * Get the X-Client-Version value for API requests.
 * Priority: ANTIGRAVITY_CLIENT_VERSION env var > product.json "version" > hardcoded fallback
 * @returns {string} Version string (e.g. "1.110.0")
 */
export function getClientVersion() {
    if (cachedClientVersion) return cachedClientVersion;

    if (process.env.ANTIGRAVITY_CLIENT_VERSION) {
        cachedClientVersion = process.env.ANTIGRAVITY_CLIENT_VERSION;
        logVersionInfo(cachedClientVersion, 'env');
        return cachedClientVersion;
    }

    const product = getProductJson();
    if (product?.version) {
        cachedClientVersion = product.version;
        logVersionInfo(cachedClientVersion, 'product.json');
        return cachedClientVersion;
    }

    cachedClientVersion = FALLBACK_CLIENT_VERSION;
    logVersionInfo(cachedClientVersion, 'fallback');
    return cachedClientVersion;
}

/**
 * Get the User-Agent version string.
 * Priority: FALLBACK_ANTIGRAVITY_VERSION env var > product.json "ideVersion" > OS detection > fallback
 * @returns {{ version: string, source: string }}
 */
function getUserAgentVersionConfig() {
    if (process.env.FALLBACK_ANTIGRAVITY_VERSION) {
        return { version: process.env.FALLBACK_ANTIGRAVITY_VERSION, source: 'env' };
    }

    const product = getProductJson();
    if (product?.ideVersion && isVersionHigher(product.ideVersion, FALLBACK_USER_AGENT_VERSION)) {
        return { version: product.ideVersion, source: 'product.json' };
    }

    // OS-specific detection (legacy — reads app binary metadata directly)
    const os = platform();
    let detectedVersion = null;
    try {
        if (os === 'darwin') {
            detectedVersion = getVersionMacos();
        } else if (os === 'win32') {
            detectedVersion = getVersionWindows();
        }
    } catch (error) {
        // Silently fail and use fallback
    }

    if (detectedVersion && isVersionHigher(detectedVersion, FALLBACK_USER_AGENT_VERSION)) {
        return { version: detectedVersion, source: 'local' };
    }

    return { version: FALLBACK_USER_AGENT_VERSION, source: 'fallback' };
}

/**
 * Generate a simplified User-Agent string used by the Antigravity binary.
 * Format: "antigravity/version os/arch"
 * @returns {string} The User-Agent string
 */
export function generateSmartUserAgent() {
    if (cachedUserAgent) return cachedUserAgent;

    const { version } = getUserAgentVersionConfig();
    const os = platform();
    const architecture = process.arch;

    const osName = os === 'darwin' ? 'darwin' : (os === 'win32' ? 'win32' : 'linux');

    cachedUserAgent = `antigravity/${version} ${osName}/${architecture}`;
    return cachedUserAgent;
}

/**
 * MacOS-specific version detection using plutil
 */
function getVersionMacos() {
    const appPath = '/Applications/Antigravity.app';
    const plistPath = join(appPath, 'Contents/Info.plist');

    if (!existsSync(plistPath)) return null;

    try {
        const version = execSync(`plutil -extract CFBundleShortVersionString raw "${plistPath}"`, { encoding: 'utf8' }).trim();
        if (/^\d+\.\d+\.\d+/.test(version)) {
            return version;
        }
    } catch (e) {
        // plutil failed or file not found
    }
    return null;
}

/**
 * Windows-specific version detection using PowerShell
 */
function getVersionWindows() {
    try {
        const localAppData = process.env.LOCALAPPDATA;
        const programFiles = process.env.ProgramFiles || 'C:\\Program Files';

        const possiblePaths = [
            join(localAppData, 'Programs', 'Antigravity', 'Antigravity.exe'),
            join(programFiles, 'Antigravity', 'Antigravity.exe')
        ];

        for (const exePath of possiblePaths) {
            if (existsSync(exePath)) {
                const cmd = `powershell -Command "(Get-Item '${exePath}').VersionInfo.FileVersion"`;
                const version = execSync(cmd, { encoding: 'utf8' }).trim();
                const match = version.match(/^(\d+\.\d+\.\d+)/);
                if (match) return match[1];
            }
        }
    } catch (e) {
        // PowerShell or path issues
    }
    return null;
}