Scripting macOS with Javascript Automation

I’ve been playing with ActivityWatch, a really neat open-source application to track what you are doing when you are on your computer. Similar to rescue time RescueTime, but open source, with some more advanced features. I’ve been using it for a couple of months as part of my digital minimalism toolkit and it’s worked great to give me an idea of what’s taking up my time.

There’s been a couple of things that have bugged me about the application, and it’s written in a couple of languages I’ve wanted to learn (Rust, Vue), so I decided to make a couple of changes as part of a learning project.

I ended up needing to modify an AppleScript and ran into macOS’s Javascript Automation for the first time. It’s a really powerful system but horribly documented, with very little open source code to learn from.

Retrieving the Active Application, Window, and URL using AppleScript

I wanted to extract the active application, title of the main window, and the URL of that window (if the active application is a browser). I found this AppleScript, which was close to what I wanted, but I also wanted to identify the main window if a non-browser was in use:

global frontApp, frontAppName, windowTitle set windowTitle to "" tell application "System Events" set frontApp to first application process whose frontmost is true set frontAppName to name of frontApp tell process frontAppName try tell (1st window whose value of attribute "AXMain" is true) set windowTitle to value of attribute "AXTitle" end tell end try end tell end tell do shell script "echo " & "\"\\\"" & frontAppName & "\\\",\\\"" & windowTitle & "\\\"\""

Here’s what combining these two scripts looks like in Javascript for Automation:

var seApp = Application("System Events"); var oProcess = seApp.processes.whose({frontmost: true})[0]; var appName = oProcess.displayedName(); // these are set to undefined for a specific reason, read more below! var url = undefined, incognito = undefined, title = undefined; switch(appName) { case "Safari": url = Application(appName).documents[0].url(); title = Application(appName).documents[0].name(); break; case "Google Chrome": case "Google Chrome Canary": case "Chromium": case "Brave Browser": const activeWindow = Application(appName).windows[0]; const activeTab = activeWindow.activeTab(); url = activeTab.url(); title = activeTab.name(); break; default: mainWindow = oProcess. windows(). find(w => w.attributes.byName("AXMain").value() === true) // in some cases, the primary window of an application may not be found // this occurs rarely and seems to be triggered by switching to a different application if(mainWindow) { title = mainWindow. attributes. byName("AXTitle"). value() } } JSON.stringify({ app: appName, url, title, incognito });

Some notes & learnings that help explain the above code:

You can write & test JXA from the "Script Editor" application. You can connect the script editor to Safari for a full-blown debugger experience, which is neat. Open up a REPL via osascript -il JavaScript There’s not really an API reference anywhere. The best alternative is Script Editor -> File -> Open Dictionary. The javascript runtime is definitely heavily modified: Object.getOwnPropertyNames returns __private__ for all of the system events-related objects. This makes it much harder to poke around in a repl to determine what methods are available to you. Use #!/usr/bin/osascript -l JavaScript at the top of your jxa to run a script directly in your terminal. whose only seems to work with properties, not attributes. If you want to filter on attributes you need to iterate over each element: windows().find(w => w.attributes.byName("AXMain").value() === true) Application objects seem to use some sort of query ORM-type model underneath the hood. The application only seems to execute queries when value() or another value is requested, otherwise you’ll just get a reference to the query that could retrieve the object. This makes it harder to poke at the objects in a repl. If you compile a script once and rerun it, you must reset your variables to undefined otherwise the values they were set to will stick around. This is why all var declarations above are set to undefined. You can import objc libraries and use them in your JXA.

It’s worth noting that some folks online mention that JXA is dead, although not deprecated. I think this is a general state on macOS scripting (including AppleScript): Apple has built some very neat technologies but has done a horrible job at continuing to develop and evangelize them so they have many sharp edges and there is sparse documentation out there.

Executing Javascript Automation Scripts from Python

