antigravity-autopilot 1.1.0 → 1.2.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.
Files changed (2) hide show
  1. package/extension.js +379 -96
  2. package/package.json +34 -7
package/extension.js CHANGED
@@ -16,6 +16,142 @@ let isPatchApplied = false;
16
16
  let panelProvider = null;
17
17
  /** @type {{ basePath: string|null, files: any[], patched: boolean } | null} */
18
18
  let _cachedStatus = null;
19
+ /** @type {boolean} */
20
+ let autoPilotEnabled = true;
21
+ /** @type {vscode.OutputChannel} */
22
+ let outputChannel;
23
+
24
+ // ─── Dangerous Command Blocking ───────────────────────────────────────────────
25
+
26
+ /**
27
+ * Built-in dangerous command patterns.
28
+ * Covers Linux/macOS/Windows destructive commands.
29
+ * Each entry: { pattern: RegExp, label: string, os: string[] }
30
+ */
31
+ const BUILTIN_DANGEROUS_PATTERNS = [
32
+ // ── 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'] },
63
+ // ── 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'] },
67
+ // ── 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'] },
88
+ ];
89
+
90
+ /**
91
+ * Checks a command string against built-in + custom dangerous patterns.
92
+ * @param {string} cmd
93
+ * @returns {{ matched: boolean, label: string, pattern: string }}
94
+ */
95
+ function checkDangerousCommand(cmd) {
96
+ const cfg = vscode.workspace.getConfiguration('antigravityAutoAccept');
97
+ const enabled = cfg.get('dangerousCommandBlocking.enabled', true);
98
+ if (!enabled) return { matched: false, label: '', pattern: '' };
99
+
100
+ const platform = process.platform; // 'win32' | 'linux' | 'darwin'
101
+ const trimmed = cmd.trim();
102
+
103
+ // Check built-in patterns (platform-filtered)
104
+ for (const entry of BUILTIN_DANGEROUS_PATTERNS) {
105
+ if (!entry.os.includes(platform)) continue;
106
+ if (entry.pattern.test(trimmed)) {
107
+ return { matched: true, label: entry.label, pattern: entry.pattern.toString() };
108
+ }
109
+ }
110
+
111
+ // Check custom user patterns
112
+ const customPatterns = /** @type {string[]} */ (cfg.get('dangerousCommandBlocking.customPatterns', []));
113
+ for (const raw of customPatterns) {
114
+ try {
115
+ const re = new RegExp(raw, 'i');
116
+ if (re.test(trimmed)) {
117
+ return { matched: true, label: `Custom: ${raw}`, pattern: raw };
118
+ }
119
+ } catch {
120
+ // Invalid regex — skip silently
121
+ }
122
+ }
123
+
124
+ return { matched: false, label: '', pattern: '' };
125
+ }
126
+
127
+ /**
128
+ * Handles a detected dangerous command according to the configured action.
129
+ * @param {string} cmd - The full command text
130
+ * @param {string} label - Human-readable reason
131
+ */
132
+ function handleDangerousCommand(cmd, label) {
133
+ const cfg = vscode.workspace.getConfiguration('antigravityAutoAccept');
134
+ const action = cfg.get('dangerousCommandBlocking.action', 'block');
135
+ const msg = `🛡️ Dangerous command detected: "${label}" — \`${cmd.trim().substring(0, 80)}\``;
136
+
137
+ outputChannel.appendLine(`[DangerBlock][${new Date().toISOString()}] ${action.toUpperCase()} | ${label} | CMD: ${cmd.trim()}`);
138
+
139
+ if (action === 'block') {
140
+ vscode.window.showErrorMessage(
141
+ `⛔ Blocked: ${label}`,
142
+ { modal: false },
143
+ 'View Details',
144
+ ).then((choice) => {
145
+ if (choice === 'View Details') {
146
+ outputChannel.show(true);
147
+ outputChannel.appendLine(`[DangerBlock] Blocked command: ${cmd.trim()}`);
148
+ }
149
+ });
150
+ } else if (action === 'warn') {
151
+ vscode.window.showWarningMessage(`⚠️ Warning: ${msg}`);
152
+ }
153
+ // 'log' — already logged above, no UI notification
154
+ }
19
155
 
20
156
  // ─── Child Process Bridge ─────────────────────────────────────────────────────
21
157
 
