Skip to content

Commit 0fa7554

Browse files
feat(launcher): add clear-proxy confirm, kernel column, fix multi-build kernel updates (#107)
* refactor: rename BotBrowserConsole to BotBrowserLauncher * feat: add kernel management, proxy management, and setup scripts * fix: enable macOS native Edit menu for Cmd+C/V/X/A keyboard shortcuts * chore: upgrade Neutralino runtime to 6.4.0 * chore: remove Neutralino binaries from repo, download via neu update * fix: improve macOS clipboard support with JS fallback for Cmd+V paste * fix: expand macOS clipboard fallback to Cmd+C/X, auto-download Neutralino via postinstall * chore: upgrade Neutralino to 6.5.0 and simplify setup scripts * fix: fix PowerShell setup script ZIP download corruption * feat: enhance proxy workflow with IP check, quick change, and reusable input component * fix: correct checkbox color override and setup script stdin handling * feat: improve profile deletion with auto-stop, group WebRTC settings, prioritize custom exec path * feat: add port protection toggle, fix kernel auto-update cleanup and UI refresh * fix: detect kernel assets by file extension to support renamed Windows/Linux packages * fix: handle non-browser process exits, add crash detection, validate kernel executable paths * docs: add --bot-time-seed documentation, changelog 2026-03-03, value range and per-context cross-links * feat: add proxy save/check, improve table layout and kernel date display - Add "Save to proxy list" button in proxy input for reusing manually entered proxies - Add batch proxy connectivity check with IP/status display in proxy management - Sort proxy list by creation time (newest first) - Fix kernel date display: parse from asset filename, fix timezone offset bug - Simplify profile status column to icon-only (play/stop) with tooltips - Remove year from "Last Launch" date to prevent truncation - Widen window to 1080px, use fixed table layout to prevent horizontal scrollbar - Add column width constraints for profile and proxy tables * feat: redesign profile editor with left nav, advanced config modes, and new fields - Add left-side anchor navigation with scrollspy (IntersectionObserver) - Split Fingerprint into separate Noise and Rendering sections - Replace expansion panel with flat Advanced section layout - Add mode toggles for Executable (kernel/custom), Cookies and Bookmarks (file/input) - Add FPS dropdown (profile/real/custom number) instead of free-text input - Add Save IP button in proxy section for explicit IP saving - Add new fields: Time Seed, Proxy Bypass Regex, Cookies, Bookmarks, Custom Headers - Put username/password on same row in proxy input - Widen status column to prevent icon clipping * fix: replace Save IP with Save to proxy list, unify dropdown option labels * fix: include username and password in proxy duplicate detection Normalize both sides with `|| ''` to handle undefined values, and add password to the comparison so proxies with the same host:port but different credentials are treated as distinct entries. * fix: prevent editing running profiles and remove auto-save IP on proxy check - Block profile editing when browser is running/launching/stopping, show alert dialog instead of silently darkening the background - Remove onIpCheckResult that auto-saved checked IP to proxyIp field * feat(launcher): add automatic self-update with GitHub commit tracking Check for launcher updates on startup and every hour by comparing the local commit hash against the latest launcher-specific commit from GitHub API (path=launcher). When an update is found, silently download, rebuild, and prompt the user to restart. - Add UpdateService with periodic check, ZIP download, and rebuild - Show update status (checking/downloading/building/ready) in sidebar - Display current version (commit hash) at sidebar bottom - Update setup scripts to save commit hash after install/build - Fix sidebar footer layout to stay at bottom via flex container * feat(launcher): improve table UX, add new CLI flags, and proxy input enhancements - Separate row highlight (single click) from checkbox selection (batch ops) - Add double-click to edit on both profile and proxy tables - Add clear proxy button in profile editor - Combine proxy type/host/port into single row layout - Add --bot-stack-seed and --bot-network-info-override CLI flags - Update noise seed from float to integer range - Stack seed uses dropdown (profile/real/custom) like FPS - Rearrange noise settings: seed+scale+timeseed row, fps+stackseed row - Widen proxy type column in proxy list table * fix(launcher): auto-highlight new profiles/proxies and default noise text rects off - Return created profile/proxy ID from dialog so the table highlights the new row after creation, matching the edit-then-highlight behavior - Change botConfigNoiseTextRects default from on to off * feat(launcher): add new CLI flags, clear user data, and unsaved changes guard - Add --bot-config-orientation dropdown in Display & Input section with all orientation variants, noted as mobile profiles only - Add --bot-gpu-emulation toggle in Behavior section for auto GPU rendering backend selection - Add "Clear User Data" action in both profile edit dialog and profile list context menu to delete user-data-dir - Guard profile and proxy edit dialogs against accidental close: Escape, backdrop click, and Cancel now prompt when forms have unsaved changes * feat(launcher): reorganize flag categories, add CLI preview, new 2026-03-26 flags - Restructure sections: Proxy → Proxy & Network, move GPU Emulation to Rendering, History Injection to Identity (now supports count) - Add --bot-enable-variations-in-context and --bot-inject-random-history={number} - Add CLI command preview in Advanced tab + Copy CLI in context menu - Backward compat for old profiles with flags in behavior.* - Fix history count type coercion, add copy snackbar feedback * fix(launcher): prevent kernel update corruption and improve extraction - Use unique directory per install (tagName_githubAssetId) to avoid overwriting running kernel files during auto-update - Verify directory deletion before removing kernel record to prevent orphaned records when files are locked on Windows - Clean up outdated kernels at runtime when browser exits, not only at launcher startup - Check 7z availability early at init and before download, show warning banner when missing - Fix nested 7z/zip extraction: use recursive dir search and trim \r from Windows dir output so chrome.7z is found and cleaned up - Add open-folder button in kernel list to reveal install directory - deleteKernel throws if files are locked instead of silently failing * refactor(launcher): add ShellService, fix UI feedback patterns - Add ShellService using non-blocking spawnProcess with event buffering to fix IPC event loop blocking during downloads and extraction - Migrate all execCommand calls to ShellService (zero remaining) - Replace native showMessageBox with snackbar (success) or AlertDialog (errors/warnings) for consistent in-app feedback - Unify all clipboard copy actions with snackbar confirmation - Add Copy URL button in proxy input * feat(launcher): add clear-proxy confirm, kernel column, fix multi-build kernel updates - Proxy input: move Clear button left of Check IP and gate it behind a confirm dialog; enable Clear in the profile-list "Change Proxy" flow. - Profile list: add Kernel column (derived from bot profile's Chrome major version) with sorting. - Kernel service: when a release contains several same-platform builds (e.g. botbrowser_20260514_148.0.7778.168 and _20260518_...), pick the asset with the latest assetDate instead of the first one returned by GitHub, so auto-update actually downloads the freshest build.
1 parent 2ae6199 commit 0fa7554

8 files changed

Lines changed: 79 additions & 16 deletions

File tree

launcher/src/app/app.component.html

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,11 @@
283283
</td>
284284
</ng-container>
285285

286+
<ng-container matColumnDef="kernel">
287+
<th mat-header-cell *matHeaderCellDef mat-sort-header>Kernel</th>
288+
<td mat-cell *matCellDef="let element">{{ getKernelVersion(element) || '-' }}</td>
289+
</ng-container>
290+
286291
<ng-container matColumnDef="status">
287292
<th mat-header-cell *matHeaderCellDef></th>
288293
<td mat-cell *matCellDef="let element">

launcher/src/app/app.component.scss

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,11 @@ mat-toolbar {
184184
max-width: 130px;
185185
}
186186

187+
.mat-column-kernel {
188+
width: 80px;
189+
max-width: 80px;
190+
}
191+
187192
.status-running {
188193
color: #f44336;
189194
}

launcher/src/app/app.component.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { cloneDeep } from 'lodash-es';
2020
import { v4 as uuidv4 } from 'uuid';
2121
import { CloneBrowserProfileComponent } from './clone-browser-profile.component';
2222
import { AppName } from './const';
23+
import { extractMajorVersion } from './data/bot-profile';
2324
import { BrowserProfileStatus, type BrowserProfile } from './data/browser-profile';
2425
import { EditBrowserProfileComponent } from './edit-browser-profile.component';
2526
import { KernelManagementComponent } from './kernel-management/kernel-management.component';
@@ -78,7 +79,7 @@ export class AppComponent implements AfterViewInit {
7879
readonly formatDateTime = formatDateTime;
7980
readonly formatProxyDisplay = formatProxyDisplay;
8081
readonly BrowserProfileStatus = BrowserProfileStatus;
81-
readonly displayedColumns = ['select', 'status', 'name', 'group', 'proxy', 'lastUsedAt'];
82+
readonly displayedColumns = ['select', 'status', 'name', 'group', 'proxy', 'kernel', 'lastUsedAt'];
8283
readonly dataSource = new MatTableDataSource<BrowserProfile>([]);
8384
readonly selection = new SelectionModel<BrowserProfile>(true, []);
8485

@@ -100,6 +101,8 @@ export class AppComponent implements AfterViewInit {
100101
return data.basicInfo.groupName ?? '';
101102
case 'status':
102103
return this.browserLauncherService.getRunningStatus(data);
104+
case 'kernel':
105+
return this.getKernelVersion(data) ?? 0;
103106
case 'lastUsedAt':
104107
return data.lastUsedAt ?? 0;
105108
default:
@@ -108,6 +111,19 @@ export class AppComponent implements AfterViewInit {
108111
};
109112
}
110113

114+
getKernelVersion(profile: BrowserProfile): number | null {
115+
const content = profile.botProfileInfo?.content;
116+
if (!content) return null;
117+
try {
118+
const info = JSON.parse(content);
119+
const userAgent = info.userAgent || '';
120+
const version = info.version || '';
121+
return extractMajorVersion(userAgent) ?? extractMajorVersion(version);
122+
} catch {
123+
return null;
124+
}
125+
}
126+
111127
newProfile(): void {
112128
this.#dialog
113129
.open(EditBrowserProfileComponent, {

launcher/src/app/data/kernel.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,19 @@ export function getPlatformFromAssetName(name: string): KernelPlatform | null {
8888
return null;
8989
}
9090

91+
// Pick the latest asset for a given platform within a release.
92+
// Among assets matching the platform, prefers the one with the highest assetDate (YYYYMMDD).
93+
export function getLatestAssetForPlatform(
94+
assets: KernelAsset[],
95+
platform: KernelPlatform
96+
): KernelAsset | undefined {
97+
const candidates = assets.filter((a) => a.platform === platform);
98+
if (candidates.length === 0) return undefined;
99+
return candidates.reduce((latest, a) =>
100+
(a.assetDate || '') > (latest.assetDate || '') ? a : latest
101+
);
102+
}
103+
91104
export function getCurrentPlatform(osName: string, arch?: string): KernelPlatform {
92105
const osLower = osName.toLowerCase();
93106

launcher/src/app/kernel-management/kernel-management.component.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { MatTableDataSource, MatTableModule } from '@angular/material/table';
88
import { MatToolbarModule } from '@angular/material/toolbar';
99
import { MatTooltipModule } from '@angular/material/tooltip';
1010
import * as Neutralino from '@neutralinojs/lib';
11-
import type { InstalledKernel, KernelRelease } from '../data/kernel';
11+
import { getLatestAssetForPlatform, type InstalledKernel, type KernelRelease } from '../data/kernel';
1212
import { AlertDialogComponent } from '../shared/alert-dialog.component';
1313
import { ConfirmDialogComponent } from '../shared/confirm-dialog.component';
1414
import { KernelService } from '../shared/kernel.service';
@@ -151,7 +151,7 @@ export class KernelManagementComponent implements OnInit {
151151

152152
getReleaseAssetDate(release: KernelRelease): string | null {
153153
const platform = this.kernelService.getCurrentPlatform();
154-
const asset = release.assets.find((a) => a.platform === platform);
154+
const asset = getLatestAssetForPlatform(release.assets, platform);
155155
return this.formatAssetDate(asset?.assetDate);
156156
}
157157

launcher/src/app/quick-proxy-change.component.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,10 @@ import { ProxyService } from './shared/proxy.service';
3939
[showQuickParse]="true"
4040
[showCheckButton]="true"
4141
[showSaveButton]="!selectedProxyId"
42+
[showClearButton]="true"
4243
(valueChange)="onProxyValueChange($event)"
4344
(saveToList)="onSaveProxyToList($event)"
45+
(clearProxy)="onClearProxy()"
4446
/>
4547
</mat-dialog-content>
4648
<mat-dialog-actions align="end">
@@ -94,6 +96,11 @@ export class QuickProxyChangeComponent implements OnInit {
9496
this.selectedProxyId = '';
9597
}
9698

99+
onClearProxy(): void {
100+
this.proxyValue = null;
101+
this.selectedProxyId = '';
102+
}
103+
97104
async onSaveProxyToList(proxy: ParsedProxy): Promise<void> {
98105
const duplicate = this.proxies.find(
99106
(p) => p.host === proxy.host && p.port === proxy.port && (p.username || '') === (proxy.username || '') && (p.password || '') === (proxy.password || '')

launcher/src/app/shared/kernel.service.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
compareVersions,
88
getAssetDate,
99
getCurrentPlatform,
10+
getLatestAssetForPlatform,
1011
getPlatformFromAssetName,
1112
parseVersion,
1213
type DownloadProgress,
@@ -390,8 +391,8 @@ export class KernelService {
390391
const installedKernel = await this.getInstalledKernelByMajorVersion(release.majorVersion);
391392

392393
if (installedKernel) {
393-
// Get the asset for current platform
394-
const latestAsset = release.assets.find((a) => a.platform === this.#currentPlatform);
394+
// Get the latest asset for current platform (highest assetDate within the release)
395+
const latestAsset = getLatestAssetForPlatform(release.assets, this.#currentPlatform);
395396
if (!latestAsset) continue;
396397

397398
// Check if there's a newer version OR newer asset date
@@ -471,7 +472,8 @@ export class KernelService {
471472
throw new Error(`${release.tagName} is already being downloaded.`);
472473
}
473474

474-
const asset = release.assets.find((a) => a.platform === this.#currentPlatform);
475+
// Pick the latest asset by assetDate so we install the freshest build within the release
476+
const asset = getLatestAssetForPlatform(release.assets, this.#currentPlatform);
475477
if (!asset) {
476478
throw new Error(`No binary available for platform: ${this.#currentPlatform}`);
477479
}

launcher/src/app/shared/proxy-input.component.ts

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { CommonModule } from '@angular/common';
22
import { Component, EventEmitter, inject, Input, NgZone, Output } from '@angular/core';
33
import { FormBuilder, FormsModule, ReactiveFormsModule } from '@angular/forms';
44
import { MatButtonModule } from '@angular/material/button';
5+
import { MatDialog } from '@angular/material/dialog';
56
import { MatFormFieldModule } from '@angular/material/form-field';
67
import { MatIconModule } from '@angular/material/icon';
78
import { MatInputModule } from '@angular/material/input';
@@ -12,6 +13,7 @@ import { MatTooltipModule } from '@angular/material/tooltip';
1213
import { ProxyTypes } from '../data/proxy';
1314
import type { ProxyType } from '../data/proxy';
1415
import * as Neutralino from '@neutralinojs/lib';
16+
import { ConfirmDialogComponent } from './confirm-dialog.component';
1517
import { ProxyCheckService, type ProxyCheckResult } from './proxy-check.service';
1618
import { ProxyParserService, type ParsedProxy } from './proxy-parser.service';
1719

@@ -93,6 +95,11 @@ import { ProxyParserService, type ParsedProxy } from './proxy-parser.service';
9395
@if (showCheckButton || showSaveButton || showClearButton) {
9496
<div class="check-ip-section">
9597
<div class="button-row">
98+
@if (showClearButton && getValue()) {
99+
<button mat-stroked-button color="warn" (click)="onClearProxy()">
100+
Clear proxy
101+
</button>
102+
}
96103
@if (showCheckButton) {
97104
<button mat-stroked-button (click)="onCheckIp()" [disabled]="checking || !getValue()">
98105
@if (checking) {
@@ -108,11 +115,6 @@ import { ProxyParserService, type ParsedProxy } from './proxy-parser.service';
108115
Save to proxy list
109116
</button>
110117
}
111-
@if (showClearButton && getValue()) {
112-
<button mat-stroked-button color="warn" (click)="onClearProxy()">
113-
Clear proxy
114-
</button>
115-
}
116118
@if (getValue()) {
117119
<button mat-stroked-button (click)="onCopyProxyUrl()">
118120
Copy URL
@@ -307,6 +309,7 @@ export class ProxyInputComponent {
307309
readonly #formBuilder = inject(FormBuilder);
308310
readonly #snackBar = inject(MatSnackBar);
309311
readonly #ngZone = inject(NgZone);
312+
readonly #dialog = inject(MatDialog);
310313

311314
readonly proxyTypes = ProxyTypes;
312315

@@ -436,11 +439,23 @@ export class ProxyInputComponent {
436439
}
437440

438441
onClearProxy(): void {
439-
this.formGroup.reset({ type: 'http', host: '', port: 8080, username: '', password: '' });
440-
this.checkResult = null;
441-
this.checkError = '';
442-
this.#emitValue();
443-
this.clearProxy.emit();
442+
this.#dialog
443+
.open(ConfirmDialogComponent, {
444+
data: {
445+
message: 'Clear the proxy?',
446+
okButtonText: 'Clear',
447+
defaultCancel: true,
448+
},
449+
})
450+
.afterClosed()
451+
.subscribe((confirmed: boolean) => {
452+
if (!confirmed) return;
453+
this.formGroup.reset({ type: 'http', host: '', port: 8080, username: '', password: '' });
454+
this.checkResult = null;
455+
this.checkError = '';
456+
this.#emitValue();
457+
this.clearProxy.emit();
458+
});
444459
}
445460

446461
getValue(): ParsedProxy | null {

0 commit comments

Comments
 (0)