A powerful aspect of the python ecosystem is PyObjc which enables you to reach into the macOS Objective-C APIs within a python script. In this case, this allows you to compile & run applescript/javascript from within a python script without shelling out to osascript. This improves performance, but also makes it much easier to detect errors and parse output from the script.

The snippet below was adapter from this StackOverflow post and requires that you pip install pyobjc-framework-OSAKit :

script = None def compileScript(): from OSAKit import OSAScript, OSALanguage scriptPath = "path/to/file.jxa" scriptContents = open(scriptPath, mode="r").read() javascriptLanguage = OSALanguage.languageForName_("JavaScript") script = OSAScript.alloc().initWithSource_language_(scriptContents, javascriptLanguage) (success, err) = script.compileAndReturnError_(None) # should only occur if jxa is incorrectly written if not success: raise Exception("error compiling jxa script") return script def execute(): # use a global variable to cache the compiled script for performance global script if not script: script = compileScript() (result, err) = script.executeAndReturnError_(None) if err: raise Exception("jxa error: {}".format(err["NSLocalizedDescription"])) # assumes your jxa script returns JSON as described in the above example return json.loads(result.stringValue())

Here’s the structure of an AppleScript err after executing the script:

{ NSLocalizedDescription = "Error: Error: Can't get object."; NSLocalizedFailureReason = "Error: Error: Can't get object."; OSAScriptErrorBriefMessageKey = "Error: Error: Can't get object."; OSAScriptErrorMessageKey = "Error: Error: Can't get object."; OSAScriptErrorNumberKey = "-1728"; OSAScriptErrorRangeKey = "NSRange: {0, 0}"; }

Here are some tips and tricks for working with pyobjc in python:

Always pass None for objc reference arguments. References are returned in a tuple instead. You can see this in the above code ((result, err) = script.executeAndReturnError_(None)): result is the return value of the method, while err is reference argument passed as None in : is replaced by _ in the method signatures There’s a separate package for each objc framework. Import only what you need to avoid application bloat. Objc keyword arguments are transformed into positional arguments, not python keyword arguments. I ran into weird initialization errors if I had pyobj calls in the global namespace (for instance, caching the script immediately as opposed to setting script = None). I’m not sure if this was specific to how the rest of the application I was working in was structured. Resources

Here are some helpful resources I ran into when

Best group open source example scripts I could find: https://github.com/voostindie/vincents-productivity-suite-for-alfred Not sure why, but this forum has a lot of good sample code to copy from. https://forum.keyboardmaestro.com https://apple-dev.groups.io/g/jxa/wiki/3202 Some helpful snippets & usage examples https://gist.github.com/heckj/5b7bb332463a762639e179a37ea3a216 Official Apple release notes which a nice group of snippets. A great technical deep dive with links to many interesting resources

Continue Reading

Reclaiming Your Mind: Creating an Information Diet

There’s been a lot of areas of my life that I’ve been ‘auditing’ and attempting to tweak the habits that have intentionally or accidentally fallen into place. One of these is my information diet: how I find, consume, and process information.I’ve been tracking my time spent on reading/time on the internet and I’m not liking the trend. I’ve felt more addicted to information this year and I want to eliminate that feeling. Revamping my information intake is one way I’m going to do that. It’s worth thinking about why it’s worth spending time consuming information, how I consume information, and how I want to change my information consumption.

