antigravity-autopilot 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/AutoAccept.ps1 ADDED
@@ -0,0 +1,305 @@
1
+ # Antigravity Auto-Accept - Floating Panel
2
+ # A small always-on-top window for toggling auto-accept
3
+ # Run with: powershell -WindowStyle Hidden -File AutoAccept.ps1
4
+
5
+ Add-Type -AssemblyName System.Windows.Forms
6
+ Add-Type -AssemblyName System.Drawing
7
+
8
+ Add-Type @"
9
+ using System;
10
+ using System.Runtime.InteropServices;
11
+ public class WinAPI {
12
+ [DllImport("user32.dll")]
13
+ public static extern IntPtr GetForegroundWindow();
14
+ [DllImport("user32.dll", SetLastError=true)]
15
+ public static extern int GetWindowThreadProcessId(IntPtr hWnd, out int processId);
16
+ [DllImport("user32.dll")]
17
+ public static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);
18
+ }
19
+ "@
20
+
21
+ # ---- State ----
22
+ $script:IsEnabled = $false
23
+ $script:Count = 0
24
+ $script:Timer = $null
25
+ $HWND_TOPMOST = [IntPtr](-1)
26
+ $SWP_NOSIZE = 0x0001
27
+ $SWP_NOMOVE = 0x0002
28
+
29
+ # ---- Fonts & Colors ----
30
+ $fontMain = New-Object System.Drawing.Font("Segoe UI", 9)
31
+ $fontBig = New-Object System.Drawing.Font("Segoe UI", 11, [System.Drawing.FontStyle]::Bold)
32
+ $fontMono = New-Object System.Drawing.Font("Consolas", 8)
33
+ $colorBg = [System.Drawing.Color]::FromArgb(30, 30, 30)
34
+ $colorPanel = [System.Drawing.Color]::FromArgb(40, 40, 40)
35
+ $colorOn = [System.Drawing.Color]::FromArgb(0, 200, 100)
36
+ $colorOff = [System.Drawing.Color]::FromArgb(100, 100, 100)
37
+ $colorText = [System.Drawing.Color]::White
38
+ $colorDim = [System.Drawing.Color]::FromArgb(160, 160, 160)
39
+ $colorAccent = [System.Drawing.Color]::FromArgb(90, 180, 255)
40
+
41
+ # ---- Main Form ----
42
+ $form = New-Object System.Windows.Forms.Form
43
+ $form.Text = "AG Auto-Accept"
44
+ $form.Size = New-Object System.Drawing.Size(280, 320)
45
+ $form.StartPosition = "Manual"
46
+ $form.Location = New-Object System.Drawing.Point(
47
+ ([System.Windows.Forms.Screen]::PrimaryScreen.WorkingArea.Right - 290),
48
+ ([System.Windows.Forms.Screen]::PrimaryScreen.WorkingArea.Bottom - 330)
49
+ )
50
+ $form.FormBorderStyle = "FixedSingle"
51
+ $form.MaximizeBox = $false
52
+ $form.MinimizeBox = $true
53
+ $form.BackColor = $colorBg
54
+ $form.ForeColor = $colorText
55
+ $form.Font = $fontMain
56
+ $form.TopMost = $true
57
+ $form.ShowInTaskbar = $true
58
+
59
+ # ---- Title bar area ----
60
+ $lblTitle = New-Object System.Windows.Forms.Label
61
+ $lblTitle.Text = "⚡ Antigravity Auto-Accept"
62
+ $lblTitle.Font = $fontBig
63
+ $lblTitle.ForeColor = $colorAccent
64
+ $lblTitle.Location = New-Object System.Drawing.Point(12, 12)
65
+ $lblTitle.Size = New-Object System.Drawing.Size(256, 24)
66
+ $form.Controls.Add($lblTitle)
67
+
68
+ $lblSub = New-Object System.Windows.Forms.Label
69
+ $lblSub.Text = "Tự động accept Antigravity commands"
70
+ $lblSub.Font = $fontMono
71
+ $lblSub.ForeColor = $colorDim
72
+ $lblSub.Location = New-Object System.Drawing.Point(12, 36)
73
+ $lblSub.Size = New-Object System.Drawing.Size(256, 16)
74
+ $form.Controls.Add($lblSub)
75
+
76
+ # ---- Separator ----
77
+ $sep = New-Object System.Windows.Forms.Label
78
+ $sep.Location = New-Object System.Drawing.Point(12, 57)
79
+ $sep.Size = New-Object System.Drawing.Size(254, 1)
80
+ $sep.BackColor = [System.Drawing.Color]::FromArgb(60, 60, 60)
81
+ $form.Controls.Add($sep)
82
+
83
+ # ---- Status indicator (big circle + text) ----
84
+ $pnlStatus = New-Object System.Windows.Forms.Panel
85
+ $pnlStatus.Location = New-Object System.Drawing.Point(12, 68)
86
+ $pnlStatus.Size = New-Object System.Drawing.Size(254, 80)
87
+ $pnlStatus.BackColor = $colorPanel
88
+ $form.Controls.Add($pnlStatus)
89
+
90
+ $picDot = New-Object System.Windows.Forms.PictureBox
91
+ $picDot.Location = New-Object System.Drawing.Point(16, 20)
92
+ $picDot.Size = New-Object System.Drawing.Size(40, 40)
93
+ $picDot.BackColor = [System.Drawing.Color]::Transparent
94
+ $pnlStatus.Controls.Add($picDot)
95
+
96
+ function Draw-Dot([bool]$on) {
97
+ $bmp = New-Object System.Drawing.Bitmap(40, 40)
98
+ $g = [System.Drawing.Graphics]::FromImage($bmp)
99
+ $g.SmoothingMode = [System.Drawing.Drawing2D.SmoothingMode]::AntiAlias
100
+ $g.Clear([System.Drawing.Color]::Transparent)
101
+ if ($on) {
102
+ $g.FillEllipse([System.Drawing.Brushes]::LimeGreen, 4, 4, 32, 32)
103
+ # lightning bolt
104
+ $pen = New-Object System.Drawing.Pen([System.Drawing.Color]::White, 2.5)
105
+ $pts = @(
106
+ [System.Drawing.Point]::new(24, 8),
107
+ [System.Drawing.Point]::new(16, 22),
108
+ [System.Drawing.Point]::new(22, 22),
109
+ [System.Drawing.Point]::new(14, 32)
110
+ )
111
+ $g.DrawLines($pen, $pts)
112
+ $pen.Dispose()
113
+ }
114
+ else {
115
+ $g.FillEllipse([System.Drawing.Brushes]::DimGray, 4, 4, 32, 32)
116
+ $pen = New-Object System.Drawing.Pen([System.Drawing.Color]::Gray, 2)
117
+ $g.DrawLine($pen, 12, 12, 28, 28)
118
+ $g.DrawLine($pen, 28, 12, 12, 28)
119
+ $pen.Dispose()
120
+ }
121
+ $g.Dispose()
122
+ $picDot.Image = $bmp
123
+ }
124
+ Draw-Dot $false
125
+
126
+ $lblStatus = New-Object System.Windows.Forms.Label
127
+ $lblStatus.Text = "OFF"
128
+ $lblStatus.Font = New-Object System.Drawing.Font("Segoe UI", 20, [System.Drawing.FontStyle]::Bold)
129
+ $lblStatus.ForeColor = $colorOff
130
+ $lblStatus.Location = New-Object System.Drawing.Point(66, 14)
131
+ $lblStatus.Size = New-Object System.Drawing.Size(100, 36)
132
+ $pnlStatus.Controls.Add($lblStatus)
133
+
134
+ $lblStatusSub = New-Object System.Windows.Forms.Label
135
+ $lblStatusSub.Text = "Click button to enable"
136
+ $lblStatusSub.Font = $fontMono
137
+ $lblStatusSub.ForeColor = $colorDim
138
+ $lblStatusSub.Location = New-Object System.Drawing.Point(66, 50)
139
+ $lblStatusSub.Size = New-Object System.Drawing.Size(180, 20)
140
+ $pnlStatus.Controls.Add($lblStatusSub)
141
+
142
+ # ---- Stats ----
143
+ $pnlStats = New-Object System.Windows.Forms.Panel
144
+ $pnlStats.Location = New-Object System.Drawing.Point(12, 158)
145
+ $pnlStats.Size = New-Object System.Drawing.Size(254, 50)
146
+ $pnlStats.BackColor = $colorPanel
147
+ $form.Controls.Add($pnlStats)
148
+
149
+ $lblCountLabel = New-Object System.Windows.Forms.Label
150
+ $lblCountLabel.Text = "Accepted"
151
+ $lblCountLabel.Font = $fontMono
152
+ $lblCountLabel.ForeColor = $colorDim
153
+ $lblCountLabel.Location = New-Object System.Drawing.Point(12, 8)
154
+ $lblCountLabel.Size = New-Object System.Drawing.Size(80, 16)
155
+ $pnlStats.Controls.Add($lblCountLabel)
156
+
157
+ $lblCount = New-Object System.Windows.Forms.Label
158
+ $lblCount.Text = "0"
159
+ $lblCount.Font = New-Object System.Drawing.Font("Segoe UI", 14, [System.Drawing.FontStyle]::Bold)
160
+ $lblCount.ForeColor = $colorAccent
161
+ $lblCount.Location = New-Object System.Drawing.Point(100, 4)
162
+ $lblCount.Size = New-Object System.Drawing.Size(80, 30)
163
+ $pnlStats.Controls.Add($lblCount)
164
+
165
+ $lblUnit = New-Object System.Windows.Forms.Label
166
+ $lblUnit.Text = "commands"
167
+ $lblUnit.Font = $fontMono
168
+ $lblUnit.ForeColor = $colorDim
169
+ $lblUnit.Location = New-Object System.Drawing.Point(180, 10)
170
+ $lblUnit.Size = New-Object System.Drawing.Size(70, 16)
171
+ $pnlStats.Controls.Add($lblUnit)
172
+
173
+ # ---- Toggle Button ----
174
+ $btnToggle = New-Object System.Windows.Forms.Button
175
+ $btnToggle.Text = "▶ ENABLE AUTO-ACCEPT"
176
+ $btnToggle.Font = New-Object System.Drawing.Font("Segoe UI", 10, [System.Drawing.FontStyle]::Bold)
177
+ $btnToggle.Size = New-Object System.Drawing.Size(254, 44)
178
+ $btnToggle.Location = New-Object System.Drawing.Point(12, 220)
179
+ $btnToggle.FlatStyle = [System.Windows.Forms.FlatStyle]::Flat
180
+ $btnToggle.FlatAppearance.BorderSize = 0
181
+ $btnToggle.BackColor = [System.Drawing.Color]::FromArgb(0, 150, 80)
182
+ $btnToggle.ForeColor = $colorText
183
+ $btnToggle.Cursor = [System.Windows.Forms.Cursors]::Hand
184
+ $form.Controls.Add($btnToggle)
185
+
186
+ # ---- Interval label ----
187
+ $lblInterval = New-Object System.Windows.Forms.Label
188
+ $lblInterval.Text = "Interval: 800ms | Only when VS Code focused"
189
+ $lblInterval.Font = $fontMono
190
+ $lblInterval.ForeColor = $colorDim
191
+ $lblInterval.Location = New-Object System.Drawing.Point(12, 274)
192
+ $lblInterval.Size = New-Object System.Drawing.Size(254, 16)
193
+ $lblInterval.TextAlign = [System.Drawing.ContentAlignment]::MiddleCenter
194
+ $form.Controls.Add($lblInterval)
195
+
196
+ # ---- System tray icon ----
197
+ $tray = New-Object System.Windows.Forms.NotifyIcon
198
+ $tray.Text = "Antigravity Auto-Accept: OFF"
199
+ $tray.Visible = $true
200
+
201
+ $trayBmp = New-Object System.Drawing.Bitmap(16, 16)
202
+ $tg = [System.Drawing.Graphics]::FromImage($trayBmp)
203
+ $tg.Clear([System.Drawing.Color]::Transparent)
204
+ $tg.FillEllipse([System.Drawing.Brushes]::CornflowerBlue, 1, 1, 14, 14)
205
+ $tg.Dispose()
206
+ $tray.Icon = [System.Drawing.Icon]::FromHandle($trayBmp.GetHicon())
207
+
208
+ $trayMenu = New-Object System.Windows.Forms.ContextMenuStrip
209
+ $trayShow = New-Object System.Windows.Forms.ToolStripMenuItem "Show Panel"
210
+ $trayToggle = New-Object System.Windows.Forms.ToolStripMenuItem "Enable Auto-Accept"
211
+ $traySep = New-Object System.Windows.Forms.ToolStripSeparator
212
+ $trayExit = New-Object System.Windows.Forms.ToolStripMenuItem "Exit"
213
+ $trayMenu.Items.AddRange(@($trayShow, $trayToggle, $traySep, $trayExit))
214
+ $tray.ContextMenuStrip = $trayMenu
215
+
216
+ # ---- Core: Send Alt+A ----
217
+ function Send-AltA {
218
+ $fgWnd = [WinAPI]::GetForegroundWindow()
219
+ $fgPid = 0
220
+ [WinAPI]::GetWindowThreadProcessId($fgWnd, [ref]$fgPid) | Out-Null
221
+ $fgProc = Get-Process -Id $fgPid -ErrorAction SilentlyContinue
222
+ if ($fgProc -and $fgProc.ProcessName -match 'Code') {
223
+ [System.Windows.Forms.SendKeys]::SendWait("%a")
224
+ $script:Count++
225
+ $lblCount.Text = "$($script:Count)"
226
+ $tray.Text = "AG Auto-Accept: ON ($($script:Count) accepted)"
227
+ }
228
+ }
229
+
230
+ # ---- Toggle logic ----
231
+ function Toggle {
232
+ $script:IsEnabled = -not $script:IsEnabled
233
+
234
+ if ($script:IsEnabled) {
235
+ $script:Count = 0
236
+ $lblCount.Text = "0"
237
+
238
+ # Start timer
239
+ $script:Timer = New-Object System.Windows.Forms.Timer
240
+ $script:Timer.Interval = 800
241
+ $script:Timer.Add_Tick({ Send-AltA })
242
+ $script:Timer.Start()
243
+
244
+ # Update UI
245
+ $lblStatus.Text = "ON"
246
+ $lblStatus.ForeColor = $colorOn
247
+ $lblStatusSub.Text = "Sending Alt+A every 800ms..."
248
+ $btnToggle.Text = "⏸ DISABLE AUTO-ACCEPT"
249
+ $btnToggle.BackColor = [System.Drawing.Color]::FromArgb(180, 50, 50)
250
+ $pnlStatus.BackColor = [System.Drawing.Color]::FromArgb(20, 60, 30)
251
+ $trayToggle.Text = "Disable Auto-Accept"
252
+ $tray.Text = "AG Auto-Accept: ON"
253
+ Draw-Dot $true
254
+
255
+ $tray.ShowBalloonTip(2000, "Auto-Accept ON", "Tự động accept Antigravity commands", [System.Windows.Forms.ToolTipIcon]::Info)
256
+ }
257
+ else {
258
+ if ($script:Timer) {
259
+ $script:Timer.Stop()
260
+ $script:Timer.Dispose()
261
+ $script:Timer = $null
262
+ }
263
+
264
+ # Update UI
265
+ $lblStatus.Text = "OFF"
266
+ $lblStatus.ForeColor = $colorOff
267
+ $lblStatusSub.Text = "Click button to enable"
268
+ $btnToggle.Text = "▶ ENABLE AUTO-ACCEPT"
269
+ $btnToggle.BackColor = [System.Drawing.Color]::FromArgb(0, 150, 80)
270
+ $pnlStatus.BackColor = $colorPanel
271
+ $trayToggle.Text = "Enable Auto-Accept"
272
+ $tray.Text = "AG Auto-Accept: OFF"
273
+ Draw-Dot $false
274
+ }
275
+ }
276
+
277
+ # ---- Event handlers ----
278
+ $btnToggle.Add_Click({ Toggle })
279
+ $trayToggle.Add_Click({ Toggle })
280
+ $trayShow.Add_Click({ $form.Show(); $form.BringToFront() })
281
+ $tray.Add_DoubleClick({ $form.Show(); $form.BringToFront() })
282
+ $trayExit.Add_Click({
283
+ if ($script:Timer) { $script:Timer.Stop(); $script:Timer.Dispose() }
284
+ $tray.Visible = $false
285
+ $tray.Dispose()
286
+ $form.Close()
287
+ })
288
+ $form.Add_FormClosing({
289
+ param($s, $e)
290
+ # Minimize to tray instead of closing
291
+ if ($e.CloseReason -eq [System.Windows.Forms.CloseReason]::UserClosing) {
292
+ $e.Cancel = $true
293
+ $form.Hide()
294
+ $tray.ShowBalloonTip(1500, "Still running!", "Double-click tray icon to reopen.", [System.Windows.Forms.ToolTipIcon]::Info)
295
+ }
296
+ })
297
+
298
+ # Keep always on top
299
+ $form.Add_Shown({
300
+ [WinAPI]::SetWindowPos($form.Handle, $HWND_TOPMOST, 0, 0, 0, 0, ($SWP_NOSIZE -bor $SWP_NOMOVE)) | Out-Null
301
+ })
302
+
303
+ # ---- Launch ----
304
+ $tray.ShowBalloonTip(2500, "Antigravity Auto-Accept", "✅ Running! Click toggle button to enable.", [System.Windows.Forms.ToolTipIcon]::Info)
305
+ [System.Windows.Forms.Application]::Run($form)
package/README.md ADDED
@@ -0,0 +1,38 @@
1
+ # Antigravity Auto-Accept
2
+
3
+ Auto-accept Antigravity tool call prompts with a **one-click toggle** in the VS Code status bar.
4
+
5
+ ## Features
6
+
7
+ - ⚡ **Status bar toggle** — Click to enable/disable auto-accept
8
+ - 🎯 **Smart targeting** — Only sends keystrokes when VS Code is focused
9
+ - ⌨️ **Keyboard shortcut** — `Ctrl+Shift+F12` to toggle
10
+ - 📊 **Accept counter** — Shows how many commands were auto-accepted
11
+ - ⚙️ **Configurable** — Adjust interval, auto-start on boot
12
+
13
+ ## Installation
14
+
15
+ ```powershell
16
+ # Run from this directory
17
+ .\install.ps1
18
+ ```
19
+
20
+ Then reload VS Code (`Ctrl+Shift+P` → "Developer: Reload Window").
21
+
22
+ ## Usage
23
+
24
+ 1. Look for **`$(circle-slash) Auto-Accept: OFF`** in the status bar (bottom right)
25
+ 2. Click it or press `Ctrl+Shift+F12` to toggle ON
26
+ 3. When ON (⚡ yellow background), all Antigravity commands will be auto-accepted
27
+ 4. Click again to turn OFF
28
+
29
+ ## Settings
30
+
31
+ | Setting | Default | Description |
32
+ |---------|---------|-------------|
33
+ | `antigravityAutoAccept.intervalMs` | `800` | Interval between accept attempts (ms) |
34
+ | `antigravityAutoAccept.enabledOnStartup` | `false` | Auto-enable on VS Code start |
35
+
36
+ ## How It Works
37
+
38
+ Sends `Alt+A` (the Antigravity accept shortcut) via PowerShell `SendKeys` at a configurable interval, but **only** when VS Code is the foreground window.
package/extension.js ADDED
@@ -0,0 +1,453 @@
1
+ // @ts-check
2
+ 'use strict';
3
+
4
+ const vscode = require('vscode');
5
+ const path = require('path');
6
+ const { fork } = require('child_process');
7
+
8
+ const PATCHER = path.join(__dirname, 'patcher.js');
9
+
10
+ // ─── State ───────────────────────────────────────────────────────────────────
11
+ /** @type {vscode.StatusBarItem} */
12
+ let statusBarItem;
13
+ /** @type {boolean} */
14
+ let isPatchApplied = false;
15
+ /** @type {AntigravityPanelProvider | null} */
16
+ let panelProvider = null;
17
+ /** @type {{ basePath: string|null, files: any[], patched: boolean } | null} */
18
+ let _cachedStatus = null;
19
+
20
+ // ─── Child Process Bridge ─────────────────────────────────────────────────────
21
+
22
+ /**
23
+ * Runs patcher.js in a child process.
24
+ * All heavy file I/O lives in patcher.js — never blocks the extension host.
25
+ * @param {'status'|'apply'|'revert'} command
26
+ * @returns {Promise<any>}
27
+ */
28
+ function runPatcher(command) {
29
+ return new Promise((resolve, reject) => {
30
+ const child = fork(PATCHER, [], { silent: true });
31
+ const channel = vscode.window.createOutputChannel('AutoAccept');
32
+
33
+ child.on('message', (msg) => {
34
+ if (!msg || typeof msg !== 'object') return;
35
+ const m = /** @type {{type:string,msg?:string}} */(msg);
36
+ if (m.type === 'log') {
37
+ console.log(m.msg);
38
+ channel.appendLine(m.msg || '');
39
+ } else {
40
+ resolve(msg); // status or result message
41
+ }
42
+ });
43
+
44
+ child.on('error', (err) => {
45
+ channel.appendLine(`[AutoAccept] fork error: ${err.message}`);
46
+ reject(err);
47
+ });
48
+
49
+ child.on('exit', (code) => {
50
+ if (code !== 0) {
51
+ resolve({
52
+ type: 'result',
53
+ success: false,
54
+ message: `Process exited with code ${code}. Check Output > AutoAccept for details.`,
55
+ });
56
+ }
57
+ });
58
+
59
+ child.send({ command });
60
+ });
61
+ }
62
+
63
+ // ─── Patch Manager (async, non-blocking) ─────────────────────────────────────
64
+
65
+ /**
66
+ * Gets patch status via child process.
67
+ * @returns {Promise<{basePath:string|null, patched:boolean, files:Array<{label:string,patched:boolean,exists:boolean}>}>}
68
+ */
69
+ async function getPatchStatus() {
70
+ try {
71
+ const res = /** @type {any} */(await runPatcher('status'));
72
+ const files = res.files || [];
73
+ _cachedStatus = { basePath: res.basePath || null, files, patched: files.some((/** @type {any} */f) => f.patched) };
74
+ return _cachedStatus;
75
+ } catch {
76
+ return { basePath: null, patched: false, files: [] };
77
+ }
78
+ }
79
+
80
+ /**
81
+ * Applies the patch via child process.
82
+ * @returns {Promise<{success:boolean, message:string}>}
83
+ */
84
+ async function applyPatch() {
85
+ try {
86
+ const res = /** @type {any} */(await runPatcher('apply'));
87
+ const success = res.success === true;
88
+ isPatchApplied = success;
89
+ await refreshStatus();
90
+ return { success, message: res.message || '' };
91
+ } catch (e) {
92
+ return { success: false, message: `❌ Error: ${e}` };
93
+ }
94
+ }
95
+
96
+ /**
97
+ * Reverts the patch via child process.
98
+ * @returns {Promise<{success:boolean, message:string}>}
99
+ */
100
+ async function revertPatch() {
101
+ try {
102
+ const res = /** @type {any} */(await runPatcher('revert'));
103
+ isPatchApplied = false;
104
+ await refreshStatus();
105
+ return { success: res.success === true, message: res.message || '' };
106
+ } catch (e) {
107
+ return { success: false, message: `❌ Error: ${e}` };
108
+ }
109
+ }
110
+
111
+ /** Refreshes status and updates all UI elements. */
112
+ async function refreshStatus() {
113
+ const status = await getPatchStatus();
114
+ isPatchApplied = status.patched;
115
+ updateStatusBarFromCache();
116
+ if (panelProvider) panelProvider.sendStatus(status);
117
+ }
118
+
119
+ // ─── Sidebar WebView ──────────────────────────────────────────────────────
120
+
121
+ class AntigravityPanelProvider {
122
+ /** @param {vscode.ExtensionContext} context */
123
+ constructor(context) {
124
+ this._context = context;
125
+ /** @type {vscode.WebviewView | null} */
126
+ this._view = null;
127
+ }
128
+
129
+ /** @param {vscode.WebviewView} webviewView */
130
+ resolveWebviewView(webviewView) {
131
+ this._view = webviewView;
132
+ webviewView.webview.options = { enableScripts: true };
133
+ webviewView.webview.html = this._getHtml();
134
+
135
+ webviewView.webview.onDidReceiveMessage(async (msg) => {
136
+ if (msg.command === 'apply') {
137
+ this._postLoading('⏳ Patching...');
138
+ const result = await applyPatch();
139
+ vscode.window.showInformationMessage(result.message);
140
+ } else if (msg.command === 'revert') {
141
+ this._postLoading('⏳ Reverting...');
142
+ const result = await revertPatch();
143
+ vscode.window.showInformationMessage(result.message);
144
+ } else if (msg.command === 'refresh') {
145
+ this._postLoading('⏳ Checking...');
146
+ await refreshStatus();
147
+ }
148
+ });
149
+
150
+ // Initial load
151
+ refreshStatus();
152
+ }
153
+
154
+ /** @param {string} text */
155
+ _postLoading(text) {
156
+ if (this._view) this._view.webview.postMessage({ command: 'loading', text });
157
+ }
158
+
159
+ /** @param {{basePath:string|null,patched:boolean,files:any[]}} status */
160
+ sendStatus(status) {
161
+ if (!this._view) return;
162
+ this._view.webview.postMessage({
163
+ command: 'update',
164
+ patched: status.patched,
165
+ basePath: status.basePath,
166
+ files: status.files,
167
+ });
168
+ }
169
+
170
+ /** @deprecated use sendStatus */
171
+ updateState() { refreshStatus(); }
172
+
173
+ _getHtml() {
174
+ return /* html */`<!DOCTYPE html>
175
+ <html lang="en">
176
+ <head>
177
+ <meta charset="UTF-8">
178
+ <meta name="viewport" content="width=device-width,initial-scale=1">
179
+ <style>
180
+ *{box-sizing:border-box;margin:0;padding:0}
181
+ body{
182
+ font-family:'Segoe UI',sans-serif;
183
+ background:var(--vscode-sideBar-background);
184
+ color:var(--vscode-foreground);
185
+ padding:16px;user-select:none;
186
+ }
187
+ .header{
188
+ display:flex;align-items:center;gap:8px;
189
+ margin-bottom:16px;padding-bottom:12px;
190
+ border-bottom:1px solid var(--vscode-panel-border);
191
+ }
192
+ .header-icon{font-size:20px}
193
+ .header-title{font-size:13px;font-weight:600;letter-spacing:.3px}
194
+ .header-sub{font-size:11px;color:var(--vscode-descriptionForeground);margin-top:2px}
195
+
196
+ .status-card{
197
+ border-radius:8px;padding:16px;margin-bottom:12px;
198
+ background:var(--vscode-editor-background);
199
+ border:1px solid var(--vscode-panel-border);
200
+ transition:border-color .3s,background .3s;
201
+ }
202
+ .status-card.patched{border-color:#4ec94e;background:rgba(78,201,78,.07)}
203
+ .status-card.not-found{border-color:#e06c75;background:rgba(224,108,117,.07)}
204
+ .status-row{display:flex;align-items:center;gap:12px}
205
+ .dot{
206
+ width:36px;height:36px;border-radius:50%;
207
+ display:flex;align-items:center;justify-content:center;
208
+ font-size:18px;flex-shrink:0;background:#3c3c3c;
209
+ transition:background .3s;
210
+ }
211
+ .dot.patched{background:#4ec94e}
212
+ .dot.not-found{background:#e06c75}
213
+ .status-label{font-size:18px;font-weight:700;line-height:1}
214
+ .status-label.patched{color:#4ec94e}
215
+ .status-label.pending{color:#e5c07b}
216
+ .status-label.not-found{color:#e06c75}
217
+ .status-label.loading{color:var(--vscode-descriptionForeground)}
218
+ .status-desc{font-size:11px;color:var(--vscode-descriptionForeground);margin-top:4px}
219
+
220
+ .path-box{
221
+ font-size:10px;color:var(--vscode-descriptionForeground);
222
+ background:var(--vscode-editor-background);
223
+ border:1px solid var(--vscode-panel-border);
224
+ border-radius:4px;padding:6px 8px;margin-bottom:10px;
225
+ word-break:break-all;
226
+ }
227
+
228
+ .files-list{margin-bottom:12px}
229
+ .file-item{
230
+ display:flex;align-items:center;gap:6px;
231
+ font-size:11px;padding:4px 0;
232
+ }
233
+ .file-dot{width:8px;height:8px;border-radius:50%;flex-shrink:0}
234
+ .file-dot.patched{background:#4ec94e}
235
+ .file-dot.pending{background:#e5c07b}
236
+
237
+ .btn{
238
+ width:100%;padding:10px;border:none;border-radius:6px;
239
+ font-size:12px;font-weight:700;letter-spacing:.5px;
240
+ cursor:pointer;transition:background .2s,transform .1s;
241
+ font-family:inherit;margin-bottom:6px;
242
+ }
243
+ .btn:active{transform:scale(.98)}
244
+ .btn:disabled{opacity:.5;cursor:not-allowed}
245
+ .btn-apply{background:#0e7a4c;color:#fff}
246
+ .btn-apply:hover:not(:disabled){background:#0f9058}
247
+ .btn-revert{background:#5a1a1a;color:#fff}
248
+ .btn-revert:hover:not(:disabled){background:#7a2020}
249
+ .btn-refresh{background:var(--vscode-button-secondaryBackground);color:var(--vscode-button-secondaryForeground)}
250
+
251
+ .note{
252
+ margin-top:10px;font-size:10px;
253
+ color:var(--vscode-descriptionForeground);
254
+ text-align:center;line-height:1.5;
255
+ }
256
+ </style>
257
+ </head>
258
+ <body>
259
+ <div class="header">
260
+ <span class="header-icon">⚡</span>
261
+ <div>
262
+ <div class="header-title">Antigravity Auto-Accept</div>
263
+ <div class="header-sub">Patches "Always Proceed" to auto-run</div>
264
+ </div>
265
+ </div>
266
+
267
+ <div class="status-card" id="card">
268
+ <div class="status-row">
269
+ <div class="dot" id="dot">⊘</div>
270
+ <div>
271
+ <div class="status-label loading" id="lbl">Loading...</div>
272
+ <div class="status-desc" id="desc">Detecting Antigravity...</div>
273
+ </div>
274
+ </div>
275
+ </div>
276
+
277
+ <div class="path-box" id="pathBox" style="display:none"></div>
278
+
279
+ <div class="files-list" id="filesList"></div>
280
+
281
+ <button class="btn btn-apply" id="btnApply" onclick="send('apply')" style="display:none">⚡ APPLY PATCH</button>
282
+ <button class="btn btn-revert" id="btnRevert" onclick="send('revert')" style="display:none">↩ REVERT PATCH</button>
283
+ <button class="btn btn-refresh" onclick="send('refresh')">🔄 Refresh Status</button>
284
+
285
+ <div class="note" id="noteBox"></div>
286
+
287
+ <script>
288
+ const vscode = acquireVsCodeApi();
289
+ function send(cmd) {
290
+ document.getElementById('btnApply').disabled = true;
291
+ document.getElementById('btnRevert').disabled = true;
292
+ document.getElementById('btnRefresh') && (document.getElementById('btnRefresh').disabled = true);
293
+ vscode.postMessage({ command: cmd });
294
+ }
295
+ send('refresh');
296
+
297
+ window.addEventListener('message', e => {
298
+ const { command, patched, basePath, files, text } = e.data;
299
+
300
+ if (command === 'loading') {
301
+ document.getElementById('lbl').className = 'status-label loading';
302
+ document.getElementById('lbl').textContent = text || '⏳ Working...';
303
+ document.getElementById('desc').textContent = 'Please wait...';
304
+ return;
305
+ }
306
+
307
+ if (command !== 'update') return;
308
+
309
+ // Re-enable buttons
310
+ document.getElementById('btnApply').disabled = false;
311
+ document.getElementById('btnRevert').disabled = false;
312
+
313
+ const notFound = !basePath;
314
+
315
+ document.getElementById('card').className = 'status-card' + (notFound ? ' not-found' : patched ? ' patched' : '');
316
+ document.getElementById('dot').className = 'dot' + (notFound ? ' not-found' : patched ? ' patched' : '');
317
+ document.getElementById('dot').textContent = notFound ? '✕' : patched ? '✓' : '○';
318
+
319
+ const lbl = document.getElementById('lbl');
320
+ lbl.className = 'status-label ' + (notFound ? 'not-found' : patched ? 'patched' : 'pending');
321
+ lbl.textContent = notFound ? 'NOT FOUND' : patched ? 'PATCHED' : 'NOT PATCHED';
322
+
323
+ document.getElementById('desc').textContent = notFound
324
+ ? 'Antigravity not installed'
325
+ : patched
326
+ ? 'useEffect added — restart Antigravity!'
327
+ : 'Patch not applied yet';
328
+
329
+ const pathBox = document.getElementById('pathBox');
330
+ if (basePath) {
331
+ pathBox.textContent = '📍 ' + basePath;
332
+ pathBox.style.display = '';
333
+ } else {
334
+ pathBox.style.display = 'none';
335
+ }
336
+
337
+ const filesList = document.getElementById('filesList');
338
+ filesList.innerHTML = '';
339
+ if (files && files.length) {
340
+ for (const f of files) {
341
+ const d = document.createElement('div');
342
+ d.className = 'file-item';
343
+ d.innerHTML = '<div class="file-dot ' + (f.patched ? 'patched' : 'pending') + '"></div>'
344
+ + '<span>' + f.label + ': ' + (f.patched ? '✅ patched' : '⬜ not patched') + '</span>';
345
+ filesList.appendChild(d);
346
+ }
347
+ }
348
+
349
+ document.getElementById('btnApply').style.display = (notFound || patched) ? 'none' : '';
350
+ document.getElementById('btnRevert').style.display = patched ? '' : 'none';
351
+
352
+ document.getElementById('noteBox').textContent = notFound
353
+ ? '⚠️ Install Antigravity first'
354
+ : patched
355
+ ? '💡 Re-run after Antigravity updates'
356
+ : '💡 Apply patch once, then restart Antigravity';
357
+ });
358
+ </script>
359
+ </body>
360
+ </html>`;
361
+ }
362
+ }
363
+
364
+ // ─── Status Bar ──────────────────────────────────────────────────────────
365
+
366
+ function updateStatusBarFromCache() {
367
+ const status = _cachedStatus;
368
+ if (!status || !status.basePath) {
369
+ statusBarItem.text = `$(warning) AG Patch: Not Found`;
370
+ statusBarItem.tooltip = 'Antigravity not detected';
371
+ statusBarItem.backgroundColor = undefined;
372
+ } else if (status.patched) {
373
+ statusBarItem.text = `$(check) AG Patch: Active`;
374
+ statusBarItem.tooltip = 'Auto-Accept patch is applied — click to manage';
375
+ statusBarItem.backgroundColor = new vscode.ThemeColor('statusBarItem.warningBackground');
376
+ } else {
377
+ statusBarItem.text = `$(zap) AG Patch: OFF`;
378
+ statusBarItem.tooltip = 'Auto-Accept patch not applied — click to open panel';
379
+ statusBarItem.backgroundColor = undefined;
380
+ }
381
+ }
382
+
383
+ /** @deprecated kept for backward compat */
384
+ function updateStatusBar() { updateStatusBarFromCache(); }
385
+
386
+ // ─── Activate / Deactivate ──────────────────────────────────────────────
387
+
388
+ /** @param {vscode.ExtensionContext} context */
389
+ function activate(context) {
390
+ // Status bar — shows spinner until first async check completes
391
+ statusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right, 100);
392
+ statusBarItem.command = 'antigravityAutoAccept.openPanel';
393
+ statusBarItem.text = `$(sync~spin) AG Patch`;
394
+ statusBarItem.tooltip = 'Checking patch status...';
395
+ statusBarItem.show();
396
+
397
+ // Sidebar
398
+ panelProvider = new AntigravityPanelProvider(context);
399
+ context.subscriptions.push(
400
+ vscode.window.registerWebviewViewProvider(
401
+ 'antigravityAutoAccept.panel',
402
+ panelProvider,
403
+ { webviewOptions: { retainContextWhenHidden: true } },
404
+ ),
405
+ );
406
+
407
+ // Commands
408
+ context.subscriptions.push(
409
+ vscode.commands.registerCommand('antigravityAutoAccept.applyPatch', async () => {
410
+ const result = await applyPatch();
411
+ vscode.window.showInformationMessage(result.message);
412
+ }),
413
+ vscode.commands.registerCommand('antigravityAutoAccept.revertPatch', async () => {
414
+ const result = await revertPatch();
415
+ vscode.window.showInformationMessage(result.message);
416
+ }),
417
+ vscode.commands.registerCommand('antigravityAutoAccept.openPanel', () => {
418
+ vscode.commands.executeCommand('antigravityAutoAccept.panel.focus');
419
+ }),
420
+ vscode.commands.registerCommand('antigravityAutoAccept.checkStatus', async () => {
421
+ const status = await getPatchStatus();
422
+ if (!status.basePath) {
423
+ vscode.window.showWarningMessage('Antigravity not found!');
424
+ } else {
425
+ vscode.window.showInformationMessage(
426
+ `Patch status: ${status.patched ? '✅ Applied' : '⬜ Not applied'} | ${status.basePath}`,
427
+ );
428
+ }
429
+ }),
430
+ );
431
+
432
+ context.subscriptions.push(statusBarItem);
433
+
434
+ // Async startup — never blocks extension host!
435
+ (async () => {
436
+ const status = await getPatchStatus();
437
+ isPatchApplied = status.patched;
438
+ updateStatusBarFromCache();
439
+ if (panelProvider) panelProvider.sendStatus(status);
440
+
441
+ const cfg = vscode.workspace.getConfiguration('antigravityAutoAccept');
442
+ if (cfg.get('applyOnStartup') && !status.patched && status.basePath) {
443
+ const result = await applyPatch();
444
+ if (result.success) {
445
+ console.log('[AutoAccept] Auto-patch applied on startup');
446
+ }
447
+ }
448
+ })();
449
+ }
450
+
451
+ function deactivate() { /* nothing to clean up */ }
452
+
453
+ module.exports = { activate, deactivate };
@@ -0,0 +1,3 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
2
+ <polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/>
3
+ </svg>
package/icon.png ADDED
Binary file
package/install.ps1 ADDED
@@ -0,0 +1,48 @@
1
+ # Antigravity Auto-Accept - Installation Script
2
+ # Creates a symlink in VS Code extensions directory for development
3
+
4
+ $ExtensionName = "antigravity-auto-accept"
5
+ $SourceDir = $PSScriptRoot
6
+ $VSCodeExtDir = Join-Path $env:USERPROFILE ".vscode\extensions\$ExtensionName"
7
+
8
+ Write-Host ""
9
+ Write-Host "========================================" -ForegroundColor Cyan
10
+ Write-Host " Antigravity Auto-Accept Installer" -ForegroundColor Cyan
11
+ Write-Host "========================================" -ForegroundColor Cyan
12
+ Write-Host ""
13
+
14
+ # Check if already installed
15
+ if (Test-Path $VSCodeExtDir) {
16
+ Write-Host "[!] Extension already installed at: $VSCodeExtDir" -ForegroundColor Yellow
17
+ $confirm = Read-Host "Remove and reinstall? (y/n)"
18
+ if ($confirm -ne 'y') {
19
+ Write-Host "Cancelled." -ForegroundColor Red
20
+ exit 1
21
+ }
22
+ # Remove existing (handle both symlink and directory)
23
+ if ((Get-Item $VSCodeExtDir).Attributes -band [IO.FileAttributes]::ReparsePoint) {
24
+ (Get-Item $VSCodeExtDir).Delete()
25
+ } else {
26
+ Remove-Item $VSCodeExtDir -Recurse -Force
27
+ }
28
+ Write-Host "[OK] Removed existing installation" -ForegroundColor Green
29
+ }
30
+
31
+ # Create symlink (requires admin or developer mode)
32
+ try {
33
+ New-Item -ItemType SymbolicLink -Path $VSCodeExtDir -Target $SourceDir -ErrorAction Stop | Out-Null
34
+ Write-Host "[OK] Created symlink:" -ForegroundColor Green
35
+ Write-Host " $VSCodeExtDir -> $SourceDir" -ForegroundColor DarkGray
36
+ } catch {
37
+ Write-Host "[!] Symlink failed (may need admin or Developer Mode)." -ForegroundColor Yellow
38
+ Write-Host " Falling back to file copy..." -ForegroundColor Yellow
39
+
40
+ New-Item -ItemType Directory -Path $VSCodeExtDir -Force | Out-Null
41
+ Copy-Item -Path "$SourceDir\*" -Destination $VSCodeExtDir -Recurse -Force
42
+ Write-Host "[OK] Copied files to: $VSCodeExtDir" -ForegroundColor Green
43
+ }
44
+
45
+ Write-Host ""
46
+ Write-Host "[NEXT] Reload VS Code:" -ForegroundColor Cyan
47
+ Write-Host " Ctrl+Shift+P -> 'Developer: Reload Window'" -ForegroundColor White
48
+ Write-Host ""
package/package.json ADDED
@@ -0,0 +1,71 @@
1
+ {
2
+ "name": "antigravity-autopilot",
3
+ "displayName": "Antigravity AutoPilot",
4
+ "description": "Enables autopilot mode for Antigravity: automatically executes all tool calls and terminal commands without manual confirmation. Patches the runtime JS bundle to inject auto-accept logic whenever the 'Always Proceed' policy is active — regex-based and version-agnostic.",
5
+ "version": "1.0.0",
6
+ "publisher": "nguyen-hoang",
7
+ "engines": {
8
+ "vscode": "^1.85.0"
9
+ },
10
+ "categories": ["Other"],
11
+ "activationEvents": ["onStartupFinished"],
12
+ "main": "./extension.js",
13
+ "icon": "icon.png",
14
+ "contributes": {
15
+ "viewsContainers": {
16
+ "activitybar": [
17
+ {
18
+ "id": "antigravity-auto-accept",
19
+ "title": "Antigravity Auto-Accept",
20
+ "icon": "icon-sidebar.svg"
21
+ }
22
+ ]
23
+ },
24
+ "views": {
25
+ "antigravity-auto-accept": [
26
+ {
27
+ "type": "webview",
28
+ "id": "antigravityAutoAccept.panel",
29
+ "name": "Auto-Accept Patcher"
30
+ }
31
+ ]
32
+ },
33
+ "commands": [
34
+ {
35
+ "command": "antigravityAutoAccept.applyPatch",
36
+ "title": "Antigravity: Apply Auto-Accept Patch",
37
+ "icon": "$(zap)"
38
+ },
39
+ {
40
+ "command": "antigravityAutoAccept.revertPatch",
41
+ "title": "Antigravity: Revert Auto-Accept Patch",
42
+ "icon": "$(discard)"
43
+ },
44
+ {
45
+ "command": "antigravityAutoAccept.checkStatus",
46
+ "title": "Antigravity: Check Patch Status",
47
+ "icon": "$(info)"
48
+ },
49
+ {
50
+ "command": "antigravityAutoAccept.openPanel",
51
+ "title": "Antigravity: Open Patcher Panel"
52
+ }
53
+ ],
54
+ "keybindings": [
55
+ {
56
+ "command": "antigravityAutoAccept.applyPatch",
57
+ "key": "ctrl+shift+f12"
58
+ }
59
+ ],
60
+ "configuration": {
61
+ "title": "Antigravity Auto-Accept",
62
+ "properties": {
63
+ "antigravityAutoAccept.applyOnStartup": {
64
+ "type": "boolean",
65
+ "default": false,
66
+ "description": "Automatically apply the patch when this extension activates (on startup). Safe to enable — patch is idempotent."
67
+ }
68
+ }
69
+ }
70
+ }
71
+ }
package/patcher.js ADDED
@@ -0,0 +1,265 @@
1
+ // patcher.js — runs in child_process.fork(), handles all heavy file I/O
2
+ // Ported directly from https://github.com/Kanezal/better-antigravity/blob/main/fixes/auto-run-fix/patch.js
3
+ 'use strict';
4
+
5
+ const fs = require('fs');
6
+ const path = require('path');
7
+ const os = require('os');
8
+
9
+ // ─── Installation Detection ──────────────────────────────────────────────────
10
+
11
+ function findAntigravityPath() {
12
+ const candidates = [];
13
+ if (process.platform === 'win32') {
14
+ candidates.push(
15
+ path.join(process.env.LOCALAPPDATA || '', 'Programs', 'Antigravity'),
16
+ path.join(process.env.PROGRAMFILES || '', 'Antigravity'),
17
+ path.join(process.env['PROGRAMFILES(X86)'] || '', 'Antigravity'),
18
+ );
19
+ } else if (process.platform === 'darwin') {
20
+ candidates.push(
21
+ '/Applications/Antigravity.app/Contents/Resources',
22
+ path.join(os.homedir(), 'Applications', 'Antigravity.app', 'Contents', 'Resources'),
23
+ );
24
+ } else {
25
+ candidates.push(
26
+ '/usr/share/antigravity',
27
+ '/opt/antigravity',
28
+ path.join(os.homedir(), '.local', 'share', 'antigravity'),
29
+ );
30
+ }
31
+ for (const c of candidates) {
32
+ const f = path.join(c, 'resources', 'app', 'out', 'vs', 'workbench', 'workbench.desktop.main.js');
33
+ if (fs.existsSync(f)) return c;
34
+ }
35
+ return null;
36
+ }
37
+
38
+ function getTargetFiles(basePath) {
39
+ return [
40
+ {
41
+ filePath: path.join(basePath, 'resources', 'app', 'out', 'vs', 'workbench', 'workbench.desktop.main.js'),
42
+ label: 'workbench',
43
+ },
44
+ {
45
+ filePath: path.join(basePath, 'resources', 'app', 'out', 'jetskiAgent', 'main.js'),
46
+ label: 'jetskiAgent',
47
+ },
48
+ ];
49
+ }
50
+
51
+ // ─── Smart Pattern Matching (ported from better-antigravity) ─────────────────
52
+
53
+ /**
54
+ * Finds the onChange handler and extracts variable names, regardless of minification.
55
+ * Port of: https://github.com/Kanezal/better-antigravity/blob/main/fixes/auto-run-fix/patch.js
56
+ */
57
+ function analyzeFile(content, label) {
58
+ const log = (msg) => process.send({ type: 'log', msg: `[AutoAccept] [${label}] ${msg}` });
59
+
60
+ // 1. Find the onChange handler: contains setTerminalAutoExecutionPolicy AND .EAGER
61
+ // Pattern: VARNAME=CALLBACK(ARG=>{...setTerminalAutoExecutionPolicy...,ARG===ENUM.EAGER&&CONFIRM(!0)},[...])
62
+ // Exact regex from https://github.com/Kanezal/better-antigravity/blob/main/fixes/auto-run-fix/patch.js
63
+ const onChangeRe = /(\w+)=(\w+)\((\w+)=>\{\w+\?\.setTerminalAutoExecutionPolicy\?\.\(\3\),\3===(\w+)\.EAGER&&(\w+)\(!0\)\},\[[\w,]*\]\)/;
64
+ const onChangeMatch = content.match(onChangeRe);
65
+
66
+ if (!onChangeMatch) {
67
+ log('❌ Could not find onChange handler pattern');
68
+ const idx = content.indexOf('setTerminalAutoExecutionPolicy');
69
+ if (idx >= 0) {
70
+ log(` Context: ...${content.slice(Math.max(0, idx - 80), idx + 120)}...`);
71
+ }
72
+ return null;
73
+ }
74
+
75
+ const [fullMatch, assignVar, callbackAlias, argName, enumAlias, confirmFn] = onChangeMatch;
76
+ const matchIndex = content.indexOf(fullMatch);
77
+
78
+ log(`✓ Found onChange at offset ${matchIndex}`);
79
+ log(` callback=${callbackAlias}, enum=${enumAlias}, confirm=${confirmFn}`);
80
+
81
+ // 2. Find policy variable: VARNAME=HANDLER?.terminalAutoExecutionPolicy??ENUM.OFF
82
+ // NOTE: must use ?\. (optional chaining) — this was the bug in previous version
83
+ const policyRe = new RegExp(`(\\w+)=\\w+\\?\\.terminalAutoExecutionPolicy\\?\\?${enumAlias}\\.OFF`);
84
+ const policyMatch = content.substring(Math.max(0, matchIndex - 2000), matchIndex).match(policyRe);
85
+
86
+ if (!policyMatch) {
87
+ log('❌ Could not find policy variable');
88
+ return null;
89
+ }
90
+ const policyVar = policyMatch[1];
91
+ log(` policyVar=${policyVar}`);
92
+
93
+ // 3. Find secureMode variable: VARNAME=HANDLER?.secureModeEnabled??!1
94
+ const secureRe = /(\w+)=\w+\?\.secureModeEnabled\?\?!1/;
95
+ const secureMatch = content.substring(Math.max(0, matchIndex - 2000), matchIndex).match(secureRe);
96
+
97
+ if (!secureMatch) {
98
+ log('❌ Could not find secureMode variable');
99
+ return null;
100
+ }
101
+ const secureVar = secureMatch[1];
102
+ log(` secureVar=${secureVar}`);
103
+
104
+ // 4. Find useEffect alias: look for ALIAS(()=>{...},[...]) calls nearby (not useCallback/useMemo)
105
+ const nearbyCode = content.substring(Math.max(0, matchIndex - 5000), matchIndex + 5000);
106
+ const effectCandidates = {};
107
+ const effectRe = /\b(\w{2,3})\(\(\)=>\{[^}]{3,80}\},\[/g;
108
+ let m;
109
+ while ((m = effectRe.exec(nearbyCode)) !== null) {
110
+ const alias = m[1];
111
+ if (alias !== callbackAlias && alias !== 'var' && alias !== 'new') {
112
+ effectCandidates[alias] = (effectCandidates[alias] || 0) + 1;
113
+ }
114
+ }
115
+
116
+ // Also check broader file for common useEffect patterns (with cleanup return)
117
+ const cleanupRe = /\b(\w{2,3})\(\(\)=>\{[^}]*return\s*\(\)=>/g;
118
+ while ((m = cleanupRe.exec(content)) !== null) {
119
+ const alias = m[1];
120
+ if (alias !== callbackAlias) {
121
+ effectCandidates[alias] = (effectCandidates[alias] || 0) + 5; // higher weight
122
+ }
123
+ }
124
+
125
+ // Pick the most common candidate
126
+ let useEffectAlias = null;
127
+ let maxCount = 0;
128
+ for (const [alias, count] of Object.entries(effectCandidates)) {
129
+ if (count > maxCount) {
130
+ maxCount = count;
131
+ useEffectAlias = alias;
132
+ }
133
+ }
134
+
135
+ if (!useEffectAlias) {
136
+ log('❌ Could not determine useEffect alias');
137
+ return null;
138
+ }
139
+ log(` useEffect=${useEffectAlias} (confidence: ${maxCount} hits)`);
140
+
141
+ // 5. Build patch — exact same logic as original
142
+ const patchCode = `_aep=${useEffectAlias}(()=>{${policyVar}===${enumAlias}.EAGER&&!${secureVar}&&${confirmFn}(!0)},[]),`;
143
+
144
+ return {
145
+ target: fullMatch,
146
+ replacement: patchCode + fullMatch,
147
+ patchMarker: `_aep=${useEffectAlias}(()=>{${policyVar}===${enumAlias}.EAGER`,
148
+ label
149
+ };
150
+ }
151
+
152
+ // ─── File Operations ─────────────────────────────────────────────────────────
153
+
154
+ function isFilePatched(filePath) {
155
+ if (!fs.existsSync(filePath)) return false;
156
+ try {
157
+ const content = fs.readFileSync(filePath, 'utf8');
158
+ return content.includes('_aep=') && /_aep=\w+\(\(\)=>\{[^}]+EAGER/.test(content);
159
+ } catch {
160
+ return false;
161
+ }
162
+ }
163
+
164
+ function patchFile(filePath, label) {
165
+ if (!fs.existsSync(filePath)) {
166
+ process.send({ type: 'log', msg: `[AutoAccept] ⏭️ [${label}] File not found, skipping` });
167
+ return true; // optional file missing is not a failure
168
+ }
169
+
170
+ let content;
171
+ try {
172
+ content = fs.readFileSync(filePath, 'utf8');
173
+ } catch (e) {
174
+ process.send({ type: 'log', msg: `[AutoAccept] ❌ [${label}] Read error: ${e.message}` });
175
+ return false;
176
+ }
177
+
178
+ if (isFilePatched(filePath)) {
179
+ process.send({ type: 'log', msg: `[AutoAccept] ⏭️ [${label}] Already patched` });
180
+ return true;
181
+ }
182
+
183
+ const analysis = analyzeFile(content, label);
184
+ if (!analysis) return false;
185
+
186
+ // Verify target uniqueness
187
+ const count = content.split(analysis.target).length - 1;
188
+ if (count !== 1) {
189
+ process.send({ type: 'log', msg: `[AutoAccept] ❌ [${label}] Target found ${count}x (expected 1)` });
190
+ return false;
191
+ }
192
+
193
+ // Backup original
194
+ const bakPath = filePath + '.bak';
195
+ if (!fs.existsSync(bakPath)) {
196
+ fs.copyFileSync(filePath, bakPath);
197
+ process.send({ type: 'log', msg: `[AutoAccept] 📦 [${label}] Backup created` });
198
+ }
199
+
200
+ const patched = content.replace(analysis.target, analysis.replacement);
201
+ fs.writeFileSync(filePath, patched, 'utf8');
202
+
203
+ const sizeDiff = fs.statSync(filePath).size - fs.statSync(bakPath).size;
204
+ process.send({ type: 'log', msg: `[AutoAccept] ✅ [${label}] Patched (+${sizeDiff} bytes)` });
205
+ return true;
206
+ }
207
+
208
+ function revertFile(filePath, label) {
209
+ const bak = filePath + '.bak';
210
+ if (!fs.existsSync(bak)) {
211
+ process.send({ type: 'log', msg: `[AutoAccept] ⏭️ [${label}] No backup, skipping` });
212
+ return;
213
+ }
214
+ fs.copyFileSync(bak, filePath);
215
+ process.send({ type: 'log', msg: `[AutoAccept] ✅ [${label}] Reverted` });
216
+ }
217
+
218
+ // ─── Message Handler ──────────────────────────────────────────────────────────
219
+
220
+ process.on('message', (msg) => {
221
+ const basePath = findAntigravityPath();
222
+
223
+ if (msg.command === 'status') {
224
+ if (!basePath) {
225
+ process.send({ type: 'status', basePath: null, files: [] });
226
+ process.exit(0);
227
+ return;
228
+ }
229
+ const files = getTargetFiles(basePath).map(f => ({
230
+ label: f.label,
231
+ patched: isFilePatched(f.filePath),
232
+ exists: fs.existsSync(f.filePath),
233
+ }));
234
+ process.send({ type: 'status', basePath, files });
235
+ process.exit(0);
236
+
237
+ } else if (msg.command === 'apply') {
238
+ if (!basePath) {
239
+ process.send({ type: 'result', success: false, message: '❌ Antigravity không tìm thấy! Hãy đảm bảo đã cài đặt.' });
240
+ process.exit(1);
241
+ return;
242
+ }
243
+ const targets = getTargetFiles(basePath);
244
+ const results = targets.map(f => patchFile(f.filePath, f.label));
245
+ const success = results.every(Boolean);
246
+ process.send({
247
+ type: 'result',
248
+ success,
249
+ message: success
250
+ ? '✅ Patch thành công! Restart Antigravity để áp dụng.'
251
+ : '⚠️ Một số file không patch được. Xem Output > AutoAccept để biết chi tiết.',
252
+ });
253
+ process.exit(success ? 0 : 1);
254
+
255
+ } else if (msg.command === 'revert') {
256
+ if (!basePath) {
257
+ process.send({ type: 'result', success: false, message: '❌ Antigravity không tìm thấy!' });
258
+ process.exit(1);
259
+ return;
260
+ }
261
+ getTargetFiles(basePath).forEach(f => revertFile(f.filePath, f.label));
262
+ process.send({ type: 'result', success: true, message: '✅ Đã hoàn tác! Restart Antigravity để áp dụng.' });
263
+ process.exit(0);
264
+ }
265
+ });