Skip to content

Commit 4122d4d

Browse files
author
wangjingjing
committed
feat: add focus-window action and opencli <site> open
command - Add focus-window protocol action (extension/src/protocol.ts, daemon-client.ts) - Implement handleFocusWindow in Chrome extension background script - Add Page.focusWindow() method and IPage interface declaration - Auto-inject 'open' subcommand for all browser-capable sites in registerAllCommands
1 parent 2d005d1 commit 4122d4d

File tree

6 files changed

+52
-2
lines changed

6 files changed

+52
-2
lines changed

extension/src/background.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,8 @@ async function handleCommand(cmd: Command): Promise<Result> {
279279
return await handleSessions(cmd);
280280
case 'set-file-input':
281281
return await handleSetFileInput(cmd, workspace);
282+
case 'focus-window':
283+
return await handleFocusWindow(cmd, workspace);
282284
default:
283285
return { id: cmd.id, ok: false, error: `Unknown action: ${cmd.action}` };
284286
}
@@ -692,6 +694,24 @@ async function handleCloseWindow(cmd: Command, workspace: string): Promise<Resul
692694
return { id: cmd.id, ok: true, data: { closed: true } };
693695
}
694696

697+
async function handleFocusWindow(cmd: Command, workspace: string): Promise<Result> {
698+
const session = automationSessions.get(workspace);
699+
if (session) {
700+
try {
701+
await chrome.windows.update(session.windowId, { focused: true });
702+
return { id: cmd.id, ok: true, data: { focused: true } };
703+
} catch {
704+
// Window was closed externally — clean up and fall through to create
705+
if (session.idleTimer) clearTimeout(session.idleTimer);
706+
automationSessions.delete(workspace);
707+
}
708+
}
709+
// No live session — create one and bring it to the foreground
710+
const windowId = await getAutomationWindow(workspace);
711+
await chrome.windows.update(windowId, { focused: true });
712+
return { id: cmd.id, ok: true, data: { focused: true } };
713+
}
714+
695715
async function handleSetFileInput(cmd: Command, workspace: string): Promise<Result> {
696716
if (!cmd.files || !Array.isArray(cmd.files) || cmd.files.length === 0) {
697717
return { id: cmd.id, ok: false, error: 'Missing or empty files array' };

extension/src/protocol.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
* Everything else is just JS code sent via 'exec'.
66
*/
77

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

1010
export interface Command {
1111
/** Unique request ID */

src/browser/daemon-client.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ function generateId(): string {
2121

2222
export interface DaemonCommand {
2323
id: string;
24-
action: 'exec' | 'navigate' | 'tabs' | 'cookies' | 'screenshot' | 'close-window' | 'sessions' | 'set-file-input' | 'cdp';
24+
action: 'exec' | 'navigate' | 'tabs' | 'cookies' | 'screenshot' | 'close-window' | 'sessions' | 'set-file-input' | 'cdp' | 'focus-window';
2525
tabId?: number;
2626
code?: string;
2727
workspace?: string;

src/browser/page.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,11 @@ export class Page extends BasePage {
123123
}
124124
}
125125

126+
/** Bring the automation window to the foreground. Creates one if none exists. */
127+
async focusWindow(): Promise<void> {
128+
await sendCommand('focus-window', { ...this._wsOpt() });
129+
}
130+
126131
async tabs(): Promise<unknown[]> {
127132
const result = await sendCommand('tabs', { op: 'list', ...this._wsOpt() });
128133
return Array.isArray(result) ? result : [];

src/commanderAdapter.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import { Command } from 'commander';
1414
import chalk from 'chalk';
1515
import { type CliCommand, fullName, getRegistry } from './registry.js';
16+
import { sendCommand } from './browser/daemon-client.js';
1617
import { formatRegistryHelpText } from './serialization.js';
1718
import { render as renderOutput } from './output.js';
1819
import { executeCommand } from './execution.js';
@@ -302,6 +303,8 @@ export function registerAllCommands(
302303
siteGroups: Map<string, Command>,
303304
): void {
304305
const seen = new Set<CliCommand>();
306+
const browserSites = new Set<string>();
307+
305308
for (const [, cmd] of getRegistry()) {
306309
if (seen.has(cmd)) continue;
307310
seen.add(cmd);
@@ -311,5 +314,25 @@ export function registerAllCommands(
311314
siteGroups.set(cmd.site, siteCmd);
312315
}
313316
registerCommandToProgram(siteCmd, cmd);
317+
if (cmd.browser) browserSites.add(cmd.site);
318+
}
319+
320+
// Inject `open` subcommand for every site that has at least one browser command.
321+
for (const site of browserSites) {
322+
const siteCmd = siteGroups.get(site);
323+
if (!siteCmd) continue;
324+
if (siteCmd.commands.some((c: Command) => c.name() === 'open')) continue;
325+
siteCmd
326+
.command('open')
327+
.description('Bring the automation window to the foreground')
328+
.action(async () => {
329+
try {
330+
await sendCommand('focus-window', { workspace: `site:${site}` });
331+
console.log(chalk.green(`✓ ${site} automation window is now in the foreground.`));
332+
} catch (err) {
333+
console.error(chalk.red(`Failed to focus window: ${err instanceof Error ? err.message : err}`));
334+
process.exitCode = EXIT_CODES.GENERIC_ERROR;
335+
}
336+
});
314337
}
315338
}

src/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,8 @@ export interface IPage {
7171
*/
7272
setFileInput?(files: string[], selector?: string): Promise<void>;
7373
closeWindow?(): Promise<void>;
74+
/** Bring the automation window to the foreground. Creates one if none exists. */
75+
focusWindow?(): Promise<void>;
7476
/** Returns the current page URL, or null if unavailable. */
7577
getCurrentUrl?(): Promise<string | null>;
7678
/** Returns the active tab ID, or undefined if not yet resolved. */

0 commit comments

Comments
 (0)