Photo by David Wirzba / Unsplash

Taming 2000 Safari Tabs

Articles Sep 10, 2024 (Sep 11, 2024) Loading...

My machine is :slow.

Well, it's fast, but the experience of using it feels hesitant. Perhaps that's because, with three Apple devices sharing tabs, and an ill-disciplined approach to looking stuff up, I have :countless tabs open.

I wondered whether Claude could help. After a couple of queries to ask how to manage my tabs and to see if it knew a tool I didn't (reader, it did), I asked this....

I'd like to create a custom automator script to extract details of my tabs from safari. I guess that the details I want are the title, URL, parent window details and any date information. Can you help with that?

Claude gave me code, instructions and alternatives. I followed its instructions and ran the supplied AppleScript in Automator. The output let me see that the numbers were credible: there were thousands of open tabs.

After some :fiddling (see below), a Shortcut-wrapped version of the script produced something like this but with much larger numbers...

A modal dialog showing text detailing my open safari tabs, summarised with an overall count and a list of the hostnames that have most tabs with. Don't you go making assumptions about my politics...

So that set me rolling: closing and bookmarking and switching and sorting tabs. From time to time I hit that Shortcut and saw the number of open tabs going down. The numbers went down by tens, then hundreds. Each of my top clusters of awfulness rolled up like a woodlouse and vanished – pop – like a dull grey bubble. How satisfying.

