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.
If you don't want a list of every message, just recent ones, use
--cutoff
to specify a UNIX timestamp that will make Unreplied ignore all messages sent before it.By default, Unreplied will use the Contacts API to attempt to resolve phone numbers and email addresses into contact names. If you don't want this, use
--no--contacts
.If you want to pipe Unreplied's output into another application, like
awk
orsed
, use--csv
to have Unreplied output in CSV, which should be easier to parse.
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!