antigravity-autopilot 1.2.0 → 1.4.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Nguyen Hoang (nguyenhx2 or Brian)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,9 +1,14 @@
1
1
  # Antigravity AutoPilot
2
2
 
3
+ <p align="center">
4
+ <img src="icon.png" width="128" alt="Antigravity AutoPilot Logo">
5
+ </p>
6
+
3
7
  > Automatically execute all tool calls and terminal commands in Antigravity — no manual confirmation needed.
4
8
 
5
9
  [![npm](https://img.shields.io/npm/v/antigravity-autopilot)](https://www.npmjs.com/package/antigravity-autopilot)
6
10
  [![GitHub](https://img.shields.io/badge/GitHub-Antigravity--AutoPilot-blue)](https://github.com/nguyenhx2/Antigravity-AutoPilot)
11
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
12
 
8
13
  ---
9
14
 
@@ -17,6 +22,9 @@ Antigravity has an **"Always Proceed"** terminal execution policy, but due to a
17
22
  - ✅ Non-destructive — creates `.bak` backup before patching
18
23
  - ✅ Reversible — restore originals anytime with `--revert`
19
24
  - ✅ Available as VS Code Extension **and** CLI (`npx`)
25
+ - 🛡️ 54+ built-in dangerous command presets (Linux/macOS/Windows)
26
+ - 🔘 On/Off toggle for Command Blocking directly from sidebar
27
+ - ⚙️ Fully customizable preset management with Reset Defaults
20
28
 
21
29
  ---
22
30
 
@@ -49,7 +57,7 @@ Install the extension directly into Antigravity for a UI-based experience (sideb
49
57
 
50
58
  ```bash
51
59
  # Download .vsix from GitHub Releases, then:
52
- antigravity --install-extension antigravity-autopilot-1.0.0.vsix
60
+ antigravity --install-extension antigravity-autopilot-1.4.0.vsix
53
61
  ```
54
62
 
55
63
  **Extension features:**
@@ -57,6 +65,49 @@ antigravity --install-extension antigravity-autopilot-1.0.0.vsix
57
65
  - 📊 Status bar showing current patch state
58
66
  - ⌨️ Keyboard shortcut: `Ctrl+Shift+F12`
59
67
  - ⚙️ `applyOnStartup` setting for fully automatic operation
68
+ - 🔘 `enabledOnStartup` — toggle AutoPilot active/suspended on launch
69
+ - 🛡️ **Command Blocking On/Off** — toggle dangerous command blocking directly from the sidebar UI
70
+ - 📋 **Preset Management** — view, remove, and reset 54+ built-in dangerous command presets
71
+
72
+ ---
73
+
74
+ ## 🛡️ Dangerous Command Blocking
75
+
76
+ Built-in protection against destructive commands. **54+ preset patterns** covering all major platforms:
77
+
78
+ | Platform | Examples |
79
+ |----------|----------|
80
+ | **Linux/macOS** | `rm -rf /`, `dd of=/dev/sda`, `mkfs`, fork bombs, `curl \| sh`, `chmod 777 -R /` |
81
+ | **macOS** | `diskutil eraseDisk`, `csrutil disable` |
82
+ | **Windows** | `format C:`, `Remove-Item -Recurse C:\`, `bcdedit /deletevalue`, `IEX download-and-exec` |
83
+
84
+ ### Sidebar Controls
85
+
86
+ - 🔘 **On/Off Toggle** — enable or disable command blocking with a single switch
87
+ - 📋 **View all presets** — full list of blocked commands with OS badges (LNX/MAC/WIN)
88
+ - ✕ **Remove individual presets** — click the ✕ button to exclude a preset
89
+ - 🔄 **Reset Defaults** — restore all removed presets with one click
90
+ - 📊 **Active count** — always see how many presets are active
91
+ - 🔅 **Visual feedback** — presets section dims when blocking is disabled
92
+
93
+ ### Custom Patterns
94
+
95
+ Add your own patterns via Settings:
96
+
97
+ ```json
98
+ "antigravityAutoAccept.dangerousCommandBlocking.customPatterns": [
99
+ "^my-dangerous-script",
100
+ "DROP TABLE"
101
+ ]
102
+ ```
103
+
104
+ ### Action Modes
105
+
106
+ | Mode | Behavior |
107
+ |------|----------|
108
+ | `block` | Block command + show error notification (default) |
109
+ | `warn` | Show warning but allow command to proceed |
110
+ | `log` | Silently log to Output channel |
60
111
 
61
112
  ---
62
113
 
@@ -88,4 +139,4 @@ Variable names are resolved via regex at runtime, making the patch resilient to
88
139
 
89
140
  ## License
90
141
 
91
- MIT
142
+ [MIT](LICENSE) — Copyright (c) 2026 Nguyen Hoang (nguyenhx2 or Brian)
package/cli.js CHANGED
@@ -1,16 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  /**
4
- * Antigravity AutoPilot — CLI
5
- * ============================
6
- * Patches Antigravity's runtime JS bundle so that the
7
- * "Always Proceed" terminal execution policy actually
8
- * auto-executes commands without manual confirmation.
9
- *
10
- * Usage:
4
+ * Antigravity AutoPilot — CLI v1.3.0
5
+ * ====================================
11
6
  * npx antigravity-autopilot Apply patch
12
- * npx antigravity-autopilot --check Check patch status
13
- * npx antigravity-autopilot --revert Restore original files
7
+ * npx antigravity-autopilot --check Check status
8
+ * npx antigravity-autopilot --revert Restore originals
9
+ * npx antigravity-autopilot --help Show help
14
10
  */
15
11
 
16
12
  'use strict';
@@ -19,7 +15,113 @@ const fs = require('fs');
19
15
  const path = require('path');
20
16
  const os = require('os');
21
17
 
22
- // ─── Installation Detection ──────────────────────────────────────────────────
18
+ // ─── ANSI Colors (auto-disable when not a TTY) ───────────────────────────────
19
+
20
+ const isTTY = process.stdout.isTTY;
21
+ const c = {
22
+ reset: isTTY ? '\x1b[0m' : '',
23
+ bold: isTTY ? '\x1b[1m' : '',
24
+ dim: isTTY ? '\x1b[2m' : '',
25
+ yellow: isTTY ? '\x1b[33m' : '',
26
+ cyan: isTTY ? '\x1b[36m' : '',
27
+ green: isTTY ? '\x1b[32m' : '',
28
+ red: isTTY ? '\x1b[31m' : '',
29
+ blue: isTTY ? '\x1b[34m' : '',
30
+ magenta: isTTY ? '\x1b[35m' : '',
31
+ white: isTTY ? '\x1b[97m' : '',
32
+ gray: isTTY ? '\x1b[90m' : '',
33
+ bgBlue: isTTY ? '\x1b[44m' : '',
34
+ };
35
+
36
+ const W = 54; // box width (inner)
37
+
38
+ function repeat(ch, n) { return ch.repeat(Math.max(0, n)); }
39
+ function pad(str, n) {
40
+ const visible = str.replace(/\x1b\[[0-9;]*m/g, '');
41
+ return str + repeat(' ', Math.max(0, n - visible.length));
42
+ }
43
+
44
+ // ─── Banner ───────────────────────────────────────────────────────────────────
45
+
46
+ function printBanner() {
47
+ const top = `╔${repeat('═', W)}╗`;
48
+ const bot = `╚${repeat('═', W)}╝`;
49
+ const div = `╟${repeat('─', W)}╢`;
50
+ const empty = `║${repeat(' ', W)}║`;
51
+
52
+ const logoLines = [
53
+ ' ██████╗ ██╗ ██╗████████╗ ██████╗',
54
+ ' ██╔═══██╗ ██║ ██║╚══██╔══╝██╔═══██╗',
55
+ ' ███████║ ██║ ██║ ██║ ██║ ██║',
56
+ ' ██╔══██║ ██║ ██║ ██║ ██║ ██║',
57
+ ' ██║ ██║ ╚██████╔╝ ██║ ╚██████╔╝',
58
+ ' ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═════╝ ',
59
+ ];
60
+
61
+ const pkg = require('./package.json');
62
+
63
+ console.log('');
64
+ console.log(c.cyan + c.bold + top + c.reset);
65
+ console.log(c.cyan + c.bold + empty + c.reset);
66
+
67
+ for (const line of logoLines) {
68
+ const inner = pad(c.yellow + c.bold + line + c.reset, W + (c.yellow + c.bold + c.reset).length * 2);
69
+ console.log(c.cyan + c.bold + '║' + c.reset + inner + c.cyan + c.bold + '║' + c.reset);
70
+ }
71
+
72
+ // Pilot subtitle
73
+ const subtitle = '⚡ A N T I G R A V I T Y A U T O P I L O T ⚡';
74
+ const subInner = pad(c.white + c.bold + subtitle.padStart(Math.floor((W + subtitle.length) / 2)).padEnd(W) + c.reset,
75
+ W + (c.white + c.bold + c.reset).length * 2);
76
+ console.log(c.cyan + c.bold + '║' + c.reset + c.cyan + c.bold + empty.slice(1, -1) + c.reset + c.cyan + c.bold + '║' + c.reset);
77
+ console.log(c.cyan + c.bold + '║' + c.reset + subInner + c.cyan + c.bold + '║' + c.reset);
78
+
79
+ // Version line
80
+ const ver = `v${pkg.version} · MIT License · github.com/nguyenhx2/Antigravity-AutoPilot`;
81
+ const verInner = pad(c.gray + ver.padStart(Math.floor((W + ver.length) / 2)).padEnd(W) + c.reset,
82
+ W + (c.gray + c.reset).length * 2);
83
+ console.log(c.cyan + c.bold + '║' + c.reset + verInner + c.cyan + c.bold + '║' + c.reset);
84
+
85
+ console.log(c.cyan + c.bold + empty + c.reset);
86
+ console.log(c.cyan + c.bold + bot + c.reset);
87
+ console.log('');
88
+ }
89
+
90
+ // ─── Help Screen ──────────────────────────────────────────────────────────────
91
+
92
+ function printHelp() {
93
+ const cmds = [
94
+ ['(no args)', 'Apply the AutoPilot patch'],
95
+ ['--check', 'Check current patch status'],
96
+ ['--revert', 'Restore original files'],
97
+ ['--help', 'Show this help screen'],
98
+ ];
99
+ console.log(c.bold + c.white + ' USAGE' + c.reset);
100
+ console.log(c.gray + ' ─────────────────────────────────────────────' + c.reset);
101
+ console.log(' ' + c.cyan + 'npx antigravity-autopilot' + c.reset + c.gray + ' [option]' + c.reset + '\n');
102
+ console.log(c.bold + c.white + ' OPTIONS' + c.reset);
103
+ console.log(c.gray + ' ─────────────────────────────────────────────' + c.reset);
104
+ for (const [flag, desc] of cmds) {
105
+ console.log(' ' + c.yellow + c.bold + flag.padEnd(12) + c.reset + ' ' + c.white + desc + c.reset);
106
+ }
107
+ console.log('');
108
+ console.log(c.bold + c.white + ' WORKFLOW' + c.reset);
109
+ console.log(c.gray + ' ─────────────────────────────────────────────' + c.reset);
110
+ console.log(' ' + c.green + '1.' + c.reset + ' Run ' + c.cyan + 'npx antigravity-autopilot' + c.reset + ' → patch applied');
111
+ console.log(' ' + c.green + '2.' + c.reset + ' Restart Antigravity → AutoPilot active 🚀');
112
+ console.log(' ' + c.green + '3.' + c.reset + ' Run ' + c.cyan + '--revert' + c.reset + ' → undo anytime');
113
+ console.log('');
114
+ }
115
+
116
+ // ─── Section header ───────────────────────────────────────────────────────────
117
+
118
+ function section(title, icon) {
119
+ const line = `${icon} ${title}`;
120
+ console.log(c.bold + c.white + line + c.reset);
121
+ console.log(c.gray + ' ' + repeat('─', W - 2) + c.reset);
122
+ }
123
+
124
+ // ─── Installation Detection ───────────────────────────────────────────────────
23
125
 
24
126
  function findAntigravityPath() {
25
127
  const candidates = [];
@@ -62,47 +164,36 @@ function getVersion(basePath) {
62
164
  } catch { return 'unknown'; }
63
165
  }
64
166
 
65
- // ─── Core Patch Logic (regex-based, version-agnostic) ───────────────────────
167
+ // ─── Core Patch Logic ─────────────────────────────────────────────────────────
66
168
 
67
169
  function analyzeFile(content, label) {
68
- // 1. Find the onChange handler for setTerminalAutoExecutionPolicy
69
- const onChangeRe = /(\w+)=(\w+)\((\w+)=>\{\w+\?\.setTerminalAutoExecutionPolicy\?\.\(\3\),\3===(\w+)\.EAGER&&(\w+)\(!0\)\},\[[\w,]*\]\)/;
170
+ const onChangeRe = /(\w+)=(\w+)\((\w+)=>\{\w+\?\.setTerminalAutoExecutionPolicy\?\.\(\3\),\3===(\w+)\.EAGER\&\&(\w+)\(!0\)\},\[[\w,]*\]\)/;
70
171
  const onChangeMatch = content.match(onChangeRe);
71
- if (!onChangeMatch) {
72
- console.log(` ❌ [${label}] Could not find onChange handler pattern`);
73
- return null;
74
- }
172
+ if (!onChangeMatch) { return null; }
75
173
 
76
174
  const [fullMatch, , callbackAlias, , enumAlias, confirmFn] = onChangeMatch;
77
175
  const matchIndex = content.indexOf(fullMatch);
78
- console.log(` 📋 [${label}] Found onChange at offset ${matchIndex}`);
79
- console.log(` callback=${callbackAlias}, enum=${enumAlias}, confirm=${confirmFn}`);
80
176
 
81
- // 2. Find policy variable: VAR=HANDLER?.terminalAutoExecutionPolicy??ENUM.OFF
82
- const policyRe = new RegExp(`(\\w+)=\\w+\\?\\.terminalAutoExecutionPolicy\\?\\?${enumAlias}\\.OFF`);
177
+ const policyRe = new RegExp(`(\\w+)=\\w+\\.terminalAutoExecutionPolicy\\?\\?${enumAlias}\\.OFF`);
83
178
  const policyMatch = content.substring(Math.max(0, matchIndex - 2000), matchIndex).match(policyRe);
84
- if (!policyMatch) { console.log(` ❌ [${label}] Could not find policy variable`); return null; }
179
+ if (!policyMatch) return null;
85
180
  const policyVar = policyMatch[1];
86
- console.log(` policyVar=${policyVar}`);
87
181
 
88
- // 3. Find secureMode variable: VAR=HANDLER?.secureModeEnabled??!1
89
182
  const secureRe = /(\w+)=\w+\?\.secureModeEnabled\?\?!1/;
90
183
  const secureMatch = content.substring(Math.max(0, matchIndex - 2000), matchIndex).match(secureRe);
91
- if (!secureMatch) { console.log(` ❌ [${label}] Could not find secureMode variable`); return null; }
184
+ if (!secureMatch) return null;
92
185
  const secureVar = secureMatch[1];
93
- console.log(` secureVar=${secureVar}`);
94
186
 
95
- // 4. Find useEffect alias via frequency counting
96
187
  const nearbyCode = content.substring(Math.max(0, matchIndex - 5000), matchIndex + 5000);
97
188
  const effectCandidates = {};
98
- const effectRe = /\b(\w{2,3})\(\(\)=>\{[^}]{3,80}\},\[/g;
189
+ const effectRe = /\b(\w{2,3})\(()=>\{[^}]{3,80}\},\[/g;
99
190
  let m;
100
191
  while ((m = effectRe.exec(nearbyCode)) !== null) {
101
192
  const alias = m[1];
102
193
  if (alias !== callbackAlias && alias !== 'var' && alias !== 'new')
103
194
  effectCandidates[alias] = (effectCandidates[alias] || 0) + 1;
104
195
  }
105
- const cleanupRe = /\b(\w{2,3})\(\(\)=>\{[^}]*return\s*\(\)=>/g;
196
+ const cleanupRe = /\b(\w{2,3})\(()=>\{[^}]*return\s*()=>/g;
106
197
  while ((m = cleanupRe.exec(content)) !== null) {
107
198
  const alias = m[1];
108
199
  if (alias !== callbackAlias)
@@ -113,10 +204,8 @@ function analyzeFile(content, label) {
113
204
  for (const [alias, count] of Object.entries(effectCandidates)) {
114
205
  if (count > maxCount) { maxCount = count; useEffectAlias = alias; }
115
206
  }
116
- if (!useEffectAlias) { console.log(` ❌ [${label}] Could not determine useEffect alias`); return null; }
117
- console.log(` useEffect=${useEffectAlias} (confidence: ${maxCount} hits)`);
207
+ if (!useEffectAlias) return null;
118
208
 
119
- // 5. Build patch
120
209
  const patchCode = `_aep=${useEffectAlias}(()=>{${policyVar}===${enumAlias}.EAGER&&!${secureVar}&&${confirmFn}(!0)},[]),`;
121
210
  return {
122
211
  target: fullMatch,
@@ -125,107 +214,160 @@ function analyzeFile(content, label) {
125
214
  };
126
215
  }
127
216
 
128
- // ─── File Operations ─────────────────────────────────────────────────────────
217
+ // ─── File Operations ──────────────────────────────────────────────────────────
129
218
 
130
219
  function isPatched(filePath) {
131
220
  if (!fs.existsSync(filePath)) return false;
132
221
  const c = fs.readFileSync(filePath, 'utf8');
133
- return c.includes('_aep=') && /_aep=\w+\(\(\)=>\{[^}]+EAGER/.test(c);
222
+ return c.includes('_aep=') && /_aep=\w+\(()=>\{[^}]+EAGER/.test(c);
223
+ }
224
+
225
+ function row(icon, color, label, msg) {
226
+ console.log(' ' + color + icon + c.reset + ' ' + c.bold + label.padEnd(14) + c.reset + c.gray + msg + c.reset);
134
227
  }
135
228
 
136
229
  function patchFile(filePath, label) {
137
230
  if (!fs.existsSync(filePath)) {
138
- console.log(` ⏭️ [${label}] Not found, skipping`);
231
+ row('⊘', c.gray, `[${label}]`, 'File not found skipping');
139
232
  return true;
140
233
  }
141
234
  const content = fs.readFileSync(filePath, 'utf8');
142
235
  if (isPatched(filePath)) {
143
- console.log(` ⏭️ [${label}] Already patched`);
236
+ row('✔', c.green, `[${label}]`, 'Already patched');
144
237
  return true;
145
238
  }
146
239
  const analysis = analyzeFile(content, label);
147
- if (!analysis) return false;
240
+ if (!analysis) {
241
+ row('✖', c.red, `[${label}]`, 'Pattern not found — may be incompatible version');
242
+ return false;
243
+ }
148
244
 
149
245
  const count = content.split(analysis.target).length - 1;
150
- if (count !== 1) { console.log(` ❌ [${label}] Target found ${count}x (expected 1)`); return false; }
246
+ if (count !== 1) {
247
+ row('✖', c.red, `[${label}]`, `Target found ${count}× (expected 1)`);
248
+ return false;
249
+ }
151
250
 
152
251
  const bak = filePath + '.bak';
153
- if (!fs.existsSync(bak)) { fs.copyFileSync(filePath, bak); console.log(` 📦 [${label}] Backup created`); }
252
+ if (!fs.existsSync(bak)) {
253
+ fs.copyFileSync(filePath, bak);
254
+ row('◈', c.blue, `[${label}]`, 'Backup created (.bak)');
255
+ }
154
256
 
155
257
  fs.writeFileSync(filePath, content.replace(analysis.target, analysis.replacement), 'utf8');
156
258
  const diff = fs.statSync(filePath).size - fs.statSync(bak).size;
157
- console.log(`[${label}] Patched (+${diff} bytes)`);
259
+ row('✔', c.green, `[${label}]`, `Patched successfully (+${diff} bytes)`);
158
260
  return true;
159
261
  }
160
262
 
161
263
  function revertFile(filePath, label) {
162
264
  const bak = filePath + '.bak';
163
- if (!fs.existsSync(bak)) { console.log(` ⏭️ [${label}] No backup, skipping`); return; }
265
+ if (!fs.existsSync(bak)) {
266
+ row('⊘', c.gray, `[${label}]`, 'No backup found — skipping');
267
+ return;
268
+ }
164
269
  fs.copyFileSync(bak, filePath);
165
- console.log(`[${label}] Restored`);
270
+ row('✔', c.green, `[${label}]`, 'Restored from backup');
166
271
  }
167
272
 
168
273
  function checkFile(filePath, label) {
169
- if (!fs.existsSync(filePath)) { console.log(` ❌ [${label}] Not found`); return false; }
274
+ if (!fs.existsSync(filePath)) {
275
+ row('✖', c.red, `[${label}]`, 'File not found');
276
+ return false;
277
+ }
170
278
  const content = fs.readFileSync(filePath, 'utf8');
171
279
  const patched = isPatched(filePath);
172
280
  const hasBak = fs.existsSync(filePath + '.bak');
281
+ const analysis = !patched ? analyzeFile(content, label) : null;
282
+
173
283
  if (patched) {
174
- console.log(`[${label}] PATCHED` + (hasBak ? ' (backup exists)' : ''));
284
+ row('✔', c.green, `[${label}]`, 'PATCHED' + (hasBak ? ' · backup exists' : ''));
285
+ } else if (analysis) {
286
+ row('○', c.yellow, `[${label}]`, 'NOT PATCHED · patchable, ready to apply');
175
287
  } else {
176
- const analysis = analyzeFile(content, label);
177
- console.log(analysis ? ` ⬜ [${label}] NOT PATCHED (patchable)` : ` ⚠️ [${label}] NOT PATCHED (may be incompatible)`);
288
+ row('⚠', c.yellow, `[${label}]`, 'NOT PATCHED · may be incompatible');
178
289
  }
179
290
  return patched;
180
291
  }
181
292
 
182
- // ─── Main ────────────────────────────────────────────────────────────────────
293
+ // ─── Main ─────────────────────────────────────────────────────────────────────
183
294
 
184
295
  function main() {
185
296
  const args = process.argv.slice(2);
186
- const action = args.includes('--revert') ? 'revert' : args.includes('--check') ? 'check' : 'apply';
297
+ const action = args.includes('--revert') ? 'revert'
298
+ : args.includes('--check') ? 'check'
299
+ : args.includes('--help') ? 'help'
300
+ : 'apply';
187
301
 
188
- console.log('');
189
- console.log('╔══════════════════════════════════════════╗');
190
- console.log('║ ⚡ Antigravity AutoPilot ║');
191
- console.log('╚══════════════════════════════════════════╝');
302
+ printBanner();
192
303
 
304
+ if (action === 'help') { printHelp(); return; }
305
+
306
+ // ── Locate Antigravity ──
193
307
  const basePath = findAntigravityPath();
308
+
309
+ console.log(' ' + c.gray + '📍 Installation' + c.reset);
194
310
  if (!basePath) {
195
- console.log('\n❌ Antigravity not found. Make sure it is installed.\n');
311
+ console.log(' ' + c.red + c.bold + '✖ Antigravity not found.' + c.reset);
312
+ console.log(' Install Antigravity from ' + c.cyan + 'https://antigravity.dev' + c.reset + ' first.\n');
196
313
  process.exit(1);
197
314
  }
198
-
199
- console.log(`\n📍 ${basePath}`);
200
- console.log(`📦 Antigravity version: ${getVersion(basePath)}\n`);
315
+ console.log(' ' + c.white + basePath + c.reset);
316
+ console.log(' ' + c.gray + `Version: ${getVersion(basePath)}` + c.reset);
317
+ console.log('');
201
318
 
202
319
  const files = getTargetFiles(basePath);
203
320
 
321
+ // ── Action ──
204
322
  switch (action) {
205
- case 'check':
206
- console.log('🔍 Checking patch status...\n');
323
+ case 'check': {
324
+ section('Status Check', '🔍');
325
+ console.log('');
207
326
  files.forEach(f => checkFile(f.filePath, f.label));
327
+ console.log('');
328
+ const allPatched = files.every(f => isPatched(f.filePath));
329
+ if (allPatched) {
330
+ console.log(' ' + c.green + c.bold + '✔ AutoPilot is ACTIVE on this machine.' + c.reset);
331
+ console.log(' ' + c.gray + 'Restart Antigravity for changes to take effect.' + c.reset);
332
+ } else {
333
+ console.log(' ' + c.yellow + ' Run ' + c.cyan + 'npx antigravity-autopilot' + c.yellow + ' to activate AutoPilot.' + c.reset);
334
+ }
335
+ console.log('');
208
336
  break;
337
+ }
209
338
 
210
- case 'revert':
211
- console.log('↩️ Reverting patch...\n');
339
+ case 'revert': {
340
+ section('Reverting Patch', '↩');
341
+ console.log('');
212
342
  files.forEach(f => revertFile(f.filePath, f.label));
213
- console.log('\n✨ Restored! Restart Antigravity to apply changes.');
343
+ console.log('');
344
+ console.log(' ' + c.green + c.bold + '✔ Restored!' + c.reset + c.white + ' Restart Antigravity to apply changes.' + c.reset);
345
+ console.log('');
214
346
  break;
347
+ }
215
348
 
216
349
  case 'apply':
217
- default:
218
- console.log('🚀 Applying autopilot patch...\n');
350
+ default: {
351
+ section('Applying AutoPilot Patch', '⚡');
352
+ console.log('');
219
353
  const ok = files.every(f => patchFile(f.filePath, f.label));
354
+ console.log('');
220
355
  if (ok) {
221
- console.log('\n✨ Done! Restart Antigravity to activate AutoPilot.');
222
- console.log('💡 Run with --revert to undo.');
223
- console.log('⚠️ Re-run after Antigravity updates.\n');
356
+ console.log(' ┌' + repeat('─', W - 2) + '┐');
357
+ console.log(' │' + pad(c.green + c.bold + ' ✔ Patch applied successfully!' + c.reset, W - 2 + (c.green + c.bold + c.reset).length * 2) + '│');
358
+ console.log(' │' + pad(c.white + ' Restart Antigravity to activate AutoPilot.' + c.reset, W - 2 + (c.white + c.reset).length * 2) + '│');
359
+ console.log(' │' + repeat(' ', W - 2) + '│');
360
+ console.log(' │' + pad(c.gray + ' 💡 Run with --revert to undo at any time.' + c.reset, W - 2 + (c.gray + c.reset).length * 2) + '│');
361
+ console.log(' │' + pad(c.gray + ' ⚠ Re-run this command after Antigravity updates.' + c.reset, W - 2 + (c.gray + c.reset).length * 2) + '│');
362
+ console.log(' └' + repeat('─', W - 2) + '┘');
224
363
  } else {
225
- console.log('\n⚠️ Some files could not be patched. Check output above.\n');
364
+ console.log(' ' + c.red + c.bold + '✖ Some files could not be patched.' + c.reset);
365
+ console.log(' ' + c.gray + 'Check output above for details.' + c.reset);
226
366
  process.exit(1);
227
367
  }
368
+ console.log('');
228
369
  break;
370
+ }
229
371
  }
230
372
  }
231
373
 
package/extension.js CHANGED
@@ -20,75 +20,94 @@ let _cachedStatus = null;
20
20
  let autoPilotEnabled = true;
21
21
  /** @type {vscode.OutputChannel} */
22
22
  let outputChannel;
23
+ /** @type {vscode.ExtensionContext} */
24
+ let _ctx;
23
25
 
24
26
  // ─── Dangerous Command Blocking ───────────────────────────────────────────────
25
27
 
26
28
  /**
27
29
  * Built-in dangerous command patterns.
28
30
  * Covers Linux/macOS/Windows destructive commands.
29
- * Each entry: { pattern: RegExp, label: string, os: string[] }
31
+ * Each entry: { id: string, pattern: RegExp, label: string, os: string[] }
30
32
  */
31
33
  const BUILTIN_DANGEROUS_PATTERNS = [
32
34
  // ── Linux / macOS ──
33
- { pattern: /rm\s+(-[a-zA-Z]*f[a-zA-Z]*\s+|--force\s+).*\/(\s|$)/, label: 'rm -rf /', os: ['linux', 'darwin'] },
34
- { pattern: /rm\s+-[a-zA-Z]*r[a-zA-Z]*\s+\/(\s|$)/, label: 'rm -r / (root wipe)', os: ['linux', 'darwin'] },
35
- { pattern: /rm\s+-[a-zA-Z]*r[a-zA-Z]*\s+~(\s|$|\/)/, label: 'rm -r ~ (home wipe)', os: ['linux', 'darwin'] },
36
- { pattern: /rm\s+.*--no-preserve-root/, label: 'rm --no-preserve-root', os: ['linux', 'darwin'] },
37
- { pattern: /:\(\)\s*\{.*:\|:&\s*\};\s*:/, label: 'Fork bomb :(){:|:&};:', os: ['linux', 'darwin'] },
38
- { pattern: /mkfs\.(ext[234]|xfs|btrfs|vfat|ntfs)\s+\/dev\/(sd|hd|nvme|vd)/, label: 'mkfs on block device', os: ['linux', 'darwin'] },
39
- { pattern: /dd\s+.*of=\/dev\/(sd[a-z]|hd[a-z]|nvme\d+|zero|null)/, label: 'dd overwrite device', os: ['linux', 'darwin'] },
40
- { pattern: />\s*\/dev\/(sd[a-z]|hd[a-z]|nvme\d+)/, label: 'Redirect to block device', os: ['linux', 'darwin'] },
41
- { pattern: /shred\s+(-[a-zA-Z]*n\s*\d+\s+)?\/dev\//, label: 'shred device', os: ['linux', 'darwin'] },
42
- { pattern: /mv\s+.*\s+\/dev\/null/, label: 'mv to /dev/null', os: ['linux', 'darwin'] },
43
- { pattern: /chmod\s+-[rR]\s+000\s+\//, label: 'chmod 000 recursive on /', os: ['linux', 'darwin'] },
44
- { pattern: /chmod\s+777\s+-R\s+\/(\s|$)/, label: 'chmod 777 -R /', os: ['linux', 'darwin'] },
45
- { pattern: /chown\s+.*-R\s+.*\s+\/(\s|$)/, label: 'chown -R on /', os: ['linux', 'darwin'] },
46
- { pattern: /passwd\s+root\s*$/, label: 'passwd root (no new password)', os: ['linux', 'darwin'] },
47
- { pattern: /sudo\s+rm\s+-[a-zA-Z]*rf?\s+\/(\s|$)/, label: 'sudo rm -rf /', os: ['linux', 'darwin'] },
48
- { pattern: /wget\s+.*\|\s*(ba)?sh/, label: 'wget pipe to shell', os: ['linux', 'darwin'] },
49
- { pattern: /curl\s+.*\|\s*(ba)?sh/, label: 'curl pipe to shell', os: ['linux', 'darwin'] },
50
- { pattern: /base64\s+-d.*\|\s*(ba)?sh/, label: 'base64 decode pipe to shell', os: ['linux', 'darwin'] },
51
- { pattern: /eval\s+\$\(.*\)/, label: 'eval $(...) subshell', os: ['linux', 'darwin'] },
52
- { pattern: /fdisk\s+\/dev\/(sd[a-z]|nvme\d+)/, label: 'fdisk on disk', os: ['linux', 'darwin'] },
53
- { pattern: /parted\s+\/dev\/(sd[a-z]|nvme\d+)/, label: 'parted on disk', os: ['linux', 'darwin'] },
54
- { pattern: /wipefs\s+.*\/dev\//, label: 'wipefs on device', os: ['linux', 'darwin'] },
55
- { pattern: /truncate\s+-s\s+0\s+\/dev\//, label: 'truncate device to 0', os: ['linux', 'darwin'] },
56
- { pattern: /echo\s+.*>\s*\/boot\//, label: 'overwrite /boot/', os: ['linux', 'darwin'] },
57
- { pattern: /cat\s+\/dev\/zero\s+>\s+\//, label: 'cat /dev/zero to /', os: ['linux', 'darwin'] },
58
- { pattern: /umount\s+-a/, label: 'umount -a (unmount all)', os: ['linux', 'darwin'] },
59
- { pattern: /init\s+0/, label: 'init 0 (halt system)', os: ['linux', 'darwin'] },
60
- { pattern: /poweroff|halt\s*$/, label: 'System shutdown command', os: ['linux', 'darwin'] },
61
- { pattern: /iptables\s+-F/, label: 'iptables -F (flush all rules)', os: ['linux', 'darwin'] },
62
- { pattern: /ufw\s+--force\s+reset/, label: 'ufw --force reset', os: ['linux', 'darwin'] },
35
+ { id: 'rm-rf-root', pattern: /rm\s+(-[a-zA-Z]*f[a-zA-Z]*\s+|--force\s+).*\/(\\s|$)/, label: 'rm -rf /', os: ['linux', 'darwin'] },
36
+ { id: 'rm-r-root', pattern: /rm\s+-[a-zA-Z]*r[a-zA-Z]*\s+\/(\s|$)/, label: 'rm -r / (root wipe)', os: ['linux', 'darwin'] },
37
+ { id: 'rm-r-home', pattern: /rm\s+-[a-zA-Z]*r[a-zA-Z]*\s+~(\s|$|\/)/, label: 'rm -r ~ (home wipe)', os: ['linux', 'darwin'] },
38
+ { id: 'rm-no-preserve', pattern: /rm\s+.*--no-preserve-root/, label: 'rm --no-preserve-root', os: ['linux', 'darwin'] },
39
+ { id: 'fork-bomb', pattern: /:\(\)\s*\{.*:\|:&\s*\};\s*:/, label: 'Fork bomb :(){:|:&};:', os: ['linux', 'darwin'] },
40
+ { id: 'mkfs-device', pattern: /mkfs\.(ext[234]|xfs|btrfs|vfat|ntfs)\s+\/dev\/(sd|hd|nvme|vd)/, label: 'mkfs on block device', os: ['linux', 'darwin'] },
41
+ { id: 'dd-overwrite', pattern: /dd\s+.*of=\/dev\/(sd[a-z]|hd[a-z]|nvme\d+|zero|null)/, label: 'dd overwrite device', os: ['linux', 'darwin'] },
42
+ { id: 'redirect-block', pattern: />\s*\/dev\/(sd[a-z]|hd[a-z]|nvme\d+)/, label: 'Redirect to block device', os: ['linux', 'darwin'] },
43
+ { id: 'shred-device', pattern: /shred\s+(-[a-zA-Z]*n\s*\d+\s+)?\/dev\//, label: 'shred device', os: ['linux', 'darwin'] },
44
+ { id: 'mv-devnull', pattern: /mv\s+.*\s+\/dev\/null/, label: 'mv to /dev/null', os: ['linux', 'darwin'] },
45
+ { id: 'chmod-000', pattern: /chmod\s+-[rR]\s+000\s+\//, label: 'chmod 000 recursive on /', os: ['linux', 'darwin'] },
46
+ { id: 'chmod-777', pattern: /chmod\s+777\s+-R\s+\/(\s|$)/, label: 'chmod 777 -R /', os: ['linux', 'darwin'] },
47
+ { id: 'chown-root', pattern: /chown\s+.*-R\s+.*\s+\/(\s|$)/, label: 'chown -R on /', os: ['linux', 'darwin'] },
48
+ { id: 'passwd-root', pattern: /passwd\s+root\s*$/, label: 'passwd root', os: ['linux', 'darwin'] },
49
+ { id: 'sudo-rm-rf', pattern: /sudo\s+rm\s+-[a-zA-Z]*rf?\s+\/(\s|$)/, label: 'sudo rm -rf /', os: ['linux', 'darwin'] },
50
+ { id: 'wget-pipe-sh', pattern: /wget\s+.*\|\s*(ba)?sh/, label: 'wget pipe to shell', os: ['linux', 'darwin'] },
51
+ { id: 'curl-pipe-sh', pattern: /curl\s+.*\|\s*(ba)?sh/, label: 'curl pipe to shell', os: ['linux', 'darwin'] },
52
+ { id: 'base64-pipe-sh', pattern: /base64\s+-d.*\|\s*(ba)?sh/, label: 'base64 decode pipe to shell', os: ['linux', 'darwin'] },
53
+ { id: 'eval-subshell', pattern: /eval\s+\$\(.*\)/, label: 'eval $(...) subshell', os: ['linux', 'darwin'] },
54
+ { id: 'fdisk-disk', pattern: /fdisk\s+\/dev\/(sd[a-z]|nvme\d+)/, label: 'fdisk on disk', os: ['linux', 'darwin'] },
55
+ { id: 'parted-disk', pattern: /parted\s+\/dev\/(sd[a-z]|nvme\d+)/, label: 'parted on disk', os: ['linux', 'darwin'] },
56
+ { id: 'wipefs-dev', pattern: /wipefs\s+.*\/dev\//, label: 'wipefs on device', os: ['linux', 'darwin'] },
57
+ { id: 'truncate-dev', pattern: /truncate\s+-s\s+0\s+\/dev\//, label: 'truncate device to 0', os: ['linux', 'darwin'] },
58
+ { id: 'echo-boot', pattern: /echo\s+.*>\s*\/boot\//, label: 'overwrite /boot/', os: ['linux', 'darwin'] },
59
+ { id: 'cat-devzero', pattern: /cat\s+\/dev\/zero\s+>\s+\//, label: 'cat /dev/zero to /', os: ['linux', 'darwin'] },
60
+ { id: 'umount-all', pattern: /umount\s+-a/, label: 'umount -a (unmount all)', os: ['linux', 'darwin'] },
61
+ { id: 'init-0', pattern: /init\s+0/, label: 'init 0 (halt system)', os: ['linux', 'darwin'] },
62
+ { id: 'poweroff', pattern: /poweroff|halt\s*$/, label: 'System shutdown command', os: ['linux', 'darwin'] },
63
+ { id: 'iptables-flush', pattern: /iptables\s+-F/, label: 'iptables -F (flush all rules)', os: ['linux', 'darwin'] },
64
+ { id: 'ufw-reset', pattern: /ufw\s+--force\s+reset/, label: 'ufw --force reset', os: ['linux', 'darwin'] },
63
65
  // ── macOS specific ──
64
- { pattern: /diskutil\s+(eraseDisk|eraseVolume|partitionDisk)\s+/, label: 'diskutil erase/repartition', os: ['darwin'] },
65
- { pattern: /diskutil\s+zeroDisk\s+/, label: 'diskutil zeroDisk', os: ['darwin'] },
66
- { pattern: /csrutil\s+disable/, label: 'csrutil disable (SIP)', os: ['darwin'] },
66
+ { id: 'diskutil-erase', pattern: /diskutil\s+(eraseDisk|eraseVolume|partitionDisk)\s+/, label: 'diskutil erase/repartition', os: ['darwin'] },
67
+ { id: 'diskutil-zero', pattern: /diskutil\s+zeroDisk\s+/, label: 'diskutil zeroDisk', os: ['darwin'] },
68
+ { id: 'csrutil-disable', pattern: /csrutil\s+disable/, label: 'csrutil disable (SIP)', os: ['darwin'] },
67
69
  // ── Windows (PowerShell / cmd) ──
68
- { pattern: /Format-Volume\s+.*-Confirm:\s*\$false/i, label: 'Format-Volume without confirm', os: ['win32'] },
69
- { pattern: /format\s+[cC]:\s*\/[qQy]/i, label: 'format C: /q or /y', os: ['win32'] },
70
- { pattern: /format\s+[a-zA-Z]:\s*\/[qQy]/i, label: 'format <drive> /q or /y', os: ['win32'] },
71
- { pattern: /del\s+\/[fsqSFQ]+\s+[cC]:\\/i, label: 'del /f/s/q C:\\ (wipe drive)', os: ['win32'] },
72
- { pattern: /rd\s+\/[sq]+\s+[cC]:\\/i, label: 'rd /s/q C:\\ (remove all)', os: ['win32'] },
73
- { pattern: /Remove-Item\s+.*-Recurse\s+.*-Force.*[cC]:\\/i, label: 'Remove-Item -Recurse -Force C:\\', os: ['win32'] },
74
- { pattern: /Remove-Item\s+.*-Recurse\s+.*-Force\s+\/\s/i, label: 'Remove-Item -Recurse -Force /', os: ['win32'] },
75
- { pattern: /Set-ExecutionPolicy\s+Unrestricted\s+-Force/i, label: 'Set-ExecutionPolicy Unrestricted -Force', os: ['win32'] },
76
- { pattern: /reg\s+(delete|add)\s+HKLM\\SYSTEM\\CurrentControlSet/i, label: 'reg delete HKLM\\SYSTEM critical', os: ['win32'] },
77
- { pattern: /bcdedit\s+\/deletevalue/i, label: 'bcdedit /deletevalue (boot config)', os: ['win32'] },
78
- { pattern: /bcdedit\s+\/set.*safeboot/i, label: 'bcdedit /set safeboot (forces safe mode)', os: ['win32'] },
79
- { pattern: /cipher\s+\/w:[cC]:\\/i, label: 'cipher /w:C:\\ (wipe free space)', os: ['win32'] },
80
- { pattern: /sfc\s+\/scannow.*\/offwindir/i, label: 'sfc offline (system repair risk)', os: ['win32'] },
81
- { pattern: /wmic\s+.*delete/i, label: 'wmic delete', os: ['win32'] },
82
- { pattern: /Invoke-Expression\s+\(.*Download.*\)/i, label: 'IEX download-and-execute', os: ['win32'] },
83
- { pattern: /iex\s+\(.*WebClient.*DownloadString/i, label: 'iex WebClient DownloadString (remote exec)', os: ['win32'] },
84
- { pattern: /powershell\s+.*-EncodedCommand/i, label: 'powershell -EncodedCommand (obfuscated)', os: ['win32'] },
85
- { pattern: /net\s+user\s+administrator\s+\*?\s*\/active:yes/i, label: 'net user administrator enable', os: ['win32'] },
86
- { pattern: /takeown\s+\/f\s+[cC]:\\/i, label: 'takeown /f C:\\ (ownership grab)', os: ['win32'] },
87
- { pattern: /icacls\s+[cC]:\\\s+\/grant/i, label: 'icacls C:\\ /grant (permission escalation)', os: ['win32'] },
70
+ { id: 'format-volume', pattern: /Format-Volume\s+.*-Confirm:\s*\$false/i, label: 'Format-Volume no confirm', os: ['win32'] },
71
+ { id: 'format-c-quick', pattern: /format\s+[cC]:\s*\/[qQy]/i, label: 'format C: /q or /y', os: ['win32'] },
72
+ { id: 'format-drive', pattern: /format\s+[a-zA-Z]:\s*\/[qQy]/i, label: 'format <drive> /q or /y', os: ['win32'] },
73
+ { id: 'del-wipe', pattern: /del\s+\/[fsqSFQ]+\s+[cC]:\\/i, label: 'del /f/s/q C:\\ (wipe)', os: ['win32'] },
74
+ { id: 'rd-all', pattern: /rd\s+\/[sq]+\s+[cC]:\\/i, label: 'rd /s/q C:\\ (remove all)', os: ['win32'] },
75
+ { id: 'remove-item-c', pattern: /Remove-Item\s+.*-Recurse\s+.*-Force.*[cC]:\\/i, label: 'Remove-Item -Recurse C:\\', os: ['win32'] },
76
+ { id: 'remove-item-root', pattern: /Remove-Item\s+.*-Recurse\s+.*-Force\s+\/\s/i, label: 'Remove-Item -Recurse /', os: ['win32'] },
77
+ { id: 'exec-policy', pattern: /Set-ExecutionPolicy\s+Unrestricted\s+-Force/i, label: 'ExecutionPolicy Unrestricted', os: ['win32'] },
78
+ { id: 'reg-system', pattern: /reg\s+(delete|add)\s+HKLM\\SYSTEM\\CurrentControlSet/i, label: 'reg modify HKLM\\SYSTEM', os: ['win32'] },
79
+ { id: 'bcdedit-del', pattern: /bcdedit\s+\/deletevalue/i, label: 'bcdedit /deletevalue', os: ['win32'] },
80
+ { id: 'bcdedit-safe', pattern: /bcdedit\s+\/set.*safeboot/i, label: 'bcdedit safeboot', os: ['win32'] },
81
+ { id: 'cipher-wipe', pattern: /cipher\s+\/w:[cC]:\\/i, label: 'cipher /w:C:\\ (wipe)', os: ['win32'] },
82
+ { id: 'sfc-offline', pattern: /sfc\s+\/scannow.*\/offwindir/i, label: 'sfc offline repair', os: ['win32'] },
83
+ { id: 'wmic-delete', pattern: /wmic\s+.*delete/i, label: 'wmic delete', os: ['win32'] },
84
+ { id: 'iex-download', pattern: /Invoke-Expression\s+\(.*Download.*\)/i, label: 'IEX download-and-exec', os: ['win32'] },
85
+ { id: 'iex-webclient', pattern: /iex\s+\(.*WebClient.*DownloadString/i, label: 'iex WebClient (remote exec)', os: ['win32'] },
86
+ { id: 'ps-encoded', pattern: /powershell\s+.*-EncodedCommand/i, label: 'powershell -EncodedCommand', os: ['win32'] },
87
+ { id: 'net-admin', pattern: /net\s+user\s+administrator\s+\*?\s*\/active:yes/i, label: 'net user admin enable', os: ['win32'] },
88
+ { id: 'takeown-c', pattern: /takeown\s+\/f\s+[cC]:\\/i, label: 'takeown /f C:\\', os: ['win32'] },
89
+ { id: 'icacls-c', pattern: /icacls\s+[cC]:\\\s+\/grant/i, label: 'icacls C:\\ /grant', os: ['win32'] },
88
90
  ];
89
91
 
92
+ /** Get the set of removed pattern IDs from global state */
93
+ function getRemovedPatternIds() {
94
+ const raw = _ctx.globalState.get('removedPatternIds', []);
95
+ return new Set(Array.isArray(raw) ? raw : []);
96
+ }
97
+
98
+ /** Save a set of removed pattern IDs to global state */
99
+ function saveRemovedPatternIds(ids) {
100
+ _ctx.globalState.update('removedPatternIds', [...ids]);
101
+ }
102
+
103
+ /** Get active built-in patterns (not removed by user) */
104
+ function getActiveBuiltinPatterns() {
105
+ const removed = getRemovedPatternIds();
106
+ return BUILTIN_DANGEROUS_PATTERNS.filter(p => !removed.has(p.id));
107
+ }
108
+
90
109
  /**
91
- * Checks a command string against built-in + custom dangerous patterns.
110
+ * Checks a command string against active built-in + custom dangerous patterns.
92
111
  * @param {string} cmd
93
112
  * @returns {{ matched: boolean, label: string, pattern: string }}
94
113
  */
@@ -100,8 +119,8 @@ function checkDangerousCommand(cmd) {
100
119
  const platform = process.platform; // 'win32' | 'linux' | 'darwin'
101
120
  const trimmed = cmd.trim();
102
121
 
103
- // Check built-in patterns (platform-filtered)
104
- for (const entry of BUILTIN_DANGEROUS_PATTERNS) {
122
+ // Check active built-in patterns (platform-filtered, respecting user removals)
123
+ for (const entry of getActiveBuiltinPatterns()) {
105
124
  if (!entry.os.includes(platform)) continue;
106
125
  if (entry.pattern.test(trimmed)) {
107
126
  return { matched: true, label: entry.label, pattern: entry.pattern.toString() };
@@ -307,7 +326,6 @@ class AntigravityPanelProvider {
307
326
  } else if (msg.command === 'toggleEnabled') {
308
327
  autoPilotEnabled = !autoPilotEnabled;
309
328
  updateStatusBarFromCache();
310
- // Persist into workspace config
311
329
  const cfg = vscode.workspace.getConfiguration('antigravityAutoAccept');
312
330
  await cfg.update('enabledOnStartup', autoPilotEnabled, vscode.ConfigurationTarget.Global);
313
331
  if (panelProvider) panelProvider.sendEnabled(autoPilotEnabled);
@@ -316,12 +334,37 @@ class AntigravityPanelProvider {
316
334
  );
317
335
  } else if (msg.command === 'openSettings') {
318
336
  vscode.commands.executeCommand('workbench.action.openSettings', 'antigravityAutoAccept');
337
+ } else if (msg.command === 'toggleCommandBlocking') {
338
+ const cfg = vscode.workspace.getConfiguration('antigravityAutoAccept');
339
+ const current = cfg.get('dangerousCommandBlocking.enabled', true);
340
+ const next = !current;
341
+ await cfg.update('dangerousCommandBlocking.enabled', next, vscode.ConfigurationTarget.Global);
342
+ this.sendBlockingEnabled(next);
343
+ vscode.window.showInformationMessage(
344
+ next ? '🛡️ Command Blocking enabled' : '⚠️ Command Blocking disabled',
345
+ );
346
+ } else if (msg.command === 'removePattern') {
347
+ // Remove a built-in pattern by ID
348
+ const removed = getRemovedPatternIds();
349
+ removed.add(msg.id);
350
+ saveRemovedPatternIds(removed);
351
+ this.sendPatterns();
352
+ vscode.window.showInformationMessage(`Removed preset: ${msg.label}`);
353
+ } else if (msg.command === 'resetPatterns') {
354
+ // Reset all removed patterns
355
+ saveRemovedPatternIds(new Set());
356
+ this.sendPatterns();
357
+ vscode.window.showInformationMessage('🔄 All preset dangerous commands restored to defaults.');
319
358
  }
320
359
  });
321
360
 
322
361
  // Initial load
323
362
  refreshStatus();
324
363
  this.sendEnabled(autoPilotEnabled);
364
+ this.sendBlockingEnabled(
365
+ vscode.workspace.getConfiguration('antigravityAutoAccept').get('dangerousCommandBlocking.enabled', true),
366
+ );
367
+ this.sendPatterns();
325
368
  }
326
369
 
327
370
  /** @param {string} text */
@@ -346,6 +389,25 @@ class AntigravityPanelProvider {
346
389
  this._view.webview.postMessage({ command: 'setEnabled', enabled });
347
390
  }
348
391
 
392
+ /** @param {boolean} enabled */
393
+ sendBlockingEnabled(enabled) {
394
+ if (!this._view) return;
395
+ this._view.webview.postMessage({ command: 'setBlockingEnabled', enabled });
396
+ }
397
+
398
+ /** Send current pattern list to the webview */
399
+ sendPatterns() {
400
+ if (!this._view) return;
401
+ const removed = getRemovedPatternIds();
402
+ const patterns = BUILTIN_DANGEROUS_PATTERNS.map(p => ({
403
+ id: p.id,
404
+ label: p.label,
405
+ os: p.os,
406
+ active: !removed.has(p.id),
407
+ }));
408
+ this._view.webview.postMessage({ command: 'patterns', patterns, totalBuiltin: BUILTIN_DANGEROUS_PATTERNS.length });
409
+ }
410
+
349
411
  _getHtml() {
350
412
  return /* html */`<!DOCTYPE html>
351
413
  <html lang="en">
@@ -431,20 +493,57 @@ class AntigravityPanelProvider {
431
493
  background:var(--vscode-editor-background);
432
494
  font-size:11px;font-weight:600;
433
495
  border-bottom:1px solid var(--vscode-panel-border);
496
+ cursor:pointer;
434
497
  }
498
+ .section-header:hover{opacity:.85}
435
499
  .section-header .badge{
436
500
  font-size:9px;padding:1px 6px;border-radius:10px;
437
- background:#e06c75;color:#fff;font-weight:700;
501
+ background:#4ec94e;color:#fff;font-weight:700;
438
502
  }
439
- .section-header .badge.on{background:#4ec94e}
440
- .blocklist{padding:6px 10px}
441
- .block-item{
442
- display:flex;align-items:center;gap:5px;
443
- font-size:10px;padding:2px 0;color:var(--vscode-descriptionForeground);
503
+ .section-header .badge.off{background:#e06c75}
504
+
505
+ .pattern-list{
506
+ max-height:220px;overflow-y:auto;padding:4px 0;
507
+ }
508
+ .pattern-list::-webkit-scrollbar{width:4px}
509
+ .pattern-list::-webkit-scrollbar-thumb{background:#555;border-radius:2px}
510
+
511
+ .pattern-item{
512
+ display:flex;align-items:center;gap:6px;
513
+ font-size:10px;padding:3px 10px;
514
+ color:var(--vscode-foreground);
515
+ border-bottom:1px solid rgba(128,128,128,.1);
516
+ transition:background .15s;
517
+ }
518
+ .pattern-item:hover{background:rgba(128,128,128,.1)}
519
+ .pattern-item.removed{opacity:.35;text-decoration:line-through}
520
+ .pattern-os{
521
+ font-size:8px;padding:1px 4px;border-radius:3px;
522
+ background:rgba(128,128,128,.2);color:var(--vscode-descriptionForeground);
523
+ flex-shrink:0;
524
+ }
525
+ .pattern-label{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
526
+ .pattern-del{
527
+ cursor:pointer;color:#e06c75;font-size:12px;flex-shrink:0;
528
+ opacity:0;transition:opacity .15s;line-height:1;
529
+ background:none;border:none;padding:0 2px;font-family:inherit;
530
+ }
531
+ .pattern-item:hover .pattern-del{opacity:1}
532
+ .pattern-del:hover{color:#ff4444}
533
+
534
+ .pattern-footer{
535
+ display:flex;align-items:center;justify-content:space-between;
536
+ padding:5px 10px;font-size:9px;
537
+ color:var(--vscode-descriptionForeground);
538
+ border-top:1px solid var(--vscode-panel-border);
539
+ background:var(--vscode-editor-background);
444
540
  }
445
- .block-dot{
446
- width:5px;height:5px;border-radius:50%;background:#e5c07b;flex-shrink:0;
541
+ .pattern-reset{
542
+ cursor:pointer;color:var(--vscode-textLink-foreground);
543
+ background:none;border:none;font-size:9px;padding:0;
544
+ font-family:inherit;
447
545
  }
546
+ .pattern-reset:hover{text-decoration:underline}
448
547
 
449
548
  /* ── Path box ── */
450
549
  .path-box{
@@ -481,6 +580,10 @@ class AntigravityPanelProvider {
481
580
  color:var(--vscode-descriptionForeground);
482
581
  text-align:center;line-height:1.5;
483
582
  }
583
+ .collapsed .pattern-list{display:none}
584
+ .collapsed .pattern-footer{display:none}
585
+ .chevron{transition:transform .2s;display:inline-block}
586
+ .collapsed .chevron{transform:rotate(-90deg)}
484
587
  </style>
485
588
  </head>
486
589
  <body>
@@ -518,20 +621,30 @@ class AntigravityPanelProvider {
518
621
 
519
622
  <div class="path-box" id="pathBox" style="display:none"></div>
520
623
 
624
+ <!-- Command Blocking toggle -->
625
+ <div class="toggle-row" id="blockingToggleRow">
626
+ <div>
627
+ <div class="toggle-label">🛡️ Command Blocking</div>
628
+ <div class="toggle-sub" id="blockingToggleSub">Active — blocking dangerous commands</div>
629
+ </div>
630
+ <label class="switch" title="Toggle Command Blocking on/off">
631
+ <input type="checkbox" id="blockingToggleCheck" checked onchange="send('toggleCommandBlocking')">
632
+ <span class="slider"></span>
633
+ </label>
634
+ </div>
635
+
521
636
  <!-- Dangerous Command Blocking section -->
522
- <div class="section">
523
- <div class="section-header">
524
- 🛡️ Command Blocking
525
- <span class="badge on" id="blockBadge">ON</span>
637
+ <div class="section" id="blockSection">
638
+ <div class="section-header" id="blockHeader">
639
+ <span><span class="chevron">▾</span> 🛡️ Dangerous Command Presets</span>
640
+ <span class="badge" id="blockBadge">0 active</span>
641
+ </div>
642
+ <div class="pattern-list" id="patternList">
643
+ <!-- populated dynamically -->
526
644
  </div>
527
- <div class="blocklist">
528
- <div class="block-item"><span class="block-dot"></span>rm -rf / and variants (Linux/macOS)</div>
529
- <div class="block-item"><span class="block-dot"></span>dd / mkfs / wipefs on devices</div>
530
- <div class="block-item"><span class="block-dot"></span>format C: / Remove-Item -Force (Windows)</div>
531
- <div class="block-item"><span class="block-dot"></span>curl/wget pipe to shell</div>
532
- <div class="block-item"><span class="block-dot"></span>Fork bombs, IEX download-exec</div>
533
- <div class="block-item"><span class="block-dot"></span>diskutil erase, bcdedit delete</div>
534
- <div class="block-item" style="color:var(--vscode-foreground);font-style:italic">+ 40 more built-in patterns</div>
645
+ <div class="pattern-footer">
646
+ <span id="patternCount">0 / 0 active</span>
647
+ <button class="pattern-reset" id="resetBtn" title="Restore all removed presets">🔄 Reset Defaults</button>
535
648
  </div>
536
649
  </div>
537
650
 
@@ -545,12 +658,13 @@ class AntigravityPanelProvider {
545
658
  <script>
546
659
  const vscode = acquireVsCodeApi();
547
660
 
548
- function send(cmd) {
549
- if (cmd !== 'openSettings' && cmd !== 'toggleEnabled' && cmd !== 'refresh') {
661
+ function send(cmd, extra) {
662
+ if (cmd !== 'openSettings' && cmd !== 'toggleEnabled' && cmd !== 'toggleCommandBlocking'
663
+ && cmd !== 'refresh' && cmd !== 'removePattern' && cmd !== 'resetPatterns') {
550
664
  document.getElementById('btnApply').disabled = true;
551
665
  document.getElementById('btnRevert').disabled = true;
552
666
  }
553
- vscode.postMessage({ command: cmd });
667
+ vscode.postMessage(Object.assign({ command: cmd }, extra || {}));
554
668
  }
555
669
 
556
670
  // Wire up buttons
@@ -561,59 +675,125 @@ class AntigravityPanelProvider {
561
675
  document.querySelector('.btn-refresh').disabled = true;
562
676
  setTimeout(() => { document.querySelector('.btn-refresh').disabled = false; }, 2000);
563
677
  });
678
+ document.getElementById('resetBtn').addEventListener('click', () => send('resetPatterns'));
679
+
680
+ // Collapse/expand
681
+ document.getElementById('blockHeader').addEventListener('click', () => {
682
+ document.getElementById('blockSection').classList.toggle('collapsed');
683
+ });
564
684
 
565
685
  send('refresh');
566
686
 
687
+ /** Render the pattern list */
688
+ function renderPatterns(patterns, totalBuiltin) {
689
+ const list = document.getElementById('patternList');
690
+ list.textContent = '';
691
+ const activeCount = patterns.filter(p => p.active).length;
692
+ document.getElementById('blockBadge').textContent = activeCount + ' active';
693
+ document.getElementById('blockBadge').className = 'badge' + (activeCount === 0 ? ' off' : '');
694
+ document.getElementById('patternCount').textContent = activeCount + ' / ' + totalBuiltin + ' active';
695
+
696
+ const osLabel = { linux: 'LNX', darwin: 'MAC', win32: 'WIN' };
697
+
698
+ for (const p of patterns) {
699
+ const row = document.createElement('div');
700
+ row.className = 'pattern-item' + (p.active ? '' : ' removed');
701
+
702
+ // OS badges
703
+ const osSpan = document.createElement('span');
704
+ osSpan.className = 'pattern-os';
705
+ osSpan.textContent = p.os.map(o => osLabel[o] || o).join('/');
706
+ row.appendChild(osSpan);
707
+
708
+ // Label
709
+ const lbl = document.createElement('span');
710
+ lbl.className = 'pattern-label';
711
+ lbl.textContent = p.label;
712
+ lbl.title = p.label;
713
+ row.appendChild(lbl);
714
+
715
+ // Delete button (only for active items)
716
+ if (p.active) {
717
+ const del = document.createElement('button');
718
+ del.className = 'pattern-del';
719
+ del.textContent = '✕';
720
+ del.title = 'Remove this preset';
721
+ del.addEventListener('click', (e) => {
722
+ e.stopPropagation();
723
+ send('removePattern', { id: p.id, label: p.label });
724
+ });
725
+ row.appendChild(del);
726
+ }
727
+
728
+ list.appendChild(row);
729
+ }
730
+ }
731
+
567
732
  window.addEventListener('message', e => {
568
- const { command, patched, basePath, files, text, enabled } = e.data;
733
+ const data = e.data;
569
734
 
570
- if (command === 'setEnabled') {
735
+ if (data.command === 'setEnabled') {
571
736
  const chk = document.getElementById('toggleCheck');
572
- chk.checked = enabled;
573
- document.getElementById('toggleSub').textContent = enabled
737
+ chk.checked = data.enabled;
738
+ document.getElementById('toggleSub').textContent = data.enabled
574
739
  ? 'Active — executing all commands'
575
740
  : 'Suspended — commands require confirmation';
576
741
  }
577
742
 
578
- if (command === 'loading') {
743
+ if (data.command === 'setBlockingEnabled') {
744
+ const chk = document.getElementById('blockingToggleCheck');
745
+ chk.checked = data.enabled;
746
+ document.getElementById('blockingToggleSub').textContent = data.enabled
747
+ ? 'Active — blocking dangerous commands'
748
+ : 'Disabled — commands not blocked';
749
+ // Dim the presets section when blocking is off
750
+ const section = document.getElementById('blockSection');
751
+ if (section) section.style.opacity = data.enabled ? '1' : '0.4';
752
+ }
753
+
754
+ if (data.command === 'patterns') {
755
+ renderPatterns(data.patterns, data.totalBuiltin);
756
+ }
757
+
758
+ if (data.command === 'loading') {
579
759
  document.getElementById('lbl').className = 'status-label loading';
580
- document.getElementById('lbl').textContent = text || '⏳ Working...';
760
+ document.getElementById('lbl').textContent = data.text || '⏳ Working...';
581
761
  document.getElementById('desc').textContent = 'Please wait...';
582
762
  return;
583
763
  }
584
764
 
585
- if (command !== 'update') return;
765
+ if (data.command !== 'update') return;
586
766
 
587
767
  // Re-enable buttons
588
768
  document.getElementById('btnApply').disabled = false;
589
769
  document.getElementById('btnRevert').disabled = false;
590
770
 
591
- const notFound = !basePath;
771
+ const notFound = !data.basePath;
592
772
 
593
- document.getElementById('card').className = 'status-card' + (notFound ? ' not-found' : patched ? ' patched' : '');
594
- document.getElementById('dot').className = 'dot' + (notFound ? ' not-found' : patched ? ' patched' : '');
595
- document.getElementById('dot').textContent = notFound ? '✕' : patched ? '✓' : '○';
773
+ document.getElementById('card').className = 'status-card' + (notFound ? ' not-found' : data.patched ? ' patched' : '');
774
+ document.getElementById('dot').className = 'dot' + (notFound ? ' not-found' : data.patched ? ' patched' : '');
775
+ document.getElementById('dot').textContent = notFound ? '✕' : data.patched ? '✓' : '○';
596
776
 
597
777
  const lbl = document.getElementById('lbl');
598
- lbl.className = 'status-label ' + (notFound ? 'not-found' : patched ? 'patched' : 'pending');
599
- lbl.textContent = notFound ? 'Not Found' : patched ? 'Patched' : 'Not Patched';
778
+ lbl.className = 'status-label ' + (notFound ? 'not-found' : data.patched ? 'patched' : 'pending');
779
+ lbl.textContent = notFound ? 'Not Found' : data.patched ? 'Patched' : 'Not Patched';
600
780
 
601
781
  document.getElementById('desc').textContent = notFound
602
782
  ? 'Antigravity installation not detected'
603
- : patched ? 'AutoPilot is active on this machine' : 'Click APPLY PATCH to activate';
783
+ : data.patched ? 'AutoPilot is active on this machine' : 'Click APPLY PATCH to activate';
604
784
 
605
- if (basePath) {
785
+ if (data.basePath) {
606
786
  const pb = document.getElementById('pathBox');
607
- pb.textContent = basePath;
787
+ pb.textContent = data.basePath;
608
788
  pb.style.display = 'block';
609
789
  }
610
790
 
611
- document.getElementById('btnApply').style.display = notFound || patched ? 'none' : 'block';
612
- document.getElementById('btnRevert').style.display = patched ? 'block' : 'none';
791
+ document.getElementById('btnApply').style.display = notFound || data.patched ? 'none' : 'block';
792
+ document.getElementById('btnRevert').style.display = data.patched ? 'block' : 'none';
613
793
 
614
794
  document.getElementById('noteBox').textContent = notFound
615
795
  ? 'Install Antigravity first, then click Refresh.'
616
- : patched ? 'Restart Antigravity to apply changes.' : '';
796
+ : data.patched ? 'Restart Antigravity to apply changes.' : '';
617
797
  });
618
798
  </script>
619
799
  </body>
@@ -630,6 +810,8 @@ function updateStatusBar() { updateStatusBarFromCache(); }
630
810
 
631
811
  /** @param {vscode.ExtensionContext} context */
632
812
  function activate(context) {
813
+ _ctx = context;
814
+
633
815
  // Shared output channel
634
816
  outputChannel = vscode.window.createOutputChannel('AutoPilot');
635
817
  context.subscriptions.push(outputChannel);
@@ -657,9 +839,6 @@ function activate(context) {
657
839
  );
658
840
 
659
841
  // ── Terminal command watcher (Dangerous Command Blocking) ──────────────────
660
- // VS Code API: onDidWriteTerminalData captures output; we intercept typed
661
- // commands via onDidStartTerminalShellExecution (VS Code 1.87+).
662
- // Fallback: detect via terminal write events.
663
842
  if (typeof vscode.window.onDidStartTerminalShellExecution === 'function') {
664
843
  context.subscriptions.push(
665
844
  vscode.window.onDidStartTerminalShellExecution((event) => {
@@ -668,14 +847,12 @@ function activate(context) {
668
847
  const check = checkDangerousCommand(cmd);
669
848
  if (check.matched) {
670
849
  handleDangerousCommand(cmd, check.label);
671
- // Note: VS Code does not expose a cancellation API for shell exec;
672
- // we log/warn/notify. For full blocking, pair with shell hook.
673
850
  }
674
851
  }),
675
852
  );
676
853
  }
677
854
 
678
- // Config change listener — react to user toggling blocking or action
855
+ // Config change listener
679
856
  context.subscriptions.push(
680
857
  vscode.workspace.onDidChangeConfiguration((e) => {
681
858
  if (e.affectsConfiguration('antigravityAutoAccept.enabledOnStartup')) {
package/icon.png CHANGED
Binary file
package/package.json CHANGED
@@ -2,7 +2,8 @@
2
2
  "name": "antigravity-autopilot",
3
3
  "displayName": "Antigravity AutoPilot",
4
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.2.0",
5
+ "version": "1.4.0",
6
+ "license": "MIT",
6
7
  "publisher": "nguyen-hoang",
7
8
  "bin": {
8
9
  "antigravity-autopilot": "./cli.js"