Categories Stories. I’ve been almost exclusively consuming non-fiction for the last decade and rarely read any non-fiction. At the suggestion of my ever-wise wife and the promptings of a great podcast on story I’ve reprioritized non-fiction as something worth spending time on. Great stories can change our perspective on our life and increase our creative thinking.Curiosity & Exploration. Investing in discovering new and interesting ideas has always paid off for me. For me, this generally looks like browsing community sites like hacker news, reading a random newsletter, of following interesting people on Twitter. Learning about random, interesting topics has always been really enjoyable for me—it sparks creativity (and joy) and is useful later Work. Learning specific to a work-related problem. Entertainment & Social. Twitter, Facebook, news, etc. When you look back at your time spent here you always feel like it was a waste. I’ve been convinced that keeping up with news is largely a waste of time (you’ll hear anything worth knowing about through friends), and time spend communicating with friends over social is better spent with friends in person. This doesn’t mean that there isn’t a place for this category, but for me it means I need to bias towards eliminating any time spent on this category. Infrequent Personal-ish Updates. There’s a group of organizations or people I follow that I want to keep tabs on, but don’t send emails often. A friend running a non-profit, bands announcing a new album, etc. Deals/Promotions. Transaction/Service Emails. Mediums Podcasts.BooksBlogs.Community News. Hacker News, Lobsters, Reddit, Product Hunt.News Sites. Google News, Bloomberg, TechCrunch, etc. Social. Twitter, FaceBook.Movies. NetFlix, Amazon Prime, YouTube.Email.Personal communication. Texts, voicemails, etc.

Thoughts on Consumption.

Continually improve the system. Set aside time every month to quickly audit what I’m consuming and what tools I’m using. Make consumption a choice rather than a reaction. Right now, I randomly visit Hacker News or see an article come through my email. Instead I want to centralize information in one category into a single place that I can go. Optimize for pull vs push consumption. A great example here is email newsletters. They are push, not pull, and are often messy to read and pile up in my inbox. I want to separate “conversations with people” over “updates from companies/interesting news”.Categorizing information is critical. Email newsletters can’t be categorized easily. I want to put feeds into separate buckets that I can prioritize and triage separately. I should use RSS again. Way back when I read everything via NetNewsWire. Email newsletters took over seemingly overnight and I forgot that RSS existed. Most sites I care about still support RSS (even if it’s not advertised explicitly).Use a RSS Reader. Specifically, one supported by paid subscriptions. Free is great, but most free things (without a huge market) die or have negative externalities over time. I don’t want to have to mess with this part of my toolkit much and deal with a killed product. Paid subscriptions mean it’s a real business that will continue to improve over time. Limit consumption. I want to enforce a limit on the numbers of things I’m consuming. I wonder if there is a way to automatically reset the read count of various feeds so it doesn’t look like there are too many articles to read when I use a reader.Prefer books over articles. For most business/technology problems, blogs and Q&A sites are the main source of data, but work aside, books are generally higher-quality information compared to blogs. The time it took someone to create the content is a good indicator of the quality. Books > Blogs > Twitter. (this gets a bit tricky with low-cost kindle books: skip these). Optimize for highest impact & quality information at the beginning of the day. This means reading books and long-form articles at the beginning of the day while my mind is clear, instead of consuming blogs, tweets, texts, etc. Treat books like a blog archive. I really like this concept, can’t remember where I first heard it. Reading books from cover to cover doesn’t make a ton of sense, although it’s definitely how I’m trained to read books. Skimming through a chapter (or skipping it entirely) if you find it boring or too verbose shouldn’t feel ‘wrong’. If the writer can’t keep your attention, that’s their fault. Additionally, books are generally longer than they need to be in order to hit page quotas.Don’t switch contexts. If you are reading a book, don’t stop and read a blog article. Cultivating sustained laser-focus attention on a single thing is critically important. I’ve found this to be more challenging as the years go by, and it’s something I need to be even more intentional about.Focus on managing written internet media. I don’t over-consume podcasts or books. I struggle most with interesting, distracting news sources like Hacker News or What I’m going to change Limit number of news feeds to 30. I suspect this number will change as I continue to slowly improve how I’m processing information, but this is a good start.Convert email newsletters to RSS. Most newsletters (like Ruby Weekly) have an RSS feed. For those that don’t, FeedBin has a service to convert email newsletters into a feed, and I imagine there are standalone services that will do this for you automatically.Mass-unsubscribe from email newsletters. I’ve been using unroll.me for years (I don’t love the privacy component, but it’s a useful tool). It looks like their unsubscribe option will actually click through the unsubscribe links for me. I should go through my daily Unroll.me summary and remove newsletters I’m not interested in, and convert the others into a feed.Setup two aliases email+updates@gmail.com and email+promotions@gmail.com. Forward all updates to FeedBin and auto-archive. Auto-archive & tag all promotions.Subscribe to weekly summaries on community news site. Mass unfollow everyone on Twitter, and limit the people I follow to 50.Update my website blocking strategy, including blocking of all news & social sites. More on this in a separate post.Stop using Apple Podcasts. I find it hard to keep things organized and Apple seems to randomly reverse the listing of certain podcasts. I should trim which Podcasts I subscribe to and find another Podcast application. Categories: engineering, techReview all gmail filtersReview and trim all YouTube subscriptions. Review all Twitter app connectionsReview any compromised passwords via 1Password