Immediate, on-demand, consistent reports helped to keep me going. I could come back after a break and still feel progress. I could open a few more tabs (because crap habits don't die easy), and not feel so hopeless. The situation that slowed down my beast of a machine for six months turned from a problem into a story and a bit of code to share.

Getting the tiny tool from an idea to good-enough took an hour – at least half of which was spent on an unnecessary integration. Managing the tabs took perhaps 6 hours overall.

The tool was valuable because it changed my output, not because its numbers were perfect. I needed the tool to motivate me to do the work, and didn't need to trust it to do the work itself. Without the tool, the work would not have been done, by now. I could be spending months glaring at that beachball.

If I hadn't had access to a magic box incorporating a compression of half of humanity's code, I might have :spent frustrating hours on the trivial script. And as with any script, it's useless without its complex surrounding system. Let's not imagine that the code is either mine, or Claude's – it's a speck standing on mighty shoulders. Here it is:

function getHostame(urlString) {
	// need to use regex as JS for AS doesn't appear to have a URL object
	// could use const parsedUrl = new URL(url); const hostname = parsedUrl.hostname;
	const regex = /^https?:\/\/([^/?#]+)(?:[/?#]|$)/i;
	const matches = urlString.match(regex);
	const hostname = matches && matches[1];
	return hostname
}
	
function getTopHostnames(tabs, limit = 5) {
	const hostnameCount = tabs.reduce((listOfHostnames, tab) => {
		const hostname = getHostame(tab.url);
		listOfHostnames[hostname] = (listOfHostnames[hostname] || 0) + 1;
		return listOfHostnames;
	}, {});

	return Object.entries(hostnameCount)
	.sort((a, b) => b[1] - a[1])
	.slice(0, limit)
	.map(([hostname, count]) => ` ${count} of ${hostname}`);
}

function getListOfTabs(input, parameters) {
    var Safari = Application('Safari');
    
    var tabInfo = [];
    
    Safari.windows().forEach(function(window) {
        window.tabs().forEach(function(tab) {
            tabInfo.push({
                title: tab.name(),
                url: tab.url()
            });
        });
    });
    
	return tabInfo;	
}

function sendOutput(numTabs, topSites){
	const message = "You have "+numTabs+" tabs open. Common Sites: "+topSites
	console.log( message ) // send to stdout
	//return message; // un-comment to pass to ScriptEditor output	
}

const listTabs = getListOfTabs()
const numTabs  = listTabs.length;
const topSites = getTopHostnames(listTabs, 10 );
sendOutput(numTabs, topSites);

I have a Shortcut that picks up this script, uses the Run Shell Script action to call osascript to run it, and the Show action to pop it up where i can see it.

A screenshot from Shortcuts showing how I use "Run Shell Script" to pass my script to osascript, and pass the results to "show". Note that this is old – the file is suffixed .js now.

So that's my tiny handy tool. I used it intensely for a weekend, and I'll probably forget about it in a week. Have it if you want it. Use it but don't trust it. It's ephemeral, it's got no tests, it's a means to an end, it's yours.

Better yet, make whatever tool you need to support the task that's bugging you, and tell the rest of us.


Fiddling the script

The numbers were OK, but the details were dumb.

Seeking to polish out the dumb, I asked Claude to sort the set by url and count the top 10 most-common hostnames. It wrote a bubble sort (because training data, I guess), so I gave that a jiggle towards a native sort with another prompt. Then asked it to switch the :AppleScript into JavaScript.

The suggested script used URL, and JavaScript for Automation baulked / borked at that. Once I'd asked for a regex to hoik out the hostname instead, all was broadly OK: the script was producing the same count, more-useful output, and now had clearer code. I broke out a few functions and parameterised a magic number, to give me some sense of agency and to make the thing clearer still if I came back to it.

Then, wanting a touch of integration. I switched from working on the code in Automator, which is a pain, to working on the code in Script Editor, and running the executable in Shortcuts. With the benefit of hindsight, I'm actually not sure precisely why. But the decision cost me.

  • Could I get Script Editor to ouptut a \n newline? I could not.
  • Did the script give me half an hour's debugging arseache with a rogue empty-but-not-really tab? It did.
  • Did I resort to idiot debugging and tiny iterations? Yes: I blame Script Editor, and I draw your attention, fellow debug-bruised Script Wranglers (and me when forgetful), to the Log History in Script Editor's Windows menu.
  • Did Shortcuts try to ask permission to open every single URL when I put the output into a notification – and am I therefore happy with the ugly compromise of a poorly formatted dialog? Why, yes, and yes again.

Ooof. 40 mins I won't get back. So that's the more-human part of AI-assisted toolsmitherly.

When writing this article, I smoothed some more bumps.

  • I did more re-naming and refactoring.
  • I took out an unused library, and two pointless variables.
  • My asked-for and twice-tuned step to sort the list of URLs was unnecessary, given that Claude's way of finding the top hostnames doesn't require a sorted list. It's gone.
  • I disliked Claude's way to get information out of the script, but having a run() function that returned a value, and that value happened to go to stdout. I wanted the return to be out of a function so it was clear what was coming back. AppleScript allows a return outside a function, but JSX doesn't. So I took more AI advice, switched it to console.log and put the whole thing in a findable wrapper.
  • I switched the code from a .scpt file to a .jsx file to reflect the contents. It worked.
  • I switched again to a .js file, to see if I could edit in my usual editors. I could, and I could also get rid of some Script Editor-imposed cruft at top and bottom, so now the code is text, rather than cruft-text-cruft, which seems right for code.

Footnotes

: slowbox

When Safari is open, I find that I'm terribly frustrated by the blink-long gap between button-press and glyph-appear. And the beachballing on bookmarks.

: countless

Not countless. Of course they can be counted, but it's not worth my time. I'll get my machine to count them.

: needignore

More likely, I'd have ignored the need and continued to be mildly frustrated.

: OMGAppleScript

frankly, writing AppleScript is like playing an 80s 'natural language' text adventure game. You know; the ones where you fail with a dozen variants of 'Tie the rope to the bucket' 'attach the cord to the handle' 'make a lowerable container' before you give up and succeed irritatingly with 'throw the bucket in the well'. I loved those things, but there's better stuff to do with one's attention now.


Member reactions

Reactions are loading...

Sign in to leave reactions on posts

Tags

Comments

Sign in or become a Workroom Productions member to read and leave comments.

Great! You've successfully subscribed.
Great! Next, complete checkout for full access.
Welcome back! You've successfully signed in.
Success! Your account is fully activated, you now have access to all content.