Skip to content

Commit d37f212

Browse files
refactor(launcher): add ShellService, fix UI feedback patterns (#102)
* 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
1 parent f860ddc commit d37f212

8 files changed

Lines changed: 255 additions & 131 deletions

File tree

launcher/src/app/app.component.ts

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import { BrowserProfileService } from './shared/browser-profile.service';
3131
import { ConfirmDialogComponent } from './shared/confirm-dialog.component';
3232
import { UpdateService } from './shared/update.service';
3333
import { KernelService } from './shared/kernel.service';
34+
import { ShellService } from './shared/shell.service';
3435
import { StopPropagationDirective } from './shared/stop-propagation.directive';
3536
import { compressFolder, decompressZip, formatDateTime, formatProxyDisplay } from './utils';
3637
import { WarmupComponent } from './warmup.component';
@@ -65,6 +66,7 @@ export type NavItem = 'profiles' | 'proxies' | 'kernels';
6566
})
6667
export class AppComponent implements AfterViewInit {
6768
readonly #browserProfileService = inject(BrowserProfileService);
69+
readonly #shell = inject(ShellService);
6870
readonly browserLauncherService = inject(BrowserLauncherService);
6971
readonly kernelService = inject(KernelService);
7072
readonly updateService = inject(UpdateService);
@@ -196,7 +198,7 @@ export class AppComponent implements AfterViewInit {
196198
});
197199

198200
const browserProfilePath = await this.#browserProfileService.getBrowserProfilePath(browserProfile);
199-
await compressFolder(browserProfilePath, entry);
201+
await compressFolder(browserProfilePath, entry, this.#shell);
200202
}
201203

202204
async warmupProfile(browserProfile: BrowserProfile): Promise<void> {
@@ -219,7 +221,7 @@ export class AppComponent implements AfterViewInit {
219221
if (!entry?.[0]) return;
220222

221223
const browserProfilePath = await this.#browserProfileService.getBasePath();
222-
await decompressZip(entry[0], browserProfilePath);
224+
await decompressZip(entry[0], browserProfilePath, this.#shell);
223225
await this.refreshProfiles();
224226
}
225227

@@ -419,16 +421,20 @@ export class AppComponent implements AfterViewInit {
419421
.writeFile(filePath, csvContent)
420422
.then(() => {
421423
console.log(`CSV file saved successfully at ${filePath}`);
422-
Neutralino.os.showMessageBox('Success', 'CSV file has been successfully exported!');
424+
this.#snackBar.open('CSV file exported successfully', '', { duration: 3000 });
423425
})
424426
.catch((err) => {
425427
console.error('Failed to save CSV file:', err);
426-
Neutralino.os.showMessageBox('Error', 'Failed to export CSV file. Please check the logs!');
428+
this.#dialog.open(AlertDialogComponent, {
429+
data: { message: 'Failed to export CSV file. Please check the logs.' },
430+
});
427431
});
428432
})
429433
.catch((err) => {
430434
console.error('Failed to show save dialog:', err);
431-
Neutralino.os.showMessageBox('Error', 'Unable to display the save dialog. Please check the logs!');
435+
this.#dialog.open(AlertDialogComponent, {
436+
data: { message: 'Unable to display the save dialog. Please check the logs.' },
437+
});
432438
});
433439
}
434440

@@ -450,10 +456,9 @@ export class AppComponent implements AfterViewInit {
450456

451457
if (lines.length <= 1) {
452458
console.warn('CSV file is empty or only contains headers.');
453-
Neutralino.os.showMessageBox(
454-
'Warning',
455-
'The CSV file is empty or only contains headers. Please check the file content!'
456-
);
459+
this.#dialog.open(AlertDialogComponent, {
460+
data: { message: 'The CSV file is empty or only contains headers. Please check the file content.' },
461+
});
457462
return;
458463
}
459464

@@ -506,10 +511,12 @@ export class AppComponent implements AfterViewInit {
506511
resolvedProfiles.map((profile) => this.#browserProfileService.saveBrowserProfile(profile))
507512
);
508513
await this.refreshProfiles();
509-
Neutralino.os.showMessageBox('Success', 'CSV file has been successfully imported!');
514+
this.#snackBar.open('CSV file imported successfully', '', { duration: 3000 });
510515
} catch (error) {
511516
console.error('Failed to import profiles from CSV:', error);
512-
Neutralino.os.showMessageBox('Error', 'Failed to import CSV file. Please check the logs!');
517+
this.#dialog.open(AlertDialogComponent, {
518+
data: { message: 'Failed to import CSV file. Please check the logs.' },
519+
});
513520
}
514521
}
515522

launcher/src/app/shared/browser-launcher.service.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { extractMajorVersion } from '../data/bot-profile';
88
import { BrowserProfileStatus, getBrowserProfileStatusText, type BrowserProfile } from '../data/browser-profile';
99
import { SimpleCDP } from '../simple-cdp';
1010
import { createDirectoryIfNotExists, sleep } from '../utils';
11+
import { ShellService } from './shell.service';
1112
import { AlertDialogComponent } from './alert-dialog.component';
1213
import { BrowserProfileService } from './browser-profile.service';
1314
import { KernelService } from './kernel.service';
@@ -25,6 +26,7 @@ export interface RunningInfo {
2526
export class BrowserLauncherService {
2627
readonly #browserProfileService = inject(BrowserProfileService);
2728
readonly #kernelService = inject(KernelService);
29+
readonly #shell = inject(ShellService);
2830
readonly #runningStatuses = new Map<string, RunningInfo>();
2931
readonly #dialog = inject(MatDialog);
3032

@@ -456,7 +458,7 @@ export class BrowserLauncherService {
456458

457459
// If no known name found, try to find the first executable in Contents/MacOS
458460
try {
459-
const result = await Neutralino.os.execCommand(`ls "${appPath}/Contents/MacOS" 2>/dev/null | head -1`);
461+
const result = await this.#shell.run(`ls "${appPath}/Contents/MacOS" 2>/dev/null | head -1`);
460462
const mainExec = result.stdOut.trim();
461463
if (mainExec) {
462464
return await Neutralino.filesystem.getJoinedPath(appPath, 'Contents', 'MacOS', mainExec);

0 commit comments

Comments
 (0)