@@ -28,21 +164,20 @@ let _cachedStatus = null;
28
164
  function runPatcher(command) {
29
165
  return new Promise((resolve, reject) => {
30
166
  const child = fork(PATCHER, [], { silent: true });
31
- const channel = vscode.window.createOutputChannel('AutoPilot');
32
167
 
33
168
  child.on('message', (msg) => {
34
169
  if (!msg || typeof msg !== 'object') return;
35
170
  const m = /** @type {{type:string,msg?:string}} */(msg);
36
171
  if (m.type === 'log') {
37
172
  console.log(m.msg);
38
- channel.appendLine(m.msg || '');
173
+ outputChannel.appendLine(m.msg || '');
39
174
  } else {
40
- resolve(msg); // status or result message
175
+ resolve(msg);
41
176
  }
42
177
  });
43
178
 
44
179
  child.on('error', (err) => {
45
- channel.appendLine(`[AutoAccept] fork error: ${err.message}`);
180
+ outputChannel.appendLine(`[AutoPilot] fork error: ${err.message}`);
46
181
  reject(err);
47
182
  });
48
183
 
@@ -51,7 +186,7 @@ function runPatcher(command) {
51
186
  resolve({
52
187
  type: 'result',
53
188
  success: false,
54
- message: `Process exited with code ${code}. Check Output > AutoAccept for details.`,
189
+ message: `Process exited with code ${code}. Check Output > AutoPilot for details.`,
55
190
  });
56
191
  }
57
192
  });
@@ -116,7 +251,32 @@ async function refreshStatus() {
116
251
  if (panelProvider) panelProvider.sendStatus(status);
117
252
  }
118
253
 
119
- // ─── Sidebar WebView ──────────────────────────────────────────────────────
254
+ // ─── Status Bar ───────────────────────────────────────────────────────────────
255
+
256
+ function updateStatusBarFromCache() {
257
+ if (!statusBarItem) return;
258
+ if (!autoPilotEnabled) {
259
+ statusBarItem.text = '$(debug-pause) AG Paused';
260
+ statusBarItem.tooltip = 'Antigravity AutoPilot is suspended';
261
+ statusBarItem.color = new vscode.ThemeColor('statusBarItem.warningForeground');
262
+ return;
263
+ }
264
+ if (!_cachedStatus || !_cachedStatus.basePath) {
265
+ statusBarItem.text = '$(warning) AG: Not Found';
266
+ statusBarItem.tooltip = 'Antigravity not found on this system';
267
+ statusBarItem.color = new vscode.ThemeColor('statusBarItem.errorForeground');
268
+ } else if (_cachedStatus.patched) {
269
+ statusBarItem.text = '$(zap) AG: Active';
270
+ statusBarItem.tooltip = 'Antigravity AutoPilot — Patch Applied ✅';
271
+ statusBarItem.color = new vscode.ThemeColor('statusBarItem.prominentForeground');
272
+ } else {
273
+ statusBarItem.text = '$(circle-slash) AG: Inactive';
274
+ statusBarItem.tooltip = 'Antigravity AutoPilot — Patch Not Applied';
275
+ statusBarItem.color = undefined;
276
+ }
277
+ }
278
+
279
+ // ─── Sidebar WebView ──────────────────────────────────────────────────────────
120
280
 
121
281
  class AntigravityPanelProvider {
122
282
  /** @param {vscode.ExtensionContext} context */
@@ -144,11 +304,24 @@ class AntigravityPanelProvider {
144
304
  } else if (msg.command === 'refresh') {
145
305
  this._postLoading('⏳ Checking...');
146
306
  await refreshStatus();
307
+ } else if (msg.command === 'toggleEnabled') {
308
+ autoPilotEnabled = !autoPilotEnabled;
309
+ updateStatusBarFromCache();
310
+ // Persist into workspace config
311
+ const cfg = vscode.workspace.getConfiguration('antigravityAutoAccept');
312
+ await cfg.update('enabledOnStartup', autoPilotEnabled, vscode.ConfigurationTarget.Global);
313
+ if (panelProvider) panelProvider.sendEnabled(autoPilotEnabled);
314
+ vscode.window.showInformationMessage(
315
+ autoPilotEnabled ? '⚡ AutoPilot resumed' : '⏸ AutoPilot suspended',
316
+ );
317
+ } else if (msg.command === 'openSettings') {
318
+ vscode.commands.executeCommand('workbench.action.openSettings', 'antigravityAutoAccept');
147
319
  }
148
320
  });