After a bunch of investigation, I settled on using FeedBin.

https://reederapp.com/mac/#faq doesn’t look like it is updated oftenhttps://github.com/mausba/rssheap went open source and hasn’t been touched in over a yearhttps://github.com/getstream/winds podcast and RSS reader, open-source, commercially supported and recently updated.https://www.inoreader.com. Standalone paid product, has an API, doesn’t look too complex. 64 feeds for free. Free and paid tiersFeedly.com is the most popular, but looks to be overdone. http://newsblur.com. Standalone paid product. Not updated frequently. Doesn’t look like a great design. Free and paid tiers. I found this reader most commonly referenced by HN and Lobsters.https://readkitapp.com ties into various services to create a great reading experience. https://www.goldenhillsoftware.com/unread/ another macos reader.https://yoleoreader.com web-based reader with a low-cost paid subscription. https://feedbin.com well-designed feed reader. Supports podcasts and receiving email newsletters via a special email address. Also has a Twitter reader as well. Bootstrapped business. Also open source, very cool. https://github.com/feedbin/feedbinhttps://github.com/ViennaRSS/vienna-rss Pete Cooper is involved in this one. Open source. Looks like a zombie product.https://www.nooshub.com new reader from HN with some fancy “AI” grouping.https://ranchero.com/netnewswire/https://apps.apple.com/app/leaf/id576338668 Leaf. Looks dead. Hasn’t been updated in two years.

Other interesting finds:

https://superfeedr.com RSS feed APIhttps://throttlehq.comhttps://news.ycombinator.com/item?id=20167143 and https://news.ycombinator.com/item?id=19909102

Continue Reading

Handling Web Timeouts in Heroku

I’m a huge fan of Heroku. Way back when, I used to manage the entire deployment infrastructure manually. I’d grab a VPS from RackSpace/AWS, install nginx, configure ruby, tinker with deployment scripts, and then in the weeks ahead endlessly tinker with settings when things didn’t work just right. Although I did enjoy the capture-the-flag feel of finding the right service configuration to solve a problem, once Heroku became a thing I switched over every application I managed.

There’s a huge amount of leverage in never having to worry about the details of your deployment infrastructure. Heroku is expensive, but it’s orders-of-magnitude cheaper than hiring a devops expert.

However, there are some limitations. The one you’ll most likely run into is the 30-second web worker timeout. If your web request doesn’t finish in time, it will be killed and the user receives a 500 error. Not good.

A much better UX is displaying some sort of ‘loading slowly, please refresh’ message to the user and implementing progressive caching. This way, if there is some sort of slow service causing an IO block, you can cache the response in the first request made by the user, so the second page load attempt works successfully.

(You may be wondering why a page load would ever take 30s. Great question. I work a lot with NetSuite, and sometimes need to pull content dynamically. If there is an API slowdown—which happens often—this can cause the page load time to spike).

The best way I’ve found to gracefully handle this situation is to use the ruby stdlib Timeout::timeoutmethod to throw an exception after 29 seconds. However, this method is dangerous. You’ll want to first understand how this operates under the hood:

https://jvns.ca/blog/2015/11/27/why-rubys-timeout-is-dangerous-and-thread-dot-raise-is-terrifying/https://medium.com/@adamhooper/in-ruby-dont-use-timeout-77d9d4e5a001https://www.mikeperham.com/2015/05/08/timeout-rubys-most-dangerous-api/

In your rails controller, here’s how you can ‘protect’ a method that could run for a long time and display a friendly timeout page instead of a standard 500.

class ApplicationController rescue_from WebWorkerTimeoutError do render :timeout end around_action :raise_on_web_timeout, only: :show def show @state = the_long_running_thing end def raise_on_web_timeout Timeout::timeout(29, WebWorkerTimeoutError) do yield end end end

Continue Reading

Three Financial Automation Techniques

I’m a huge fan of outsourcing and automating everything, even rarely used tools like faxing. Time is my most valuable resource: and although I’m far from perfect at this, I try to eliminate, automate, or delegate everything that isn’t essential for me to do.

Here is a snapshot of the toolkit I use to automate personal finance.

Automate All Bill Payments

Do you spend any time manually paying bills each month? YES! You can automate this and, if you are near a beach, you can write “relax” in the sand just like the above photo with all of your extra time.

All credit card payments can be automated. Login to your credit cards online portal and setup auto-pay; pay the full balance off every month. Do you need to write a check for rent or other expenses? This can be automated as well. Most banks allow you to schedule a monthly recurring checks to residential or business addresses. If your bank does not allow for this, find another that does (Bank of America or PNC is great in this regard). Does the monthly cost of the service fluctuate? Ask the provider if you can negotiate a flat monthly fee and settle up any balance differences on a quarterly, bi-yearly, or (best of all) a yearly basis. Is the provider not keen with a flat fee? Ask if you can pay a flat fee above the monthly average and then deduct any overpayment from the last payment in each quarter/year. If he’s not willing to work with you here – find another provider. It’s not worth your time to have to remember to pay that one extra bill. Your goal is complete financial automation! Need to pay friends or family for a meal or other event? Use Cash.me to sent payment when it’s needed, rather than having to remember later. No fees, easy to setup, and money is transferred directly into the recipients bank account. Some services (ex: car insurance) allow for quarterly payments, often with a discount. Leverage financial discipline to pre-pay for these expenses, saving on the total cost of the service and eliminating the need to remember to pay every month. Tracking Spending Across All Accounts

I aggregate transactions across all accounts (checking, savings, credit cards, investments, etc) into a single system. This means I never have to login to individual accounts. I only login to a single account.

There are a bunch of systems out there for this, I use Mint. Here’s how to use Mint to implement a budget:

Create a detailed budget in a google sheet. Recreate the budget in your google sheet in Mint. You will need to consolidate certain budget categories into a larger budget item to reduce the transaction categorization time in mint. For instance, you might have a “health insurance” and “gym” budget in your google sheet. I would recommend managing that as a single budget item in Mint “Health”. I find it’s easier to have fewer categories, Mint does better at auto-categorizing transactions with fewer categories. Check how you are doing against your budget on a weekly, bi-weekly, or even monthly basis. As you start to move closer to your ideal budget, you can check your Mint account less – further freeing up more time and clearing your mind of the mental clutter of worrying about making sure finances are taken care of.

A side benefit here is it makes it possibly to easily scale the number of accounts you have without it becoming more time consuming or complicated to manage. This is very useful if you are looking to churn cards to make thousands per year.

Automated Savings & Investing

Constraints create results.

Remember when you were under a hard deadline? You completed the project.

If there is one concept from Rich Dad, Poor Dad that is worth remembering, it’s pay yourself first: contribute to retirement and savings, and figure out how to make the rest of your monthly expenses fit into what is left over.

Automating your savings and investment contributions at the beginning of your pay period eliminates the possibility that you’ll spend money earmarked for investment and savings. Here’s how to set it up:

Open a Betterment account. It will withdraw your investment contribution on a weekly basis. A weekly contribution will enable you to view your retirement contribution in the weekly financial email report that Mint sends. Betterment is a great service – software based automated investing. Set your risk tolerance, and Betterment will automatically rebalance your portfolio and harvest any tax benefits from selling losses. It’s not a “perfect” portfolio, but it’s automated, a bit better than a standard Vanguard account, and gets you 80% of the results with very little effort. Open a high yield savings account. Although rates are still very low, they yield 5-10x the standard savings account and as interest rates rise the delta between a online-only high-interest savings account will only increase. Plus, online-only savings accounts have more advanced automatic contribution functionality compared to traditional banks.

Obviously, I am not offering professional advice. Don’t trust anything that is written here. This just is a summary of what I’ve learned and what has worked for me last couple years.

Continue Reading

What I’m Looking for From Platform Conference 2014

I have the amazing opportunity to attend Michael Hyatt’s platform conference. I’ve followed Michael Hyatt for many years ago – I’ve read his books, blog posts, and religiously listened to his podcast.

I’m a big fan.

Although in a web application and website developer by trade, I love marketing. I’ve made learning marketing a priority over the last year and I’m excited to dive into a full two day conference dedicated to building an online platform and an online business.

Here’s what I’m looking for from the conference.

Copywriting

I’m a horrible speller. I got a 10% on the state spelling exam in high school. However, I love the craft of communicating ideas and grabbing someone’s attention.

How to effectively test multiple headlines? Best places to go for example copy Basic principals to get marketing copy 80% there Authentic vs authoritative vs fun. How do others balance this? Traffic Analysis & Product Pricing

I studied math in college. I work with data structures all day. I get numbers quickly and can easily connect the dots.

Knowing what to track and how to interpret metrics for an online business is not a science it’s and art, I hope to glean the basics:

Different models and the economics behind them Psychology of pricing and value What are the major categories of analyzing traffic Tools

I love tools. Websites, apps, icon packs, services. I’m all about it. I love optimizing my productivity.

I’ve always loved The Setup and Dribbble’s Timeouts.

I’m excited to generate a list of tools and tricks that can help move the needle.

People

This is the main reason I’m attending the conference. I’m super excited to meet all of the people in Michael Hyatt’s tribe. The Twitter feed is already filled with some stellar people. Can’t wait to meet everyone.

I’ll be writing on each of these topics during and after the conference. Follow along and let me know if you have any questions you have – I’ll try to get an answer for you!

Continue Reading

Don’t Wait for Change. Attack Yourself.

Change is hard. Challenging the status quo is scary. Hurting “the existing business” is a real concern that has short term consequences.

If you don’t attack your own business – whether you’re a consultant, small business, or large corporation – someone else will. Someone else who has the pressure of a smaller budget, the focus of not being distracted by less important projects, or the freedom from red-tape, will make a game changing move.

You have to be flexible and bold enough to to replace your legacy business, to create a product or service that will change or possibly destroy your current business model. As the marketing classic The 22 Immutable Laws of Marketing states, you have to attack yourself.

Continue Reading

Don’t Sell Inputs. Sell Results.

Work is often valued on the number of hours you put in. It’s complex to price work on a per-project basis. It’s easy to set an hourly rate as opposed to pricing on a per-project basis.

However, there is a hard limit to return when working on an hourly basis. I was recently challenged to change the way I value and price my work, this quick comment has triggered a significant paradigm shift over the last week:

You have to move to be outcome driven. Stop concentrating on inputs.

All that matters is the outcome. Most of the time it doesn’t matter how you get there, as long as the goals are met (hopefully surpassed!), timelines hit, and executed within budget constraints.

Growth comes from delivering results where:

Pricing is not based on input (hours) The input needed to deliver results is significantly less than the competition

In this case, both parties win. You profit from the knowledge capital you’ve built in a specific skill set.

Continue Reading