Skip to content

Instantly share code, notes, and snippets.

@mmazzarolo
Last active November 26, 2021 17:31
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mmazzarolo/45971692a851eef05f18dfb907b8fc0f to your computer and use it in GitHub Desktop.
Save mmazzarolo/45971692a851eef05f18dfb907b8fc0f to your computer and use it in GitHub Desktop.
Deno script to create a reminder from CLI (in Apple's Reminders app)
// Deno CLI script to add reminders to Apple's Reminders app parsing the
// due date with natural language.
// Requires reminders-cli (https://github.com/keith/reminders-cli) to be
// installed in order to run. Uses reminders-cli instead of Applescript/JXA
// because reminders-cli is much faster.
// Uses chrono-node (https://github.com/wanasit/chrono) to parse the date (which
// in my opinion works better than reminders-cli natural lanaguage parser).
//
// Example usage:
// ```
// quick-reminder Doctor appointment in 2 weeks at 5pm
// ```
import chrono from "https://esm.sh/chrono-node";
import { iter } from "https://deno.land/std/io/util.ts";
import * as Colors from "https://deno.land/std/fmt/colors.ts";
import { format } from "https://deno.land/std/datetime/mod.ts";
// Helper function to run CLI script from Deno.
interface ExecOptions {
verbose?: boolean;
}
const decoder = new TextDecoder();
async function exec(cmd: string, options: ExecOptions = {}) {
const { verbose } = options;
const p = Deno.run({
cmd: ["/bin/sh", "-c", cmd],
stdout: "piped",
stderr: "piped",
});
let stdout = "";
let stderr = "";
const stdoutPromise = (async function () {
for await (const chunk of iter(p.stdout)) {
const decoded = decoder.decode(chunk);
if (verbose) await Deno.stdout.write(chunk);
stdout += decoded;
}
p.stdout.close();
})();
const stderrPromise = (async function () {
for await (const chunk of iter(p.stderr)) {
const decoded = decoder.decode(chunk);
if (verbose) await Deno.stderr.write(chunk);
stderr += decoded;
}
p.stderr.close();
})();
const { success } = await p.status();
await Promise.all([stdoutPromise, stderrPromise]);
p.close();
if (!success) {
throw new Error(stderr);
}
return stdout.trim();
}
// Use reminders-cli (https://github.com/keith/reminders-cli) to add a reminder.
async function run() {
// Ensure reminders-cli is installed.
try {
await exec(`reminders`);
} catch (_err) {
throw new Error(
`Reminders CLI doesn't seem to be installed. See https://github.com/keith/reminders-cli.`
);
}
// Get input args.
// For a better UX, we allow writing the entire string without quotes.
const inputStr = Deno.args.join(" ");
if (!inputStr) {
throw new Error(
`Missing input. Example usage: $ quick-reminder Doctor appointment tomorrow`
);
}
// Get default reminders list.
// Since the reminders.app API doesn't expose the "default" list, we'll pick
// the first one available from the list of reminders list (which follows the
// order set in the Reminder app).
const remindersCLIShowListsStdout = await exec("reminders show-lists");
const availableReminderListNames = remindersCLIShowListsStdout.split("\n");
const defaultReminderListName = availableReminderListNames[0];
if (!defaultReminderListName) {
throw "Unable to load reminder lists";
}
// Extract the natural-lanaguage date from the input string using chrono-node.
const chronoParsingResult = chrono.parse(inputStr)[0];
const naturalLanguageDueDate = chronoParsingResult?.text || "";
const reminderText = inputStr.replace(naturalLanguageDueDate, "").trim();
const reminderDueDate = chronoParsingResult?.start?.date();
// Use reminders-cli to add the reminder to reminders.app.
await exec(
reminderDueDate
? `reminders add ${defaultReminderListName} "${reminderText}" --due-date "${reminderDueDate}"`
: `reminders add ${defaultReminderListName} "${reminderText}"`
);
const prettyCheckMark = Colors.green("✔");
const prettyReminderText = Colors.magenta(reminderText);
const prettyReminderListName = Colors.magenta(defaultReminderListName);
if (reminderDueDate) {
const prettyReminderDueDate = Colors.magenta(
format(reminderDueDate, "yyyy-MM-dd HH:mm:ss")
);
console.log(
`${prettyCheckMark} Added a new "${prettyReminderText}" reminder to the "${prettyReminderListName}" list with due date ${prettyReminderDueDate}`
);
} else {
console.log(
`${prettyCheckMark} Added a new "${prettyReminderText}" reminder to the "${prettyReminderListName}"`
);
}
}
try {
await run();
} catch (err) {
console.error(`${Colors.red("✘")} ${err?.message}`);
}
@mmazzarolo
Copy link
Author

Requires reminders-cli to be globally installed.

Usage:

deno run --allow-run quick-reminder-raw-gist-url Doc appointment tomorrow at 2:30pm

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment