opencode-antigravity-config 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/main.js ADDED
@@ -0,0 +1,307 @@
1
+ const { app, BrowserWindow, ipcMain, shell, dialog } = require('electron');
2
+ const path = require('path');
3
+ const fs = require('fs');
4
+ const os = require('os');
5
+ const https = require('https');
6
+ const { exec, spawn } = require('child_process');
7
+ const configData = require('./config-data.js');
8
+
9
+ let mainWindow;
10
+ const configDir = path.join(os.homedir(), '.config', 'opencode');
11
+
12
+ const CONFIG_VERSION = '1.0.0';
13
+ const CONFIG_DATE = '2026-02-20';
14
+ const VERSION_CHECK_URL = '';
15
+
16
+ const BUNDLED_CONFIGS = [
17
+ { name: "opencode.json", key: "opencode_json", sensitive: false },
18
+ { name: "oh-my-opencode.jsonc", key: "oh_my_opencode_jsonc", sensitive: false },
19
+ { name: "dcp.jsonc", key: "dcp_jsonc", sensitive: false },
20
+ { name: "supermemory.jsonc", key: "supermemory_jsonc", sensitive: true },
21
+ { name: "opencode-sync.jsonc", key: "opencode_sync_jsonc", sensitive: true },
22
+ { name: "package.json", key: "package_json", sensitive: false }
23
+ ];
24
+
25
+ function createWindow() {
26
+ mainWindow = new BrowserWindow({
27
+ width: 860,
28
+ height: 680,
29
+ frame: false,
30
+ transparent: true,
31
+ resizable: false,
32
+ webPreferences: {
33
+ nodeIntegration: false,
34
+ contextIsolation: true,
35
+ preload: path.join(__dirname, 'preload.js')
36
+ },
37
+ icon: fs.existsSync(path.join(__dirname, 'app.ico'))
38
+ ? path.join(__dirname, 'app.ico')
39
+ : fs.existsSync(path.join(__dirname, 'app.png'))
40
+ ? path.join(__dirname, 'app.png')
41
+ : undefined
42
+ });
43
+ mainWindow.loadFile('index.html');
44
+ }
45
+
46
+ app.whenReady().then(() => {
47
+ createWindow();
48
+ app.on('activate', () => {
49
+ if (BrowserWindow.getAllWindows().length === 0) createWindow();
50
+ });
51
+ });
52
+
53
+ app.on('window-all-closed', () => {
54
+ if (process.platform !== 'darwin') app.quit();
55
+ });
56
+
57
+ // Window Controls
58
+ ipcMain.on('window-minimize', () => { if (mainWindow) mainWindow.minimize(); });
59
+ ipcMain.on('window-close', () => { if (mainWindow) mainWindow.close(); });
60
+ ipcMain.on('open-config-folder', () => { if (fs.existsSync(configDir)) shell.openPath(configDir); });
61
+ ipcMain.on('run-auth-login', () => {
62
+ if (process.platform === 'win32') {
63
+ spawn('cmd.exe', ['/c', 'start', 'cmd', '/k', 'opencode auth login'], { detached: true, stdio: 'ignore' });
64
+ }
65
+ });
66
+
67
+ function runCommand(cmd) {
68
+ return new Promise(resolve => {
69
+ exec(cmd, { timeout: 10000 }, (err, stdout) => resolve(err ? null : stdout.trim()));
70
+ });
71
+ }
72
+
73
+ // Config Metadata
74
+ ipcMain.handle('get-config-meta', async () => ({
75
+ version: CONFIG_VERSION, date: CONFIG_DATE, fileCount: Object.keys(configData).length
76
+ }));
77
+
78
+ // System Check
79
+ ipcMain.handle('check-system', async () => {
80
+ const items = [];
81
+ const nodeVer = await runCommand('node -v');
82
+ items.push(nodeVer ? { label: 'Node.js', value: nodeVer, status: 'ok' } : { label: 'Node.js', value: 'Not found', status: 'fail' });
83
+ const npmVer = await runCommand('npm -v');
84
+ items.push(npmVer ? { label: 'npm', value: 'v' + npmVer, status: 'ok' } : { label: 'npm', value: 'Not found', status: 'fail' });
85
+ const ocVer = await runCommand('opencode --version');
86
+ items.push(ocVer ? { label: 'OpenCode', value: ocVer, status: 'ok' } : { label: 'OpenCode', value: 'Not found — npm i -g opencode@latest', status: 'warn' });
87
+ const bk = Object.keys(configData).length;
88
+ items.push({ label: 'Embedded Configs', value: `${bk}/6 files`, status: bk === 6 ? 'ok' : 'fail' });
89
+ if (fs.existsSync(configDir)) {
90
+ const f = fs.readdirSync(configDir).filter(x => x.endsWith('.json') || x.endsWith('.jsonc'));
91
+ items.push(f.length > 0 ? { label: 'Existing Config', value: `${f.length} files (will backup)`, status: 'warn' } : { label: 'Config Dir', value: 'Exists, empty', status: 'ok' });
92
+ } else {
93
+ items.push({ label: 'Config Dir', value: 'Will be created', status: 'ok' });
94
+ }
95
+ items.push({ label: 'System', value: `${os.type()} ${os.release()} | ${os.userInfo().username}`, status: 'ok' });
96
+ return { items };
97
+ });
98
+
99
+ // Get Existing Keys
100
+ ipcMain.handle('get-existing-keys', async () => {
101
+ const keys = { supermemoryKey: '', openSyncKey: '', openSyncUrl: '' };
102
+ try {
103
+ if (!fs.existsSync(configDir)) return keys;
104
+ const smPath = path.join(configDir, 'supermemory.jsonc');
105
+ if (fs.existsSync(smPath)) { const c = fs.readFileSync(smPath, 'utf8'); const m = c.match(/"apiKey"\s*:\s*"([^"]+)"/); if (m && m[1] && m[1] !== "__SUPERMEMORY_API_KEY__") keys.supermemoryKey = m[1]; }
106
+ const osPath = path.join(configDir, 'opencode-sync.jsonc');
107
+ if (fs.existsSync(osPath)) { const c = fs.readFileSync(osPath, 'utf8'); const mk = c.match(/"apiKey"\s*:\s*"([^"]+)"/); if (mk && mk[1] && mk[1] !== "__OPENSYNC_API_KEY__") keys.openSyncKey = mk[1]; const mu = c.match(/"convexUrl"\s*:\s*"([^"]+)"/); if (mu && mu[1] && mu[1] !== "__OPENSYNC_CONVEX_URL__") keys.openSyncUrl = mu[1]; }
108
+ } catch (e) { }
109
+ return keys;
110
+ });
111
+
112
+ // Preview Config
113
+ ipcMain.handle('preview-config', async (ev, fileName) => {
114
+ const cfg = BUNDLED_CONFIGS.find(c => c.name === fileName);
115
+ if (!cfg || !configData[cfg.key]) return { error: 'Not found' };
116
+ return { content: Buffer.from(configData[cfg.key], 'base64').toString('utf8'), fileName: cfg.name };
117
+ });
118
+
119
+ // Diff Config
120
+ ipcMain.handle('preview-final-opencode', async (ev, plugins) => {
121
+ const b64 = configData['opencode_json'];
122
+ let content = Buffer.from(b64, 'base64').toString('utf8');
123
+ if (plugins) {
124
+ try {
125
+ const parsed = JSON.parse(content);
126
+ if (Array.isArray(parsed.plugin)) {
127
+ parsed.plugin = parsed.plugin.filter(p => plugins.includes(p));
128
+ content = JSON.stringify(parsed, null, 4);
129
+ }
130
+ } catch (e) { }
131
+ }
132
+ return { fileName: 'opencode.json', content };
133
+ });
134
+
135
+ ipcMain.handle('diff-config', async (ev, fileName) => {
136
+ const cfg = BUNDLED_CONFIGS.find(c => c.name === fileName);
137
+ if (!cfg || !configData[cfg.key]) return { error: 'Not found' };
138
+ const newContent = Buffer.from(configData[cfg.key], 'base64').toString('utf8');
139
+ const ep = path.join(configDir, fileName);
140
+ if (!fs.existsSync(ep)) return { isNew: true, newContent, fileName };
141
+ const oldContent = fs.readFileSync(ep, 'utf8');
142
+ const oL = oldContent.split('\n'), nL = newContent.split('\n');
143
+ const max = Math.max(oL.length, nL.length);
144
+ const diffLines = [];
145
+ for (let i = 0; i < max; i++) {
146
+ const o = i < oL.length ? oL[i] : undefined, n = i < nL.length ? nL[i] : undefined;
147
+ if (o === undefined) diffLines.push({ type: 'add', content: n });
148
+ else if (n === undefined) diffLines.push({ type: 'del', content: o });
149
+ else if (o.trimEnd() !== n.trimEnd()) { diffLines.push({ type: 'del', content: o }); diffLines.push({ type: 'add', content: n }); }
150
+ else diffLines.push({ type: 'same', content: o });
151
+ }
152
+ return { isNew: false, hasChanges: diffLines.some(d => d.type !== 'same'), diffLines, fileName };
153
+ });
154
+
155
+ // Export / Import
156
+ ipcMain.handle('export-settings', async (ev, settings) => {
157
+ const r = await dialog.showSaveDialog(mainWindow, { title: 'Export Settings', defaultPath: path.join(os.homedir(), 'Desktop', 'antigravity-settings.json'), filters: [{ name: 'JSON', extensions: ['json'] }] });
158
+ if (r.canceled) return { saved: false };
159
+ fs.writeFileSync(r.filePath, JSON.stringify(settings, null, 2), 'utf8');
160
+ return { saved: true, path: r.filePath };
161
+ });
162
+
163
+ ipcMain.handle('import-settings', async () => {
164
+ const r = await dialog.showOpenDialog(mainWindow, { title: 'Import Settings', filters: [{ name: 'JSON', extensions: ['json'] }], properties: ['openFile'] });
165
+ if (r.canceled || !r.filePaths.length) return { loaded: false };
166
+ try { return { loaded: true, settings: JSON.parse(fs.readFileSync(r.filePaths[0], 'utf8')) }; }
167
+ catch (e) { return { loaded: false, error: e.message }; }
168
+ });
169
+
170
+ // Version Check
171
+ ipcMain.handle('check-version', async () => {
172
+ if (!VERSION_CHECK_URL) return { current: CONFIG_VERSION, latest: CONFIG_VERSION, upToDate: true, noEndpoint: true };
173
+ return new Promise(resolve => {
174
+ const req = https.get(VERSION_CHECK_URL, { timeout: 5000 }, res => {
175
+ let d = ''; res.on('data', c => d += c);
176
+ res.on('end', () => { try { const j = JSON.parse(d); resolve({ current: CONFIG_VERSION, latest: j.version || CONFIG_VERSION, upToDate: (j.version || CONFIG_VERSION) === CONFIG_VERSION }); } catch (e) { resolve({ current: CONFIG_VERSION, latest: CONFIG_VERSION, upToDate: true, error: 'Parse error' }); } });
177
+ });
178
+ req.on('error', () => resolve({ current: CONFIG_VERSION, latest: CONFIG_VERSION, upToDate: true, error: 'Network error' }));
179
+ req.on('timeout', () => { req.destroy(); resolve({ current: CONFIG_VERSION, latest: CONFIG_VERSION, upToDate: true, error: 'Timeout' }); });
180
+ });
181
+ });
182
+
183
+ // Uninstall
184
+ ipcMain.handle('uninstall-config', async () => {
185
+ if (!fs.existsSync(configDir)) return { success: false, message: 'Config directory not found' };
186
+ const files = fs.readdirSync(configDir).filter(f => f.endsWith('.json') || f.endsWith('.jsonc'));
187
+ if (!files.length) return { success: false, message: 'No config files found' };
188
+ try {
189
+ const ds = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
190
+ const bd = path.join(configDir, `.uninstall-backup-${ds}`);
191
+ fs.mkdirSync(bd, { recursive: true });
192
+ for (const f of files) { fs.copyFileSync(path.join(configDir, f), path.join(bd, f)); fs.unlinkSync(path.join(configDir, f)); }
193
+ return { success: true, removed: files, backupDir: path.basename(bd) };
194
+ } catch (e) { return { success: false, message: e.message }; }
195
+ });
196
+
197
+ // Install Flow
198
+ function sendLog(type, message) { if (mainWindow) mainWindow.webContents.send('install-log', { type, message }); }
199
+ function sendProgress(pct) { if (mainWindow) mainWindow.webContents.send('install-progress', pct); }
200
+
201
+ ipcMain.on('start-install', async (ev, config) => {
202
+ try {
203
+ sendLog('info', 'Creating config directory...'); await new Promise(r => setTimeout(r, 100));
204
+ if (!fs.existsSync(configDir)) { fs.mkdirSync(configDir, { recursive: true }); sendLog('success', `Created: ${configDir}`); }
205
+ else sendLog('info', `Directory exists: ${configDir}`);
206
+ sendProgress(10);
207
+
208
+ if (config.backup && fs.existsSync(configDir)) {
209
+ const ds = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
210
+ const bd = path.join(configDir, `.backup-${ds}`);
211
+ const ef = fs.readdirSync(configDir).filter(f => f.endsWith('.json') || f.endsWith('.jsonc'));
212
+ if (ef.length) { sendLog('info', 'Backing up...'); fs.mkdirSync(bd); for (const f of ef) { fs.copyFileSync(path.join(configDir, f), path.join(bd, f)); sendLog('info', `Backed up: ${f}`); await new Promise(r => setTimeout(r, 60)); } sendLog('success', `Backup: ${path.basename(bd)}`); }
213
+ }
214
+ sendProgress(25); await new Promise(r => setTimeout(r, 100));
215
+
216
+ const toInstall = BUNDLED_CONFIGS;
217
+ let step = 25; const inc = toInstall.length > 0 ? 40 / toInstall.length : 40;
218
+
219
+ for (const cfg of toInstall) {
220
+ const b64 = configData[cfg.key]; if (!b64) { sendLog('warn', `Skip: ${cfg.name}`); step += inc; continue; }
221
+ let content = Buffer.from(b64, 'base64').toString('utf8');
222
+ if (cfg.sensitive) {
223
+ if (cfg.name === 'supermemory.jsonc' && config.supermemoryKey) { content = content.replace('__SUPERMEMORY_API_KEY__', config.supermemoryKey); sendLog('info', 'Supermemory key injected'); }
224
+ if (cfg.name === 'opencode-sync.jsonc') { if (config.openSyncKey) { content = content.replace('__OPENSYNC_API_KEY__', config.openSyncKey); sendLog('info', 'OpenSync key injected'); } if (config.openSyncUrl) { content = content.replace('__OPENSYNC_CONVEX_URL__', config.openSyncUrl); sendLog('info', 'OpenSync URL injected'); } }
225
+ }
226
+ // Filter plugins in opencode.json based on user selection
227
+ if (cfg.name === 'opencode.json' && config.selectedPlugins) {
228
+ try {
229
+ const parsed = JSON.parse(content);
230
+ if (Array.isArray(parsed.plugin)) {
231
+ const original = parsed.plugin.length;
232
+ parsed.plugin = parsed.plugin.filter(p => config.selectedPlugins.includes(p));
233
+ sendLog('info', `Plugins: ${parsed.plugin.length}/${original} selected`);
234
+ parsed.plugin.forEach(p => sendLog('info', ` ✓ ${p}`));
235
+ content = JSON.stringify(parsed, null, 4);
236
+ }
237
+ } catch (e) { sendLog('warn', `Plugin filter skipped: ${e.message}`); }
238
+ }
239
+ // Inject custom agent models into oh-my-opencode.jsonc
240
+ if (cfg.name === 'oh-my-opencode.jsonc' && config.agentModels) {
241
+ try {
242
+ const agentKeyMap = { multimodal_looker: 'multimodal-looker' };
243
+ let changed = 0;
244
+
245
+ Object.entries(config.agentModels).forEach(([key, model]) => {
246
+ const jsonKey = agentKeyMap[key] || key;
247
+ const re = new RegExp(`("${jsonKey}"\\s*:\\s*\\{[^}]*?"model"\\s*:\\s*)"([^"]*)"`, 's');
248
+ const match = content.match(re);
249
+ if (match && match[2] !== model) {
250
+ content = content.replace(re, `$1"${model}"`);
251
+ sendLog('info', `Agent ${jsonKey}: ${match[2].split('/').pop()} → ${model.split('/').pop()}`);
252
+ changed++;
253
+ }
254
+ });
255
+
256
+ // Also update categories based on agent groups
257
+ const heavyModel = config.agentModels.sisyphus || 'google/antigravity-gemini-3-1-pro';
258
+ const lightModel = config.agentModels.librarian || 'google/antigravity-gemini-3-flash';
259
+ const catMap = {
260
+ 'visual-engineering': heavyModel, 'ultrabrain': heavyModel, 'deep': heavyModel,
261
+ 'artistry': heavyModel, 'unspecified-high': heavyModel, 'unspecified-low': heavyModel,
262
+ 'quick': lightModel, 'writing': lightModel
263
+ };
264
+
265
+ Object.entries(catMap).forEach(([cat, model]) => {
266
+ const re = new RegExp(`("${cat}"\\s*:\\s*\\{[^}]*?"model"\\s*:\\s*)"([^"]*)"`, 's');
267
+ const match = content.match(re);
268
+ if (match && match[2] !== model) {
269
+ content = content.replace(re, `$1"${model}"`);
270
+ changed++;
271
+ }
272
+ });
273
+
274
+ if (changed > 0) sendLog('success', `Agent models: ${changed} updated`);
275
+ else sendLog('info', 'Agent models: no changes (using defaults)');
276
+ } catch (e) { sendLog('warn', `Agent model injection skipped: ${e.message}`); }
277
+ }
278
+ fs.writeFileSync(path.join(configDir, cfg.name), content, 'utf8');
279
+ sendLog('success', `Installed: ${cfg.name}`); step += inc; sendProgress(Math.min(Math.floor(step), 65));
280
+ await new Promise(r => setTimeout(r, 120));
281
+ }
282
+ sendProgress(65);
283
+
284
+ if (config.npmInstall) {
285
+ sendLog('info', ''); sendLog('info', 'Running npm install...');
286
+ const ok = await new Promise(resolve => {
287
+ const p = spawn('npm', ['install'], { cwd: configDir, shell: true, env: { ...process.env } });
288
+ let prog = 65;
289
+ p.stdout.on('data', d => { for (const l of d.toString().trim().split('\n')) { if (l.trim()) { sendLog('info', ` npm: ${l.trim()}`); prog = Math.min(prog + 2, 88); sendProgress(prog); } } });
290
+ p.stderr.on('data', d => { for (const l of d.toString().trim().split('\n')) { if (l.trim() && !l.includes('npm warn')) sendLog('warn', ` npm: ${l.trim()}`); } });
291
+ p.on('close', c => resolve(c === 0)); p.on('error', () => resolve(false));
292
+ });
293
+ sendLog(ok ? 'success' : 'warn', ok ? 'npm dependencies installed' : 'npm install had warnings');
294
+ } else sendLog('info', 'Skipped npm install');
295
+
296
+ sendProgress(90); await new Promise(r => setTimeout(r, 100));
297
+ sendLog('info', ''); sendLog('info', '─── Verification ───');
298
+ const cnt = fs.existsSync(configDir) ? fs.readdirSync(configDir).filter(f => f.endsWith('.json') || f.endsWith('.jsonc')).length : 0;
299
+ sendLog('success', `${cnt} config files installed`);
300
+ sendProgress(100); await new Promise(r => setTimeout(r, 200));
301
+ sendLog('info', ''); sendLog('success', '🎉 Installation complete!');
302
+ mainWindow.webContents.send('install-complete', { success: true, configPath: configDir });
303
+ } catch (e) {
304
+ sendLog('error', `ERROR: ${e.message}`);
305
+ mainWindow.webContents.send('install-complete', { success: false, error: e.message });
306
+ }
307
+ });
package/package.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "opencode-antigravity-config",
3
+ "version": "1.0.0",
4
+ "description": "GUI installer for Antigravity OpenCode configuration",
5
+ "main": "main.js",
6
+ "bin": {
7
+ "opencode-agc": "cli.js"
8
+ },
9
+ "scripts": {
10
+ "start": "electron ."
11
+ },
12
+ "keywords": [
13
+ "opencode",
14
+ "antigravity",
15
+ "config",
16
+ "installer",
17
+ "electron"
18
+ ],
19
+ "author": "",
20
+ "license": "ISC",
21
+ "dependencies": {
22
+ "@fontsource/fira-code": "^5.2.7",
23
+ "electron": "^40.6.0",
24
+ "jimp": "^1.6.0",
25
+ "png-to-ico": "^3.0.1"
26
+ }
27
+ }
package/preload.js ADDED
@@ -0,0 +1,22 @@
1
+ const { contextBridge, ipcRenderer } = require('electron');
2
+
3
+ contextBridge.exposeInMainWorld('api', {
4
+ minimize: () => ipcRenderer.send('window-minimize'),
5
+ close: () => ipcRenderer.send('window-close'),
6
+ checkSystem: () => ipcRenderer.invoke('check-system'),
7
+ getExistingKeys: () => ipcRenderer.invoke('get-existing-keys'),
8
+ getConfigMeta: () => ipcRenderer.invoke('get-config-meta'),
9
+ previewConfig: (fileName) => ipcRenderer.invoke('preview-config', fileName),
10
+ diffConfig: (fileName) => ipcRenderer.invoke('diff-config', fileName),
11
+ exportSettings: (settings) => ipcRenderer.invoke('export-settings', settings),
12
+ importSettings: () => ipcRenderer.invoke('import-settings'),
13
+ checkVersion: () => ipcRenderer.invoke('check-version'),
14
+ uninstallConfig: () => ipcRenderer.invoke('uninstall-config'),
15
+ previewFinalOpencode: (p) => ipcRenderer.invoke('preview-final-opencode', p),
16
+ startInstall: (config) => ipcRenderer.send('start-install', config),
17
+ onInstallProgress: (cb) => { ipcRenderer.removeAllListeners('install-progress'); ipcRenderer.on('install-progress', (e, d) => cb(d)); },
18
+ onInstallLog: (cb) => { ipcRenderer.removeAllListeners('install-log'); ipcRenderer.on('install-log', (e, d) => cb(d)); },
19
+ onInstallComplete: (cb) => { ipcRenderer.removeAllListeners('install-complete'); ipcRenderer.on('install-complete', (e, d) => cb(d)); },
20
+ openConfigFolder: () => ipcRenderer.send('open-config-folder'),
21
+ runAuthLogin: () => ipcRenderer.send('run-auth-login')
22
+ });