Something went wrong. Try again.
CLI for FastMail via JMAP
Something went wrong. Try again.
13 kB · 496 lines
TypeScript
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497#!/usr/bin/env bunimport { getAuthStatus, login, logout, promptForToken, readTokenFromStdin,} from "./auth";import { parseArgs, printCompletion, printHelp, printVersion } from "./cli";import { downloadAllAttachments, getAttachment, listAttachments,} from "./commands/attachment";import { markRead as batchMarkRead, markUnread as batchMarkUnread, deleteEmails, flagEmails, modifyEmails, moveEmails, unflagEmails,} from "./commands/batch";import { createDraft, deleteDraft, getDraft, listDrafts, sendDraft, updateDraft,} from "./commands/draft";import { markRead as emailMarkRead, markUnread as emailMarkUnread, getEmail, listEmails, searchEmails, sendEmail,} from "./commands/email";import { createMailbox, deleteMailbox, listMailboxes, updateMailbox,} from "./commands/mailbox";import { createMaskedEmail, deleteMaskedEmail, listMaskedEmails, updateMaskedEmail,} from "./commands/masked";import { getThread, modifyThread, threadAttachments } from "./commands/thread";import { batchUnsubscribe, executeUnsubscribe, showUnsubscribe,} from "./commands/unsubscribe";import { urlCommand } from "./commands/url";import { getConfigPath } from "./config";
async function main(): Promise<void> { // Bun compiled binary: process.argv = ["/path/to/binary", "arg1", "arg2", ...] // bun run script.ts: process.argv = ["/path/to/bun", "/path/to/script.ts", "arg1", ...] // The compiled binary path won't end in .ts or .js const scriptPath = process.argv[1] ?? ""; const isCompiledBinary = !scriptPath.endsWith(".ts") && !scriptPath.endsWith(".js") && !scriptPath.includes("bun"); const args = process.argv.slice(isCompiledBinary ? 1 : 2); const options = parseArgs(args);
if (options.version) { printVersion(); process.exit(0); }
if (options.completion) { const success = printCompletion(options.completion); process.exit(success ? 0 : 1); }
// Show global help only if no command specified if (!options.command) { printHelp(); process.exit(options.help ? 0 : 1); }
// Pass help flag to subcommand handlers const subcommandArgs = options.help ? ["--help", ...options.args] : options.args;
// Route to command handlers switch (options.command) { case "auth": await handleAuth(options.subcommand, subcommandArgs); break; case "batch": await handleBatch(options.subcommand, subcommandArgs); break; case "email": await handleEmail(options.subcommand, subcommandArgs); break; case "mailbox": await handleMailbox(options.subcommand, subcommandArgs); break; case "masked": await handleMasked(options.subcommand, subcommandArgs); break; case "unsubscribe": await handleUnsubscribe(options.subcommand, subcommandArgs); break; case "attachment": await handleAttachment(options.subcommand, subcommandArgs); break; case "thread": await handleThread(options.subcommand, subcommandArgs); break; case "draft": case "drafts": await handleDraft(options.subcommand, subcommandArgs); break; case "url": await handleUrl(options.subcommand, subcommandArgs); break; default: console.error(`Unknown command: ${options.command}`); printHelp(); process.exit(1); }}
function parseAuthArgs(args: string[]): { token?: string; help: boolean } { let token: string | undefined; let help = false;
for (let i = 0; i < args.length; i++) { const arg = args[i]; if (arg === "-h" || arg === "--help") { help = true; } else if (arg === "--token" && args[i + 1]) { token = args[i + 1]; i++; } else if (arg?.startsWith("--token=")) { token = arg.slice(8); } }
return { token, help };}
async function handleAuth( subcommand: string | null, args: string[],): Promise<void> { const parsed = parseAuthArgs(args);
switch (subcommand) { case "login": { if (parsed.help) { console.log(`Usage: fastmail auth login [--token <token>]
Authenticate with FastMail using an API token.
Options: --token <token> API token (can also be piped via stdin) -h, --help Show this help
Get your API token from: FastMail Settings > Privacy & Security > API tokens`); return; }
// Get token from: 1) --token flag, 2) stdin, 3) interactive prompt let token = parsed.token;
if (!token) { token = (await readTokenFromStdin()) ?? undefined; }
if (!token) { token = await promptForToken(); }
if (!token) { console.error("Error: No API token provided"); process.exit(1); }
console.log("Validating token..."); const result = await login(token);
if (result.success) { console.log(`Authenticated as: ${result.username}`); if (result.accountName) { console.log(`Primary account: ${result.accountName}`); } console.log(`Token saved to: ${getConfigPath()}`); } else { console.error(`Login failed: ${result.error}`); process.exit(1); } break; }
case "logout": { if (parsed.help) { console.log(`Usage: fastmail auth logout
Remove stored API token from config file.
Options: -h, --help Show this help`); return; }
await logout(); console.log("Logged out. Token removed from config."); break; }
case "status": { if (parsed.help) { console.log(`Usage: fastmail auth status
Show current authentication status.
Options: -h, --help Show this help`); return; }
const status = await getAuthStatus();
if (status.authenticated) { console.log(`Status: Authenticated`); console.log(`User: ${status.username}`); if (status.accountName) { console.log(`Account: ${status.accountName}`); } console.log( `Token source: ${status.tokenSource === "env" ? "FASTMAIL_API_TOKEN env var" : "config file"}`, ); } else { console.log(`Status: Not authenticated`); if (status.error) { console.log(`Reason: ${status.error}`); } if (status.tokenSource) { console.log( `Token source: ${status.tokenSource === "env" ? "FASTMAIL_API_TOKEN env var" : "config file"}`, ); } process.exit(1); } break; }
default: console.error( `Usage: fastmail auth <login|logout|status>\n\nSubcommands:\n login Authenticate with API token\n logout Remove stored credentials\n status Show authentication status`, ); process.exit(1); }}
async function handleEmail( subcommand: string | null, args: string[],): Promise<void> { switch (subcommand) { case "list": await listEmails(args); break; case "get": await getEmail(args); break; case "send": await sendEmail(args); break; case "search": await searchEmails(args); break; case "mark-read": await emailMarkRead(args); break; case "mark-unread": await emailMarkUnread(args); break; default: console.error( `Usage: fastmail email <list|get|send|search|mark-read|mark-unread>\n\nSubcommands:\n list List emails in a mailbox\n get Get email by ID\n send Send an email\n search Search emails\n mark-read Mark emails as read\n mark-unread Mark emails as unread`, ); process.exit(1); }}
async function handleMailbox( subcommand: string | null, args: string[],): Promise<void> { switch (subcommand) { case "list": await listMailboxes(args); break; case "create": await createMailbox(args); break; case "update": await updateMailbox(args); break; case "delete": await deleteMailbox(args); break; default: console.error( `Usage: fastmail mailbox <list|create|update|delete>\n\nSubcommands:\n list List all mailboxes\n create Create a mailbox\n update Update a mailbox\n delete Delete a mailbox`, ); process.exit(1); }}
async function handleMasked( subcommand: string | null, args: string[],): Promise<void> { switch (subcommand) { case "list": await listMaskedEmails(args); break; case "create": await createMaskedEmail(args); break; case "update": await updateMaskedEmail(args); break; case "delete": await deleteMaskedEmail(args); break; default: console.error( `Usage: fastmail masked <list|create|update|delete>\n\nSubcommands:\n list List masked email addresses\n create Create a masked email\n update Update a masked email\n delete Delete a masked email`, ); process.exit(1); }}
async function handleBatch( subcommand: string | null, args: string[],): Promise<void> { switch (subcommand) { case "read": await batchMarkRead(args); break; case "unread": await batchMarkUnread(args); break; case "flag": await flagEmails(args); break; case "unflag": await unflagEmails(args); break; case "move": await moveEmails(args); break; case "delete": await deleteEmails(args); break; case "modify": await modifyEmails(args); break; default: console.error( `Usage: fastmail batch <read|unread|flag|unflag|move|delete|modify>\n\nSubcommands:\n read Mark emails as read\n unread Mark emails as unread\n flag Flag/star emails\n unflag Remove flag/star from emails\n move Move emails to a mailbox\n delete Delete emails (move to Trash or permanent)\n modify Generic email modification`, ); process.exit(1); }}
async function handleUnsubscribe( subcommand: string | null, args: string[],): Promise<void> { switch (subcommand) { case "show": await showUnsubscribe(args); break; case "execute": await executeUnsubscribe(args); break; case "batch": await batchUnsubscribe(args); break; default: console.error( `Usage: fastmail unsubscribe <show|execute|batch>\n\nSubcommands:\n show Show unsubscribe methods for an email\n execute Execute unsubscribe for an email\n batch Batch unsubscribe from newsletters`, ); process.exit(1); }}
async function handleAttachment( subcommand: string | null, args: string[],): Promise<void> { switch (subcommand) { case "list": await listAttachments(args); break; case "get": await getAttachment(args); break; case "download": await downloadAllAttachments(args); break; default: console.error( `Usage: fastmail attachment <list|get|download>\n\nSubcommands:\n list List attachments on an email\n get Download a specific attachment\n download Download all attachments from an email`, ); process.exit(1); }}
async function handleThread( subcommand: string | null, args: string[],): Promise<void> { switch (subcommand) { case "get": await getThread(args); break; case "modify": await modifyThread(args); break; case "attachments": await threadAttachments(args); break; default: console.error( `Usage: fastmail thread <get|modify|attachments>\n\nSubcommands:\n get Get a thread with all messages\n modify Modify labels/keywords on all emails in a thread\n attachments List all attachments in a thread`, ); process.exit(1); }}
async function handleUrl( subcommand: string | null, args: string[],): Promise<void> { await urlCommand(subcommand, args);}
async function handleDraft( subcommand: string | null, args: string[],): Promise<void> { switch (subcommand) { case "list": await listDrafts(args); break; case "get": await getDraft(args); break; case "create": await createDraft(args); break; case "update": await updateDraft(args); break; case "delete": await deleteDraft(args); break; case "send": await sendDraft(args); break; default: console.error( `Usage: fastmail draft <list|get|create|update|delete|send>\n\nSubcommands:\n list List all drafts\n get Get draft by ID\n create Create a new draft\n update Update an existing draft\n delete Delete a draft\n send Send a draft`, ); process.exit(1); }}
main().catch((err) => { console.error(err); process.exit(1);});