149
321
 
150
322
  // Initial load
151
323
  refreshStatus();
324
+ this.sendEnabled(autoPilotEnabled);
152
325
  }
153
326
 
154
327
  /** @param {string} text */
@@ -167,8 +340,11 @@ class AntigravityPanelProvider {
167
340
  });
168
341
  }
169
342
 
170
- /** @deprecated use sendStatus */
171
- updateState() { refreshStatus(); }
343
+ /** @param {boolean} enabled */
344
+ sendEnabled(enabled) {
345
+ if (!this._view) return;
346
+ this._view.webview.postMessage({ command: 'setEnabled', enabled });
347
+ }
172
348
 
173
349
  _getHtml() {
174
350
  return /* html */`<!DOCTYPE html>
@@ -182,80 +358,133 @@ class AntigravityPanelProvider {
182
358
  font-family:'Segoe UI',sans-serif;
183
359
  background:var(--vscode-sideBar-background);
184
360
  color:var(--vscode-foreground);
185
- padding:16px;user-select:none;
361
+ padding:12px;user-select:none;
186
362
  }
363
+
364
+ /* ── Header ── */
187
365
  .header{
188
366
  display:flex;align-items:center;gap:8px;
189
- margin-bottom:16px;padding-bottom:12px;
367
+ margin-bottom:12px;padding-bottom:10px;
190
368
  border-bottom:1px solid var(--vscode-panel-border);
191
369
  }
192
- .header-icon{font-size:20px}
370
+ .header-icon{font-size:18px}
193
371
  .header-title{font-size:13px;font-weight:600;letter-spacing:.3px}
194
- .header-sub{font-size:11px;color:var(--vscode-descriptionForeground);margin-top:2px}
372
+ .header-sub{font-size:10px;color:var(--vscode-descriptionForeground);margin-top:2px}
195
373
 
374
+ /* ── Toggle Row ── */
375
+ .toggle-row{
376
+ display:flex;align-items:center;justify-content:space-between;
377
+ background:var(--vscode-editor-background);
378
+ border:1px solid var(--vscode-panel-border);
379
+ border-radius:6px;padding:8px 10px;margin-bottom:10px;
380
+ }
381
+ .toggle-label{font-size:11px;font-weight:600}
382
+ .toggle-sub{font-size:10px;color:var(--vscode-descriptionForeground);margin-top:1px}
383
+ .switch{position:relative;display:inline-block;width:34px;height:18px;flex-shrink:0}
384
+ .switch input{opacity:0;width:0;height:0}
385
+ .slider{
386
+ position:absolute;cursor:pointer;inset:0;
387
+ background:#555;border-radius:18px;
388
+ transition:background .2s;
389
+ }
390
+ .slider:before{
391
+ position:absolute;content:'';height:14px;width:14px;
392
+ left:2px;bottom:2px;background:#fff;border-radius:50%;
393
+ transition:transform .2s;
394
+ }
395
+ input:checked + .slider{background:#4ec94e}
396
+ input:checked + .slider:before{transform:translateX(16px)}
397
+
398
+ /* ── Status card ── */
196
399
  .status-card{
197
- border-radius:8px;padding:16px;margin-bottom:12px;
400
+ border-radius:6px;padding:12px;margin-bottom:10px;
198
401
  background:var(--vscode-editor-background);
199
402
  border:1px solid var(--vscode-panel-border);
200
403
  transition:border-color .3s,background .3s;
201
404
  }
202
405
  .status-card.patched{border-color:#4ec94e;background:rgba(78,201,78,.07)}
203
406
  .status-card.not-found{border-color:#e06c75;background:rgba(224,108,117,.07)}
204
- .status-row{display:flex;align-items:center;gap:12px}
407
+ .status-row{display:flex;align-items:center;gap:10px}
205
408
  .dot{
206
- width:36px;height:36px;border-radius:50%;
409
+ width:32px;height:32px;border-radius:50%;
207
410
  display:flex;align-items:center;justify-content:center;
208
- font-size:18px;flex-shrink:0;background:#3c3c3c;
411
+ font-size:16px;flex-shrink:0;background:#3c3c3c;
209
412
  transition:background .3s;
210
413
  }
211
414
  .dot.patched{background:#4ec94e}
212
415
  .dot.not-found{background:#e06c75}
213
- .status-label{font-size:18px;font-weight:700;line-height:1}
416
+ .status-label{font-size:16px;font-weight:700;line-height:1}
214
417
  .status-label.patched{color:#4ec94e}
215
418
  .status-label.pending{color:#e5c07b}
216
419
  .status-label.not-found{color:#e06c75}
217
420
  .status-label.loading{color:var(--vscode-descriptionForeground)}
218
- .status-desc{font-size:11px;color:var(--vscode-descriptionForeground);margin-top:4px}
421
+ .status-desc{font-size:10px;color:var(--vscode-descriptionForeground);margin-top:3px}
422
+
423
+ /* ── Security section ── */
424
+ .section{
425
+ border:1px solid var(--vscode-panel-border);
426
+ border-radius:6px;margin-bottom:10px;overflow:hidden;
427
+ }
428
+ .section-header{
429
+ display:flex;align-items:center;justify-content:space-between;
430
+ padding:7px 10px;
431
+ background:var(--vscode-editor-background);
432
+ font-size:11px;font-weight:600;
433
+ border-bottom:1px solid var(--vscode-panel-border);
434
+ }
435
+ .section-header .badge{
436
+ font-size:9px;padding:1px 6px;border-radius:10px;
437
+ background:#e06c75;color:#fff;font-weight:700;
438
+ }
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);
444
+ }
445
+ .block-dot{
446
+ width:5px;height:5px;border-radius:50%;background:#e5c07b;flex-shrink:0;
447
+ }
219
448
 
449
+ /* ── Path box ── */
220
450
  .path-box{
221
- font-size:10px;color:var(--vscode-descriptionForeground);
451
+ font-size:9px;color:var(--vscode-descriptionForeground);
222
452
  background:var(--vscode-editor-background);
223
453
  border:1px solid var(--vscode-panel-border);
224
- border-radius:4px;padding:6px 8px;margin-bottom:10px;
454
+ border-radius:4px;padding:4px 6px;margin-bottom:8px;
225
455
  word-break:break-all;
226
456
  }
227
457
 
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
-
458
+ /* ── Buttons ── */
237
459
  .btn{
238
- width:100%;padding:10px;border:none;border-radius:6px;
239
- font-size:12px;font-weight:700;letter-spacing:.5px;
460
+ width:100%;padding:8px;border:none;border-radius:5px;
461
+ font-size:11px;font-weight:700;letter-spacing:.4px;
240
462
  cursor:pointer;transition:background .2s,transform .1s;
241
- font-family:inherit;margin-bottom:6px;
463
+ font-family:inherit;margin-bottom:5px;
242
464
  }
243
465
  .btn:active{transform:scale(.98)}
244
- .btn:disabled{opacity:.5;cursor:not-allowed}
466
+ .btn:disabled{opacity:.4;cursor:not-allowed}
245
467
  .btn-apply{background:#0e7a4c;color:#fff}
246
468
  .btn-apply:hover:not(:disabled){background:#0f9058}
247
469
  .btn-revert{background:#5a1a1a;color:#fff}
248
470
  .btn-revert:hover:not(:disabled){background:#7a2020}
249
471
  .btn-refresh{background:var(--vscode-button-secondaryBackground);color:var(--vscode-button-secondaryForeground)}
472
+ .btn-settings{
473
+ background:transparent;color:var(--vscode-descriptionForeground);
474
+ border:1px solid var(--vscode-panel-border);font-size:10px;
475
+ margin-top:2px;
476
+ }
477
+ .btn-settings:hover{background:var(--vscode-editor-background)}
250
478
 
251
479
  .note{
252
- margin-top:10px;font-size:10px;
480
+ margin-top:8px;font-size:9px;
253
481
  color:var(--vscode-descriptionForeground);
254
482
  text-align:center;line-height:1.5;
255
483
  }
256
484
  </style>
257
485
  </head>
258
486
  <body>
487
+
259
488
  <div class="header">
260
489
  <span class="header-icon">⚡</span>
261
490
  <div>
@@ -264,6 +493,19 @@ class AntigravityPanelProvider {
264
493
  </div>
265
494
  </div>
266
495
 
496
+ <!-- Enabled/Disabled toggle -->
497
+ <div class="toggle-row" id="toggleRow">
498
+ <div>
499
+ <div class="toggle-label">AutoPilot</div>
500
+ <div class="toggle-sub" id="toggleSub">Active — executing all commands</div>
501
+ </div>
502
+ <label class="switch" title="Toggle AutoPilot on/off">
503
+ <input type="checkbox" id="toggleCheck" checked onchange="send('toggleEnabled')">
504
+ <span class="slider"></span>
505
+ </label>
506
+ </div>
507
+
508
+ <!-- Patch Status -->
267
509
  <div class="status-card" id="card">
268
510
  <div class="status-row">
269
511
  <div class="dot" id="dot">⊘</div>
@@ -276,26 +518,62 @@ class AntigravityPanelProvider {
276
518
 
277
519
  <div class="path-box" id="pathBox" style="display:none"></div>
278
520
 
279
- <div class="files-list" id="filesList"></div>
521
+ <!-- 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>
526
+ </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>
535
+ </div>
536
+ </div>
280
537
 
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>
538
+ <button class="btn btn-apply" id="btnApply" style="display:none">⚡ APPLY PATCH</button>
539
+ <button class="btn btn-revert" id="btnRevert" style="display:none">↩ REVERT PATCH</button>
540
+ <button class="btn btn-refresh">🔄 Refresh Status</button>
541
+ <button class="btn btn-settings" onclick="send('openSettings')">⚙️ Open Settings</button>
284
542
 
285
543
  <div class="note" id="noteBox"></div>
286
544
 
287
545
  <script>
288
546
  const vscode = acquireVsCodeApi();
547
+
289
548
  function send(cmd) {
290
- document.getElementById('btnApply').disabled = true;
291
- document.getElementById('btnRevert').disabled = true;
292
- document.getElementById('btnRefresh') && (document.getElementById('btnRefresh').disabled = true);
549
+ if (cmd !== 'openSettings' && cmd !== 'toggleEnabled' && cmd !== 'refresh') {
550
+ document.getElementById('btnApply').disabled = true;
551
+ document.getElementById('btnRevert').disabled = true;
552
+ }
293
553
  vscode.postMessage({ command: cmd });
294
554
  }
555
+
556
+ // Wire up buttons
557
+ document.getElementById('btnApply').addEventListener('click', () => send('apply'));
558
+ document.getElementById('btnRevert').addEventListener('click', () => send('revert'));
559
+ document.querySelector('.btn-refresh').addEventListener('click', () => {
560
+ send('refresh');
561
+ document.querySelector('.btn-refresh').disabled = true;
562
+ setTimeout(() => { document.querySelector('.btn-refresh').disabled = false; }, 2000);
563
+ });
564
+
295
565
  send('refresh');
296
566
 
297
567
  window.addEventListener('message', e => {
298
- const { command, patched, basePath, files, text } = e.data;
568
+ const { command, patched, basePath, files, text, enabled } = e.data;
569
+
570
+ if (command === 'setEnabled') {
571
+ const chk = document.getElementById('toggleCheck');
572
+ chk.checked = enabled;
573
+ document.getElementById('toggleSub').textContent = enabled
574
+ ? 'Active — executing all commands'
575
+ : 'Suspended — commands require confirmation';
576
+ }
299
577
 
300
578
  if (command === 'loading') {
301
579
  document.getElementById('lbl').className = 'status-label loading';
@@ -318,42 +596,24 @@ class AntigravityPanelProvider {
318
596
 
319
597
  const lbl = document.getElementById('lbl');
320
598
  lbl.className = 'status-label ' + (notFound ? 'not-found' : patched ? 'patched' : 'pending');
321
- lbl.textContent = notFound ? 'NOT FOUND' : patched ? 'PATCHED' : 'NOT PATCHED';
599
+ lbl.textContent = notFound ? 'Not Found' : patched ? 'Patched' : 'Not Patched';
322
600
 
323
601
  document.getElementById('desc').textContent = notFound
324
- ? 'Antigravity not installed'
325
- : patched
326
- ? 'useEffect added — restart Antigravity!'
327
- : 'Patch not applied yet';
602
+ ? 'Antigravity installation not detected'
603
+ : patched ? 'AutoPilot is active on this machine' : 'Click APPLY PATCH to activate';
328
604
 
329
- const pathBox = document.getElementById('pathBox');
330
605
  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
- }
606
+ const pb = document.getElementById('pathBox');
607
+ pb.textContent = basePath;
608
+ pb.style.display = 'block';
347
609
  }
348
610
 
349
- document.getElementById('btnApply').style.display = (notFound || patched) ? 'none' : '';
350
- document.getElementById('btnRevert').style.display = patched ? '' : 'none';
611
+ document.getElementById('btnApply').style.display = notFound || patched ? 'none' : 'block';
612
+ document.getElementById('btnRevert').style.display = patched ? 'block' : 'none';
351
613
 
352
614
  document.getElementById('noteBox').textContent = notFound
353
- ? '⚠️ Install Antigravity first'
354
- : patched
355
- ? '💡 Re-run after Antigravity updates'
356
- : '💡 Apply patch once, then restart Antigravity';
615
+ ? 'Install Antigravity first, then click Refresh.'
616
+ : patched ? 'Restart Antigravity to apply changes.' : '';
357
617
  });
358
618
  </script>
359
619
  </body>
@@ -361,38 +621,30 @@ class AntigravityPanelProvider {
361
621
  }
362
622
  }
363
623
 
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
- }
624
+ // ─── Status Bar (legacy helpers) ──────────────────────────────────────────────
382
625
 
383
626
  /** @deprecated kept for backward compat */
384
627
  function updateStatusBar() { updateStatusBarFromCache(); }
385
628
 
386
- // ─── Activate / Deactivate ──────────────────────────────────────────────
629
+ // ─── Activate / Deactivate ────────────────────────────────────────────────────
387
630
 
388
631
  /** @param {vscode.ExtensionContext} context */
389
632
  function activate(context) {
390
- // Status bar — shows spinner until first async check completes
633
+ // Shared output channel
634
+ outputChannel = vscode.window.createOutputChannel('AutoPilot');
635
+ context.subscriptions.push(outputChannel);
636
+
637
+ // Read enabledOnStartup setting
638
+ const cfg = vscode.workspace.getConfiguration('antigravityAutoAccept');
639
+ autoPilotEnabled = cfg.get('enabledOnStartup', true);
640
+
641
+ // Status bar
391
642
  statusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right, 100);
392
643
  statusBarItem.command = 'antigravityAutoAccept.openPanel';
393
644
  statusBarItem.text = `$(sync~spin) AG Patch`;
394
645
  statusBarItem.tooltip = 'Checking patch status...';
395
646
  statusBarItem.show();
647
+ context.subscriptions.push(statusBarItem);
396
648
 
397
649
  // Sidebar
398
650
  panelProvider = new AntigravityPanelProvider(context);
@@ -404,6 +656,36 @@ function activate(context) {
404
656
  ),
405
657
  );
406
658
 
659
+ // ── 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
+ if (typeof vscode.window.onDidStartTerminalShellExecution === 'function') {
664
+ context.subscriptions.push(
665
+ vscode.window.onDidStartTerminalShellExecution((event) => {
666
+ const cmd = event.execution.commandLine?.value || '';
667
+ if (!cmd) return;
668
+ const check = checkDangerousCommand(cmd);
669
+ if (check.matched) {
670
+ 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
+ }
674
+ }),
675
+ );
676
+ }
677
+
678
+ // Config change listener — react to user toggling blocking or action
679
+ context.subscriptions.push(
680
+ vscode.workspace.onDidChangeConfiguration((e) => {
681
+ if (e.affectsConfiguration('antigravityAutoAccept.enabledOnStartup')) {
682
+ autoPilotEnabled = vscode.workspace.getConfiguration('antigravityAutoAccept').get('enabledOnStartup', true);
683
+ updateStatusBarFromCache();
684
+ if (panelProvider) panelProvider.sendEnabled(autoPilotEnabled);
685
+ }
686
+ }),
687
+ );
688
+
407
689
  // Commands
408
690
  context.subscriptions.push(
409
691
  vscode.commands.registerCommand('antigravityAutoAccept.applyPatch', async () => {
@@ -429,20 +711,21 @@ function activate(context) {
429
711
  }),
430
712
  );
431
713
 
432
- context.subscriptions.push(statusBarItem);
433
-
434
714
  // Async startup — never blocks extension host!
435
715
  (async () => {
436
716
  const status = await getPatchStatus();
437
717
  isPatchApplied = status.patched;
438
718
  updateStatusBarFromCache();
439
- if (panelProvider) panelProvider.sendStatus(status);
719
+ if (panelProvider) {
720
+ panelProvider.sendStatus(status);
721
+ panelProvider.sendEnabled(autoPilotEnabled);
722
+ }
440
723
 
441
- const cfg = vscode.workspace.getConfiguration('antigravityAutoAccept');
442
- if (cfg.get('applyOnStartup') && !status.patched && status.basePath) {
724
+ const startCfg = vscode.workspace.getConfiguration('antigravityAutoAccept');
725
+ if (startCfg.get('applyOnStartup') && !status.patched && status.basePath) {
443
726
  const result = await applyPatch();
444
727
  if (result.success) {
445
- console.log('[AutoAccept] Auto-patch applied on startup');
728
+ outputChannel.appendLine('[AutoPilot] Auto-patch applied on startup');
446
729
  }
447
730
  }
448
731
  })();
package/package.json CHANGED
@@ -2,7 +2,7 @@
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.1.0",
5
+ "version": "1.2.0",
6
6
  "publisher": "nguyen-hoang",
7
7
  "bin": {
8
8
  "antigravity-autopilot": "./cli.js"
@@ -33,19 +33,19 @@
33
33
  {
34
34
  "type": "webview",
35
35
  "id": "antigravityAutoAccept.panel",
36
- "name": "Auto-Accept Patcher"
36
+ "name": "AutoPilot"
37
37
  }
38
38
  ]
39
39
  },
40
40
  "commands": [
41
41
  {
42
42
  "command": "antigravityAutoAccept.applyPatch",
43
- "title": "Antigravity: Apply Auto-Accept Patch",
43
+ "title": "Antigravity: Apply AutoPilot Patch",
44
44
  "icon": "$(zap)"
45
45
  },
46
46
  {
47
47
  "command": "antigravityAutoAccept.revertPatch",
48
- "title": "Antigravity: Revert Auto-Accept Patch",
48
+ "title": "Antigravity: Revert AutoPilot Patch",
49
49
  "icon": "$(discard)"
50
50
  },
51
51
  {
@@ -55,7 +55,7 @@
55
55
  },
56
56
  {
57
57
  "command": "antigravityAutoAccept.openPanel",
58
- "title": "Antigravity: Open Patcher Panel"
58
+ "title": "Antigravity: Open AutoPilot Panel"
59
59
  }
60
60
  ],
61
61
  "keybindings": [
@@ -65,12 +65,39 @@
65
65
  }
66
66
  ],
67
67
  "configuration": {
68
- "title": "Antigravity Auto-Accept",
68
+ "title": "Antigravity AutoPilot",
69
69
  "properties": {
70
70
  "antigravityAutoAccept.applyOnStartup": {
71
71
  "type": "boolean",
72
72
  "default": false,
73
- "description": "Automatically apply the patch when this extension activates (on startup). Safe to enable — patch is idempotent."
73
+ "description": "Automatically apply the AutoPilot patch when VS Code starts. Safe to enable — patch is idempotent."
74
+ },
75
+ "antigravityAutoAccept.enabledOnStartup": {
76
+ "type": "boolean",
77
+ "default": true,
78
+ "description": "Keep AutoPilot enabled (active) when VS Code starts. When disabled, AutoPilot will be suspended until manually turned on from the sidebar."
79
+ },
80
+ "antigravityAutoAccept.dangerousCommandBlocking.enabled": {
81
+ "type": "boolean",
82
+ "default": true,
83
+ "description": "Block dangerous terminal commands (e.g. rm -rf /, format C:) before they execute. Fully customizable via the blocklist setting."
84
+ },
85
+ "antigravityAutoAccept.dangerousCommandBlocking.action": {
86
+ "type": "string",
87
+ "enum": ["block", "warn", "log"],
88
+ "enumDescriptions": [
89
+ "Block the command entirely and show an error notification.",
90
+ "Show a warning notification but still allow the command to proceed.",
91
+ "Silently log the command to the output channel and allow it to proceed."
92
+ ],
93
+ "default": "block",
94
+ "description": "Action to take when a dangerous command is detected."
95
+ },
96
+ "antigravityAutoAccept.dangerousCommandBlocking.customPatterns": {
97
+ "type": "array",
98
+ "items": { "type": "string" },
99
+ "default": [],
100
+ "description": "Additional regex patterns (JavaScript syntax) to treat as dangerous commands. Example: [\"^dd if=.*of=/dev/sd\", \"^shred\"]"
74
101
  }
75
102
  }
76
103
  }