Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions extension/src/background.ts
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,8 @@ async function handleCommand(cmd: Command): Promise<Result> {
return await handleSessions(cmd);
case 'set-file-input':
return await handleSetFileInput(cmd, workspace);
case 'focus-window':
return await handleFocusWindow(cmd, workspace);
default:
return { id: cmd.id, ok: false, error: `Unknown action: ${cmd.action}` };
}
Expand Down Expand Up @@ -692,6 +694,24 @@ async function handleCloseWindow(cmd: Command, workspace: string): Promise<Resul
return { id: cmd.id, ok: true, data: { closed: true } };
}

async function handleFocusWindow(cmd: Command, workspace: string): Promise<Result> {
const session = automationSessions.get(workspace);
if (session) {
try {
await chrome.windows.update(session.windowId, { focused: true });
return { id: cmd.id, ok: true, data: { focused: true } };
} catch {
// Window was closed externally — clean up and fall through to create
if (session.idleTimer) clearTimeout(session.idleTimer);
automationSessions.delete(workspace);
}
}
// No live session — create one and bring it to the foreground
const windowId = await getAutomationWindow(workspace);
await chrome.windows.update(windowId, { focused: true });
return { id: cmd.id, ok: true, data: { focused: true } };
}

async function handleSetFileInput(cmd: Command, workspace: string): Promise<Result> {
if (!cmd.files || !Array.isArray(cmd.files) || cmd.files.length === 0) {
return { id: cmd.id, ok: false, error: 'Missing or empty files array' };
Expand Down
2 changes: 1 addition & 1 deletion extension/src/protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* Everything else is just JS code sent via 'exec'.
*/

export type Action = 'exec' | 'navigate' | 'tabs' | 'cookies' | 'screenshot' | 'close-window' | 'sessions' | 'set-file-input' | 'cdp';
export type Action = 'exec' | 'navigate' | 'tabs' | 'cookies' | 'screenshot' | 'close-window' | 'sessions' | 'set-file-input' | 'cdp' | 'focus-window';

export interface Command {
/** Unique request ID */
Expand Down
2 changes: 1 addition & 1 deletion src/browser/daemon-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ function generateId(): string {

export interface DaemonCommand {
id: string;
action: 'exec' | 'navigate' | 'tabs' | 'cookies' | 'screenshot' | 'close-window' | 'sessions' | 'set-file-input' | 'cdp';
action: 'exec' | 'navigate' | 'tabs' | 'cookies' | 'screenshot' | 'close-window' | 'sessions' | 'set-file-input' | 'cdp' | 'focus-window';
tabId?: number;
code?: string;
workspace?: string;
Expand Down
5 changes: 5 additions & 0 deletions src/browser/page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,11 @@ export class Page extends BasePage {
}
}

/** Bring the automation window to the foreground. Creates one if none exists. */
async focusWindow(): Promise<void> {
await sendCommand('focus-window', { ...this._wsOpt() });
}

async tabs(): Promise<unknown[]> {
const result = await sendCommand('tabs', { op: 'list', ...this._wsOpt() });
return Array.isArray(result) ? result : [];
Expand Down
23 changes: 23 additions & 0 deletions src/commanderAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import { Command } from 'commander';
import chalk from 'chalk';
import { type CliCommand, fullName, getRegistry } from './registry.js';
import { sendCommand } from './browser/daemon-client.js';
import { formatRegistryHelpText } from './serialization.js';
import { render as renderOutput } from './output.js';
import { executeCommand } from './execution.js';
Expand Down Expand Up @@ -302,6 +303,8 @@ export function registerAllCommands(
siteGroups: Map<string, Command>,
): void {
const seen = new Set<CliCommand>();
const browserSites = new Set<string>();

for (const [, cmd] of getRegistry()) {
if (seen.has(cmd)) continue;
seen.add(cmd);
Expand All @@ -311,5 +314,25 @@ export function registerAllCommands(
siteGroups.set(cmd.site, siteCmd);
}
registerCommandToProgram(siteCmd, cmd);
if (cmd.browser) browserSites.add(cmd.site);
}

// Inject `open` subcommand for every site that has at least one browser command.
for (const site of browserSites) {
const siteCmd = siteGroups.get(site);
if (!siteCmd) continue;
if (siteCmd.commands.some((c: Command) => c.name() === 'open')) continue;
siteCmd
.command('open')
.description('Bring the automation window to the foreground')
.action(async () => {
try {
await sendCommand('focus-window', { workspace: `site:${site}` });
console.log(chalk.green(`✓ ${site} automation window is now in the foreground.`));
} catch (err) {
console.error(chalk.red(`Failed to focus window: ${err instanceof Error ? err.message : err}`));
process.exitCode = EXIT_CODES.GENERIC_ERROR;
}
});
}
}
2 changes: 2 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ export interface IPage {
*/
setFileInput?(files: string[], selector?: string): Promise<void>;
closeWindow?(): Promise<void>;
/** Bring the automation window to the foreground. Creates one if none exists. */
focusWindow?(): Promise<void>;
/** Returns the current page URL, or null if unavailable. */
getCurrentUrl?(): Promise<string | null>;
/** Returns the active tab ID, or undefined if not yet resolved. */
Expand Down