From 476ee462c9208514ee8fbb2575b1c0fe7b00a31e Mon Sep 17 00:00:00 2001 From: Brian Dupras Date: Thu, 22 Jan 2026 13:01:38 -0700 Subject: [PATCH 1/2] adds findWindowByIdOrName and wires up `-w` args --- README.md | 30 ++++++++++++++----- chrome-cli/App.m | 74 +++++++++++++++++++++++++++++++++++++---------- chrome-cli/main.m | 16 +++++----- 3 files changed, 88 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index 3d92878..3f08088 100644 --- a/README.md +++ b/README.md @@ -63,20 +63,20 @@ More details [here](https://www.chromium.org/developers/applescript). Thanks to chrome-cli help (Print help) chrome-cli list windows (List all windows) chrome-cli list tabs (List all tabs) - chrome-cli list tabs -w (List tabs in specific window) + chrome-cli list tabs -w (List tabs in specific window) chrome-cli list links (List all tabs' link) - chrome-cli list links -w (List tabs' link in specific window) + chrome-cli list links -w (List tabs' link in specific window) chrome-cli info (Print info for active tab) chrome-cli info -t (Print info for specific tab) chrome-cli open (Open url in new tab) chrome-cli open -n (Open url in new window) chrome-cli open -i (Open url in new incognito window) chrome-cli open -t (Open url in specific tab) - chrome-cli open -w (Open url in new tab in specific window) + chrome-cli open -w (Open url in new tab in specific window) chrome-cli close (Close active tab) chrome-cli close -w (Close active window) chrome-cli close -t (Close specific tab) - chrome-cli close -w (Close specific window) + chrome-cli close -w (Close specific window) chrome-cli reload (Reload active tab) chrome-cli reload -t (Reload specific tab) chrome-cli back (Navigate back in active tab) @@ -91,13 +91,13 @@ More details [here](https://www.chromium.org/developers/applescript). Thanks to chrome-cli presentation -t (Enter presentation mode with a specific tab) chrome-cli presentation exit (Exit presentation mode) chrome-cli size (Print size of active window) - chrome-cli size -w (Print size of specific window) + chrome-cli size -w (Print size of specific window) chrome-cli size (Set size of active window) - chrome-cli size -w (Set size of specific window) + chrome-cli size -w (Set size of specific window) chrome-cli position (Print position of active window) - chrome-cli position -w (Print position of specific window) + chrome-cli position -w (Print position of specific window) chrome-cli position (Set position of active window) - chrome-cli position -w (Set position of specific window) + chrome-cli position -w (Set position of specific window) chrome-cli source (Print source from active tab) chrome-cli source -t (Print source from specific tab) chrome-cli execute (Execute javascript in active tab) @@ -125,6 +125,20 @@ $ OUTPUT_FORMAT=json chrome-cli list tabs } ``` +#### Window selection by name + +The `-w` flag accepts either a window ID or a window name. When a non-numeric value is provided, it matches windows whose title starts with the given string (case-insensitive). If multiple windows match, the one with the lowest window ID is selected. + +```bash +# By window ID +chrome-cli list tabs -w 1869578514 + +# By window name (case-insensitive prefix match) +chrome-cli list tabs -w Gmail +chrome-cli list tabs -w "Hacker News" +chrome-cli close -w GitHub +``` + ## Examples ###### List tabs diff --git a/chrome-cli/App.m b/chrome-cli/App.m index 52202f1..116b65e 100644 --- a/chrome-cli/App.m +++ b/chrome-cli/App.m @@ -185,8 +185,8 @@ - (void)listTabsWithLink:(Arguments *)args { } - (void)listTabsInWindow:(Arguments *)args { - NSInteger windowId = [args asInteger:@"id"]; - chromeWindow *window = [self findWindow:windowId]; + NSString *windowArg = [args asString:@"id|name"]; + chromeWindow *window = [self findWindowByIdOrName:windowArg]; if (!window) { return; @@ -219,8 +219,8 @@ - (void)listTabsInWindow:(Arguments *)args { } - (void)listTabsLinksInWindow:(Arguments *)args { - NSInteger windowId = [args asInteger:@"id"]; - chromeWindow *window = [self findWindow:windowId]; + NSString *windowArg = [args asString:@"id|name"]; + chromeWindow *window = [self findWindowByIdOrName:windowArg]; if (!window) { return; @@ -309,11 +309,11 @@ - (void)openUrlInTab:(Arguments *)args { } - (void)openUrlInWindow:(Arguments *)args { - NSInteger windowId = [args asInteger:@"id"]; + NSString *windowArg = [args asString:@"id|name"]; NSString *url = [args asString:@"url"]; chromeTab *tab = [[[self.chrome classForScriptingClass:@"tab"] alloc] init]; - chromeWindow *window = [self findWindow:windowId]; + chromeWindow *window = [self findWindowByIdOrName:windowArg]; if (!window) { return; @@ -368,8 +368,8 @@ - (void)closeActiveWindow:(Arguments *)args { } - (void)closeWindow:(Arguments *)args { - NSInteger windowId = [args asInteger:@"id"]; - chromeWindow *window = [self findWindow:windowId]; + NSString *windowArg = [args asString:@"id|name"]; + chromeWindow *window = [self findWindowByIdOrName:windowArg]; if (window) { [window close]; @@ -498,8 +498,8 @@ - (void)printActiveWindowSize:(Arguments *)args { } - (void)printWindowSize:(Arguments *)args { - NSInteger windowId = [args asInteger:@"id"]; - chromeWindow *window = [self findWindow:windowId]; + NSString *windowArg = [args asString:@"id|name"]; + chromeWindow *window = [self findWindowByIdOrName:windowArg]; CGSize size = window.bounds.size; if (self->outputFormat == kOutputFormatJSON) { NSDictionary *output = @{ @@ -522,11 +522,11 @@ - (void)setActiveWindowSize:(Arguments *)args { } - (void)setWindowSize:(Arguments *)args { - NSInteger windowId = [args asInteger:@"id"]; + NSString *windowArg = [args asString:@"id|name"]; float width = [args asFloat:@"width"]; float height = [args asFloat:@"height"]; - chromeWindow *window = [self findWindow:windowId]; + chromeWindow *window = [self findWindowByIdOrName:windowArg]; CGPoint origin = window.bounds.origin; window.bounds = NSMakeRect(origin.x, origin.y, width, height); } @@ -547,8 +547,8 @@ - (void)printActiveWindowPosition:(Arguments *)args { } - (void)printWindowPosition:(Arguments *)args { - NSInteger windowId = [args asInteger:@"id"]; - chromeWindow *window = [self findWindow:windowId]; + NSString *windowArg = [args asString:@"id|name"]; + chromeWindow *window = [self findWindowByIdOrName:windowArg]; CGPoint origin = window.bounds.origin; if (self->outputFormat == kOutputFormatJSON) { @@ -572,11 +572,11 @@ - (void)setActiveWindowPosition:(Arguments *)args { } - (void)setWindowPosition:(Arguments *)args { - NSInteger windowId = [args asInteger:@"id"]; + NSString *windowArg = [args asString:@"id|name"]; float x = [args asFloat:@"x"]; float y = [args asFloat:@"y"]; - chromeWindow *window = [self findWindow:windowId]; + chromeWindow *window = [self findWindowByIdOrName:windowArg]; CGSize size = window.bounds.size; window.bounds = NSMakeRect(x, y, size.width, size.height); } @@ -806,6 +806,48 @@ - (chromeWindow *)findWindow:(NSInteger)windowId { return nil; } +- (chromeWindow *)findWindowByName:(NSString *)name { + chromeWindow *bestMatch = nil; + NSInteger bestMatchId = NSIntegerMax; + NSString *lowerName = [name lowercaseString]; + + for (chromeWindow *window in self.chrome.windows) { + NSString *windowName = [window.name lowercaseString]; + + // Check if window name starts with search string + if ([windowName hasPrefix:lowerName]) { + NSInteger windowId = [window.id integerValue]; + + // Prefer lower window ID for ties (all matches have same prefix length) + if (bestMatch == nil || windowId < bestMatchId) { + bestMatch = window; + bestMatchId = windowId; + } + } + } + + return bestMatch; +} + +- (chromeWindow *)findWindowByIdOrName:(NSString *)identifier { + // Check if identifier is purely numeric + NSCharacterSet *nonDigits = [[NSCharacterSet decimalDigitCharacterSet] invertedSet]; + BOOL isNumeric = ([identifier rangeOfCharacterFromSet:nonDigits].location == NSNotFound + && identifier.length > 0); + + if (isNumeric) { + // Try by ID first + chromeWindow *window = [self findWindow:[identifier integerValue]]; + if (window) { + return window; + } + // Fall back to name search (for windows with all-numeric titles like "2024") + return [self findWindowByName:identifier]; + } else { + return [self findWindowByName:identifier]; + } +} + - (chromeTab *)activeTab { return [self activeWindow].activeTab; } diff --git a/chrome-cli/main.m b/chrome-cli/main.m index 4dfe618..c268f82 100644 --- a/chrome-cli/main.m +++ b/chrome-cli/main.m @@ -38,9 +38,9 @@ int main(int argc, const char * argv[]) [argonaut add:@"list windows" target:app action:@selector(listWindows:) description:@"List all windows"]; [argonaut add:@"list tabs" target:app action:@selector(listTabs:) description:@"List all tabs"]; - [argonaut add:@"list tabs -w " target:app action:@selector(listTabsInWindow:) description:@"List tabs in specific window"]; + [argonaut add:@"list tabs -w " target:app action:@selector(listTabsInWindow:) description:@"List tabs in specific window"]; [argonaut add:@"list links" target:app action:@selector(listTabsLinks:) description:@"List all tabs' link"]; - [argonaut add:@"list links -w " target:app action:@selector(listTabsLinksInWindow:) description:@"List tabs' link in specific window"]; + [argonaut add:@"list links -w " target:app action:@selector(listTabsLinksInWindow:) description:@"List tabs' link in specific window"]; [argonaut add:@"list tablinks" target:app action:@selector(listTabsWithLink:) description:@"List tabs' with the link"]; @@ -51,12 +51,12 @@ int main(int argc, const char * argv[]) [argonaut add:@"open -n" target:app action:@selector(openUrlInNewWindow:) description:@"Open url in new window"]; [argonaut add:@"open -i" target:app action:@selector(openUrlInNewIncognitoWindow:) description:@"Open url in new incognito window"]; [argonaut add:@"open -t " target:app action:@selector(openUrlInTab:) description:@"Open url in specific tab"]; - [argonaut add:@"open -w " target:app action:@selector(openUrlInWindow:) description:@"Open url in new tab in specific window"]; + [argonaut add:@"open -w " target:app action:@selector(openUrlInWindow:) description:@"Open url in new tab in specific window"]; [argonaut add:@"close" target:app action:@selector(closeActiveTab:) description:@"Close active tab"]; [argonaut add:@"close -w" target:app action:@selector(closeActiveWindow:) description:@"Close active window"]; [argonaut add:@"close -t " target:app action:@selector(closeTab:) description:@"Close specific tab"]; - [argonaut add:@"close -w " target:app action:@selector(closeWindow:) description:@"Close specific window"]; + [argonaut add:@"close -w " target:app action:@selector(closeWindow:) description:@"Close specific window"]; [argonaut add:@"reload" target:app action:@selector(reloadActiveTab:) description:@"Reload active tab"]; [argonaut add:@"reload -t " target:app action:@selector(reloadTab:) description:@"Reload specific tab"]; @@ -71,14 +71,14 @@ int main(int argc, const char * argv[]) [argonaut add:@"activate -t --focus" target:app action:@selector(activateTabAndFocus:) description:@"Activate tab and bring its window to front"]; [argonaut add:@"size" target:app action:@selector(printActiveWindowSize:) description:@"Print size of active window"]; - [argonaut add:@"size -w " target:app action:@selector(printWindowSize:) description:@"Print size of specific window"]; + [argonaut add:@"size -w " target:app action:@selector(printWindowSize:) description:@"Print size of specific window"]; [argonaut add:@"size " target:app action:@selector(setActiveWindowSize:) description:@"Set size of active window"]; - [argonaut add:@"size -w " target:app action:@selector(setWindowSize:) description:@"Set size of specific window"]; + [argonaut add:@"size -w " target:app action:@selector(setWindowSize:) description:@"Set size of specific window"]; [argonaut add:@"position" target:app action:@selector(printActiveWindowPosition:) description:@"Print position of active window"]; - [argonaut add:@"position -w " target:app action:@selector(printWindowPosition:) description:@"Print position of specific window"]; + [argonaut add:@"position -w " target:app action:@selector(printWindowPosition:) description:@"Print position of specific window"]; [argonaut add:@"position " target:app action:@selector(setActiveWindowPosition:) description:@"Set position of active window"]; - [argonaut add:@"position -w " target:app action:@selector(setWindowPosition:) description:@"Set position of specific window"]; + [argonaut add:@"position -w " target:app action:@selector(setWindowPosition:) description:@"Set position of specific window"]; [argonaut add:@"source" target:app action:@selector(printSourceFromActiveTab:) description:@"Print source from active tab"]; [argonaut add:@"source -t " target:app action:@selector(printSourceFromTab:) description:@"Print source from specific tab"]; From bfa95f5784637c1c3317c24a18fa5a79c5d4005b Mon Sep 17 00:00:00 2001 From: Brian Dupras Date: Thu, 22 Jan 2026 13:11:51 -0700 Subject: [PATCH 2/2] adds script for chrome-beta-cli --- README.md | 2 ++ scripts/chrome-beta-cli | 4 ++++ 2 files changed, 6 insertions(+) create mode 100755 scripts/chrome-beta-cli diff --git a/README.md b/README.md index 3f08088..c9bfd96 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ It is a native binary that uses the Scripting Bridge to communicate with Chrome. chrome-cli has been tested with the following browsers: - Chrome +- Chrome Beta - Chrome Canary - Chromium - Brave @@ -44,6 +45,7 @@ brew install chrome-cli This will install: - chrome-cli +- chrome-beta-cli - chrome-canary-cli - chromium-cli - brave-cli diff --git a/scripts/chrome-beta-cli b/scripts/chrome-beta-cli new file mode 100755 index 0000000..8a613a2 --- /dev/null +++ b/scripts/chrome-beta-cli @@ -0,0 +1,4 @@ +#!/bin/bash + +export CHROME_BUNDLE_IDENTIFIER="com.google.Chrome.beta" +chrome-cli "$@"