Unreplied just released with command line support! Here's how to use it.

First, identify where Unreplied is installed. If you got it from the Mac App Store, it should be in /Applications/Unreplied.app.

Next, verify that your version of Unreplied supports the CLI. To do that, run:

andi@Andis-MacBook-Pro % /Applications/Unreplied.app/Contents/MacOS/Unreplied --cli --help

USAGE: unreplied [--cli][--csv] [--no-contacts][--ignore-non-contacts] [--ignore-short-codes][--cutoff <ts>]

OPTIONS:
--cli                   Launch Unreplied without a GUI.
--csv                   Print unreplied messages in CSV format.
--no-contacts           Don't attempt to resolve message handles into contact names.
--ignore-non-contacts   Don't include messages from people who are not in your Contacts.
--ignore-short-codes    Don't include messages from short codes.
--cutoff <ts>           Ignore all messages received before this UNIX
                        timestamp, in milliseconds. (default: 0)
-h, --help              Show help information.

Note that you have to call the Unreplied executable itself from inside the .app - not the .app itself (which is actually just a folder).

Now that you've gotten the help command working, you're ready to get started! Simply call /Applications/Unreplied.app/Contents/MacOS/Unreplied --cli without the --help flag to get a list of every iMessage and SMS you haven't responded to.

Behind The Scenes

I learned a bit trying to shove CLI functionality into a Swift app. There isn't much documentation on this other than a few StackOverflow posts, so I'll just document my findings here.

First, when you create a Swift app through Xcode, you start off with an AppDelegate.swift file. At the top of the class declaration, you might see @NSApplicationMain. Apple says:

Apply this attribute to a class to indicate that it is the application delegate. Using this attribute is equivalent to calling the NSApplicationMain function and passing this class’s name as the name of the delegate class.

If you do not use this attribute, supply a main.swift file with a main function that calls the NSApplicationMain function. For example, if your app uses a custom subclass of NSApplication as its principal class, call the NSApplicationMain function instead of using this attribute.

..So that's exactly what I did. I took out @NSApplicationMain, created a main.swift file with the following line:

    _ = NSApplicationMain(CommandLine.argc, CommandLine.unsafeArgv)

It runs, so now we have a way to intercept command line arguments as well as retain GUI functionality. But in CLI mode, we don't want to spawn the GUI; we just want to print to stdout. So we need a way to figure out when the app was called from the command line. My easy fix for this was to just require the --cli flag:

    if CommandLine.arguments.count > 1 && CommandLine.arguments[1] == "--cli" {
        Unreplied.main()
    }
    else {
        _ = NSApplicationMain(CommandLine.argc, CommandLine.unsafeArgv)
    }

What's Unreplied.main()? It's a ParseableCommand from the Swift Argument Parser, which, as its introduction post says, "makes it straightforward — even enjoyable! — to parse command-line arguments in Swift."

I agree! It was very easy to add CLI functionality to Unreplied with the Swift Argument Parser. Here's all the code I needed to add all of Unreplied's flags:

    struct Unreplied: ParsableCommand {

        @Flag(help: "Launch Unreplied without a GUI." )
        var cli: Bool
        @Flag(help: "Print unreplied messages in CSV format.")
        var csv: Bool
        @Flag(help: "Don't attempt to resolve message handles into contact names.")
        var noContacts: Bool
        @Flag(help: "Don't include messages from people who are not in your Contacts.")
        var ignoreNonContacts: Bool
        @Flag(help: "Don't include messages from short codes.")
        var ignoreShortCodes: Bool

        @Option(default: 0, help: ArgumentHelp("Ignore all messages received before this UNIX timestamp, in milliseconds.", valueName: "ts"))
        var cutoff: Int

        func run() throws {
            // do stuff with the arguments...
        }
    }

That's it! If you come up with some clever use for Unreplied on the command line, please let me know on Twitter @Nexuist or my Live Chat!


Tagged #technical, #shell.


Want more where that came from? Sign up to the newsletter!



Powered by Buttondown.