Actions ▸ Reference

Standard library reference

Every action that ships inside the app, section by section, with its own help. This page is generated from the library the app actually carries, so it lists what your copy has.

These are the actions the app builds from code at every launch. They are read-only: to change one, take a copy in the Actions pane, which lands above the library as one of your own and takes over that keyword.

Each is named by its keyword, which is what a pipeline writes, what a trigger names, and what a shortcut binds:

$1 | trim | upper
$2 | split , | first
$1 | addtz UTC | totz America/New_York | strftime "%F %T"

A few of these read the entry rather than the text handed to them — undo and ocr — so they have to be the first filter in a pipeline. Two of them, now and utcnow, read nothing at all, which is what makes them somewhere to start: | now | strftime %H:%M works on an empty history. forget, unfmt and html change no characters, so they can sit anywhere in a chain. And md-rich is the one filter here that makes formatting — it reads the text as Markdown and puts what that renders to on the clipboard, so it pastes as headings and bullets rather than as the marks that describe them.

General

undoReads the entry

Go back to an earlier value

Reads what the entry the pipeline started from used to hold, instead of what it holds now. undo 1 is the value before the current one, undo 2 the one before that, and undo -1 the first value it ever held. With no number it goes back once.

This is the way back from a trigger that fired when it should not have: the text as it was copied is still in the entry, behind what the trigger made of it.

$1 | undo

A variable is put back by sending the result to itself, which is recorded like any other change — so an undo is itself undoable:

$signature | undo 1 > $signature

Because it reads the entry rather than the text, undo has to be the first filter in a pipeline; anything before it would have its work thrown away. Filters after it work on the older value as usual.

ocrReads the image

Read the text in an image

Hands on the words in a picture. Every image copied to the clipboard is read on its way into the history, and this is how what it said is got at:

$1 | ocr
$2 | ocr | trim > $address

That makes it the way to get a serial number, an error message or an address out of a screenshot without typing it out. What comes back is text like any other, so the rest of the library follows it:

$1 | ocr | join-lines ", "

The reading is also what makes a picture findable: typing an invoice number into the overlay finds the screenshot of the invoice, without ocr being run at all.

Because it reads the entry's picture rather than the text it was handed, ocr has to be the first filter in a pipeline — nothing produces an image for it to read, so anything before it would only have made text for it to ignore. For the same reason it is a filter you type rather than one the overlay offers: the list of actions only appears while the newest item is text, which is exactly when this one has nothing to read.

A picture with no words in it says so rather than handing back nothing, as does one copied before the app could read images, and one whose reading has not come back yet — it happens in the background, so a very large screenshot can take a moment after it is copied.

forgetHistory

Keep it out of the history

Passes the text through exactly as it was given, and asks for it not to be kept: the entry it ran on is dropped from the clipboard history, and nothing the run produced is put back there.

The text stays on the clipboard. Forgetting is about what PasteDaemon remembers, not about what you copied — you can still paste it, once, wherever you were going to.

Name it from a trigger to keep a whole shape of thing out of the history. A trigger that matches what your password manager copies, or an API token, or a one-time code, will drop every such copy the moment it is made:

Matches regex   ^[A-Za-z0-9_-]{32,}$
Actions         forget

It can also be run on something already copied — from the menu bar, from the overlay, or by hand — which is the way to take one thing back out again:

$1 | forget

A variable is never deleted by it: variables are written down on purpose and kept until you delete them, so $token | forget keeps the run out of the history and leaves ${token} alone.

It changes no text, so it can sit anywhere in a chain — $1 | trim | forget puts the trimmed text on the clipboard and keeps both it and the untrimmed original out of the history.

unfmtplainText

Paste without formatting

Puts the text on the clipboard with none of the shapes it was copied in — no bold, no links, no colour, no fonts. The characters are untouched; what changes is what the app you paste into is offered.

Run it on the newest copy from the overlay or the menu bar, or name a row:

$2 | unfmt

Run by hand it is a decision about this paste: the entry keeps how it was copied, so pressing ↩ on that row later pastes it dressed as it always was.

Named from a trigger it is a decision about the copy, because a trigger fires when something is copied and there is no paste yet to decide about. The copy is stored plain and stays plain however often it is used:

Matches regex   .
Actions         unfmt

Text that was never formatted is already plain, so running it there costs nothing and does nothing.

htmlhtml

Paste as HTML

Puts the HTML of the copy on the clipboard in place of the richer shapes offered beside it, with the plain text as always. A copy from a web page carries the page's own markup and that is what goes back; a copy from a word processor carries rich text instead, which is converted.

$1 | html

It is the companion to unfmt: one narrows a copy to the text, the other to the markup. Both leave the characters exactly as they were, and both can be run by hand — for this paste — or named from a trigger, for the copy itself.

Because formatting describes one exact run of characters, it only answers for text that is still the text that was copied. $1 | upper | html has no HTML for what it produced, and says so rather than pasting the old copy's markup over the new text. A copy that was never formatted has no HTML either.

evaleval

Expand the references in the text

Reads the text it is handed as if it had been written out in quotes: every $name, $1 and $( … ) in it stands for what it names, exactly as it would inside a "…" in the bar.

That is what turns text that was stored into text that says something. A variable holding a template is a sentence with a $ in it until this runs on it:

$greeting                  Dear $name,
$greeting | eval           Dear Marion,

Everything a quoted piece of text can hold works here, including a command:

$line | eval               Sent $(| now | strftime %H:%M)

A name that stands for nothing is left as it was written — the same answer an argument gets, so a $ that was only ever a dollar sign survives. $$ is how to write one that must not expand.

Over a list it is map that runs it, an item at a time:

@letters | map eval

It may not reach itself: text that evaluates to text that evaluates would never finish, so a second eval inside one is refused rather than run.

Text

++Join

Join text on the end

Joins text onto the end of what it is given, with nothing between — a space goes in where a space is wanted.

$1 ++ "!"
$1 | ++ "!"            the same thing
$1 ++ " — " ++ $2      two clipboard items, with a dash between
$1 | trim ++ $(| strftime %F)

The words after it are the text to join, so quotes are needed only to keep the spaces at either end of it. A $( … ) runs a pipeline of its own and joins on what it produced.

Like the sums, it needs a space either side: $1 ++5 is refused rather than read as a join, so that an argument beginning with a + stays the argument it is.

trimSubstitution

Trim whitespace

Removes whitespace from the start and end of the text, leaving what is in the middle alone.

Handy at the front of a pipeline, since text copied out of a web page or a terminal usually arrives with something invisible attached:

$1 | trim | json

collapse-spacesSubstitution

Collapse runs of spaces and tabs

Replaces every run of spaces and tabs with a single space. Line breaks are left alone, so the shape of a paragraph survives while the ragged spacing of copied-out columns does not.

Pair it with trim to tidy a line completely.

squeeze-blanksSubstitution

Collapse repeated blank lines

Turns any run of two or more blank lines into a single blank line. Paragraph breaks stay; the gaps left behind by deleting things do not.

strip-htmlSubstitution

Remove HTML tags

Deletes anything between < and >, leaving the text between the tags.

This is a regex, not a parser: it is meant for pulling the words out of a snippet of markup, and it will happily eat a < that was never a tag. Do not use it to sanitise anything.

tidyPipeline

Trim, collapse spaces and blank lines

Everything that makes copied text untidy, in one go: the whitespace at either end, runs of spaces and tabs in the middle, and repeated blank lines.

This one is a pipeline action — it is nothing but three other actions in a row:

| trim | collapse-spaces | squeeze-blanks

The leading | is the text it was handed, so it can be run on the clipboard, named by a trigger, or used as a filter like any other. Everything in it is a substitution, so it is pure and previews live as you type.

sort-linesScript

Sort lines

Sorts the lines in ascending order, the way sort does — byte order, so capitals come before lowercase.

Follow it with unique-lines to get a sorted list with no repeats:

$1 | sort-lines | unique-lines

unique-linesScript

Drop repeated lines

Keeps the first appearance of each line and drops the rest. Unlike uniq, the input does not have to be sorted, and the order of what survives is the order it arrived in.

reverse-linesScript

Reverse the order of the lines

Last line first. Useful for reading a log that was written oldest-first.

number-linesScript

Number the lines

Puts a right-aligned line number and two spaces in front of every line, including blank ones.

join-linesScript

Join the lines into one

Runs the lines together into a single line.

The separator is the filter's argument, and defaults to a single space. Quote it to keep a trailing space or to use something with a space in it:

$1 | join-lines ", "
$1 | join-lines ,

countScript

Count lines, words and characters

Replaces the text with a count of it — 12 lines, 340 words, 2,110 characters — which makes it a filter to end a pipeline with rather than one to build on.

Characters are counted as characters, not bytes, so accented letters and emoji count once each.

Markdown

md-quoteScript

Quote the text

Puts > in front of every line, which is how Markdown says a blockquote.

A blank line inside gets a bare > rather than nothing, because a blank line with no > on it ends the quote — two paragraphs quoted that way come out as one quote and one paragraph beside it. That is the whole reason this is a filter rather than something to type.

Text that is already quoted gains a second >, which is what quoting a quote looks like.

md-unquoteSubstitution

Remove one level of quoting

Takes the > off the front of every line — the way back out of a reply, a forwarded mail or a quoted message.

One level at a time, so text quoted twice needs it twice; that is what makes it usable on a reply inside a reply without flattening the lot. The space after the > goes with it where there is one, and up to three spaces in front are allowed, which is what Markdown itself allows.

$1 | md-unquote | md-unquote

md-bulletScript

Make the lines a bulleted list

Puts - in front of every line, turning a copied column or a run of sentences into a list.

Blank lines are left blank rather than given a bullet of their own, so a gap between two groups stays a gap instead of becoming an empty item.

md-numberedScript

Make the lines a numbered list

The same as md-bullet with 1., 2., 3. instead of bullets.

The count skips blank lines rather than spending a number on them, so a gap in the middle of a list does not leave a hole in the numbering.

md-codeScript

Wrap the text in a fenced code block

Puts the text inside a fenced code block. The filter's argument is the language, and is left off where there is none:

$1 | md-code swift

The fence is made longer than the longest run of backticks in the text, so pasting a snippet that already contains a fence produces a block that holds it rather than one that ends in the middle of it. Three backticks where the text has none, five where it has four. This is the part that is worth a filter: it is exactly the case where doing it by hand goes wrong, and it goes wrong quietly.

md-tableScript

Tab-separated text to a table

Turns rows of tab-separated values into a Markdown table, with the first row as the header and the | --- | row that has to follow it.

Tabs are what a copy out of a spreadsheet is separated by, which is what this is for: select a range in Numbers or Excel, copy, and paste a table into a pull request. Another separator can be named as the argument:

$1 | md-table ,

A | inside a cell is escaped, since an unescaped one would split the cell in two, and a row with fewer cells than the header is padded with empty ones — a table whose rows are different lengths does not render at all.

md-escapeSubstitution

Escape the Markdown in the text

Puts a \ in front of the characters that would otherwise be read as markup, so text survives being pasted somewhere that renders Markdown. my_file_name stays my_file_name instead of turning file into italics.

The characters escaped are \, ` `, *, _, [, ], <, > and | — the ones that mean something **anywhere in a line**. #, - and 1.` are left alone: they only mean anything at the start of a line, and escaping every hyphen in a document to catch the two that begin one is a cure worse than the problem.

md-htmlMarkdown

Markdown to HTML

Reads the text as Markdown and hands back the HTML it describes, as characters — headings, lists, quotes, fenced code, links, bold and emphasis:

# Title            <h1>Title</h1>
- one              <ul>
- two              <li>one</li>
                   <li>two</li>
                   </ul>

What comes out is a fragment rather than a whole page, since that is what goes into a template, a CMS field or an email that is built out of parts. A fence that names a language keeps it, as the language- class a highlighter looks for.

This is the source. md-rich is the same conversion put on the clipboard as formatting instead, for pasting into something that shows it rather than something that stores it.

md-richMarkdown

Paste Markdown as formatted text

Reads the text as Markdown and puts what it renders to on the clipboard as formatting — so writing in Markdown and pasting into Mail, Notes, a document or a chat window gives real headings, real bullets and real links rather than the marks that describe them.

$1 | md-rich

The value it hands on is what the rendered document reads as — the marks gone, a list reading as its bullets. So the row reads and searches as what it renders to, an app that takes only text still gets that text, and an app that can take a shape gets the shape. It is the same bargain a script that declares its output makes, and it is held to the same rule afterwards — $1 | md-rich | upper pastes plain, because upper produced characters the formatting no longer describes.

Markdown with no markup in it renders to nothing worth having, and is left exactly as it was.

Lists

splitSplit

Split into a list

Divides the text into a list on a delimiter. The delimiter is the filter's argument, and with none given it splits on line breaks:

$1 | split ,          a, b, c  →  three items
$1 | split ", "       without the spaces
$1 | split            one item a line

A list is what @name holds, so this is how one is filled from something copied — > writes over what is there, >> adds to the end of it:

$1 | split , > @recipients
$1 | split , >> @recipients

And a list on the clipboard is pasted an item at a time — the first goes on now, the next after each paste — which is what makes $1 | split , a way to deal out a row of values into a row of fields. Copying something in the middle of that ends the sequence; reaching for the same list again carries on from where it had got to.

A list is text with a shape: its text is the elements, one to a line, so every other filter reads one as the lines it looks like. The shape describes that exact text and no other, so a filter that rewrites it produces text — | split is how the result becomes a list again.

joinjoin

Join a list into one string

Runs a list back together into a single piece of text, with the separator between the elements. The separator is the filter's argument and defaults to , :

@recipients | join            a, b, c
@recipients | join "; "       a; b; c
$1 | split , | join " and "

Handed plain text it reads the lines as the list, so $1 | join is a way to turn a column into a sentence without splitting it first.

A list is text with a shape: its text is the elements, one to a line, so every other filter reads one as the lines it looks like. The shape describes that exact text and no other, so a filter that rewrites it produces text — | split is how the result becomes a list again.

itemList

Take one item of a list

Takes a single element out of a list, by index. Lists count from zero: item 0 is the first, item 1 the second, and a negative index counts from the other end — item -1 is the last.

@recipients | item 1
@recipients[1]                the same item, written shorter
$1 | split , | item -1

@name[…] is this filter written on the reference and counts the same way, so @list[1] and | item 1 are one element.

Asking for an item the list does not have is an error naming the numbers it does run between, rather than an empty result that would look like an empty item. As everywhere, plain text is read as its lines, so $1 | item 2 is the third line.

first and last are this filter with the index already filled in — item 0 and item -1.

reverselistReverse

Reverse a list

Turns a list back to front: the last item first.

reverse @recipients
@recipients | reverse       the same thing
$1 | split , | reverse

Handed plain text it reads the lines as the list, the way every list filter does — so $1 | reverse is reverse-lines with a list coming out of it rather than text, which is what makes it worth having beside that one. On the clipboard that means the items are pasted from the bottom up.

An item with a line break inside it stays one item, which is the difference that matters once a list has been made: reverse-lines would turn such an item inside out.

continuelistContinue

Carry on down a list

Hands on what the clipboard has not been dealt yet of a list.

A list on the clipboard is pasted an item at a time, and copying something in the middle of that ends the sequence — which is the ordinary way to end up half way down one, since something has to be copied to fill in the field beside it. This is how the rest is asked for:

continue @recipients
@recipients | continue     the same thing

Naming the list itself always starts it again from the top. Carrying on is the rarer of the two and the one that would be surprising to get by accident, so it is the one that has to be said out loud.

What it hands over is the tail of the list, so a second continue finds where this one left off rather than starting again. A list nothing has been dealt out of, and one whose sequence ran to the end, both come back whole — there being nowhere in either to carry on from.

indexlistFrom

Take a list from an item onward

Hands on a list from a given item, dropping what came before it. Lists count from zero, so index 0 is the whole list, index 2 everything from the third on, and a negative index counts from the other end — index -2 is the last two.

index @recipients 2
@recipients | index 2      the same thing
index @recipients $next    from wherever a variable says

On the clipboard that means the sequence starts there: index @rows 3 picks up a form at its fourth field. Where the answer is "wherever it left off", continue says it without the counting.

Asking for an item the list does not have is an error naming the numbers it does run between, rather than an empty list that would look like an empty answer.

maplistMap

Run a filter over every item

Runs another filter over each element of a list, and hands back a list of what each one gave. The filter to run is map's first argument, and anything after it is that filter's own:

@recipients | map upper
@recipients | map trim
| map @recipients upper       the same thing
$1 | split , | map trim

The result is always the same length as the list that went in — one answer to one item, which is the whole of what mapping means. A filter that produces a list of its own therefore lands as one item holding that list's text rather than melting into the items around it.

An operator has to be quoted, because a pipeline splits its steps on one: write map "+" 5 and map "++" "!", not map + 5.

eval is the filter this exists for. A list of templates becomes a list of what they say:

@letters | map eval

A list is text with a shape: its text is the elements, one to a line, so every other filter reads one as the lines it looks like. The shape describes that exact text and no other, so a filter that rewrites it produces text — | split is how the result becomes a list again.

firstPipeline

The first item of a list

The first element — item 0 under a name worth reaching for.

@recipients | first

A variable holds one thing, so sending a list to a $name is an error rather than a guess at which item was meant. This is one of the two answers to it — join is the other, where all of them were meant:

@recipients | first > $to

lastPipeline

The last item of a list

The last element — item -1 under a name worth reaching for.

@recipients | last

Case

upperScript

UPPERCASE

Every letter in capitals. ASCII only, so accented letters are left as they are.

lowerScript

lowercase

Every letter in lowercase. ASCII only, so accented letters are left as they are.

titleScript

Title Case

Capitalises the first character of every run of non-space characters and lowercases the rest, so the QUICK brown fox becomes The Quick Brown Fox.

Spacing, tabs and line breaks are preserved exactly. It knows nothing about which small words a style guide would leave alone.

snakeScript

snake_case

Lowercases the text and joins the words with underscores. A capital inside a word is treated as the start of a new one, so myVarName becomes my_var_name, and anything that is not a letter or a digit becomes the separator.

kebabScript

kebab-case

snake with hyphens: Hello There becomes hello-there. This is also what a URL slug wants, which is what slug uses it for.

Encoding

base64Script

Encode as Base64

Base64-encodes the text as UTF-8, on one line with no wrapping.

base64-decode is the way back.

base64-decodeScript

Decode Base64

Decodes Base64 back to text. Whitespace in the input is ignored, so a wrapped block pasted out of a mail header still decodes.

Input that is not valid Base64 fails and leaves the text alone.

url-encodeScript

Percent-encode for a URL

Percent-encodes the text the way encodeURIComponent does, so it is safe to drop into a query string. &, =, / and ? are all escaped.

$1 | url-encode

url-decodeScript

Decode percent-encoding

Turns %20 and friends back into the characters they stand for.

md5Script

MD5 hash

The MD5 of the text as UTF-8, in lowercase hex, with no trailing newline included in what is hashed.

MD5 is broken for anything security-related. It is here for checksums and cache keys.

sha256Script

SHA-256 hash

The SHA-256 of the text as UTF-8, in lowercase hex, with no trailing newline included in what is hashed.

Math

+Arithmetic

Add a number

Adds to the number in the text.

$1 + 5
$1 | + 5      the same thing
$subtotal + $shipping

Every step of a pipeline runs in the order it is written, and a sum is an ordinary step — so there is no operator precedence here. $1 + 2 * 3 multiplies the sum by three; it does not add six. The search bar says so when a command mixes + or - with * or /, which is the only place the two readings differ.

Numbers are read and written plainly: a . for the point, no separator between the thousands, and a leading - for a negative. Text that is not a number — 12kg, $4.99, an empty clipboard — is an error rather than a guess.

-Arithmetic

Subtract a number

Subtracts from the number in the text.

$1 - 5
$1 - -5       which adds five

An operator needs a space either side of it, so that dateadd -30min and sed -E stay the arguments they are. $1 -5 is refused rather than read as a subtraction.

Every step of a pipeline runs in the order it is written, and a sum is an ordinary step — so there is no operator precedence here. $1 + 2 * 3 multiplies the sum by three; it does not add six. The search bar says so when a command mixes + or - with * or /, which is the only place the two readings differ.

Numbers are read and written plainly: a . for the point, no separator between the thousands, and a leading - for a negative. Text that is not a number — 12kg, $4.99, an empty clipboard — is an error rather than a guess.

*Arithmetic

Multiply by a number

Multiplies the number in the text.

$1 * 1.2      add 20% to a price
$1 * -1       flip the sign

Every step of a pipeline runs in the order it is written, and a sum is an ordinary step — so there is no operator precedence here. $1 + 2 * 3 multiplies the sum by three; it does not add six. The search bar says so when a command mixes + or - with * or /, which is the only place the two readings differ.

Numbers are read and written plainly: a . for the point, no separator between the thousands, and a leading - for a negative. Text that is not a number — 12kg, $4.99, an empty clipboard — is an error rather than a guess.

/Arithmetic

Divide by a number

Divides the number in the text.

$1 / 100
$total / $people

Division is the one sum that cannot always be written down exactly, so it is carried to 10 decimal places and rounded there. Everything else is exact: 0.1 + 0.2 is 0.3, not 0.30000000000000004, because the arithmetic is decimal rather than binary.

Dividing by zero is an error and leaves the text alone.

Every step of a pipeline runs in the order it is written, and a sum is an ordinary step — so there is no operator precedence here. $1 + 2 * 3 multiplies the sum by three; it does not add six. The search bar says so when a command mixes + or - with * or /, which is the only place the two readings differ.

Numbers are read and written plainly: a . for the point, no separator between the thousands, and a leading - for a negative. Text that is not a number — 12kg, $4.99, an empty clipboard — is an error rather than a guess.

JSON

jsonScript

Pretty print JSON

Re-indents JSON with two spaces per level, leaving the keys in the order they were written.

It parses first, so it is also a way to find out whether something is valid JSON at all: if it is not, the action fails and says where. The JSON copied trigger runs this automatically for anything that parses.

Pretty printing is idempotent, and the text as it was copied stays in the entry's history, so reformatting is never lost work.

json-compactScript

Compact JSON onto one line

The opposite of json: parses the JSON and prints it again with no whitespace at all, which is what a config file or a curl body usually wants.

json-escapeScript

Quote the text as a JSON string

Wraps the text in double quotes and escapes what has to be escaped — quotes, backslashes, newlines — so the result can be pasted straight in as the value of a JSON field.

The input is treated as plain text, whatever it looks like: {"a":1} comes back as the string "{\"a\":1}".

Time

nowThe time now

The time now

The time now, in this Mac's own zone, written as ISO 8601 so that everything here can read it back.

It reads nothing, which is what makes it a start rather than a step. Every command needs a source, so it is written with the leading | that means "the text at hand" — there just is not any:

| now                      2026-08-06T15:04:05-04:00
| now | strftime %H:%M     15:04
| now | strftime %F        2026-08-06
| now | dateadd +1w        a week today

Because it reads nothing, an empty clipboard history is no obstacle to it, nor is a picture sitting at the top of one.

With a zone named it answers in that zone — now UTC is utcnow, and now Asia/Tokyo is what time it is in Tokyo. Zones are named as they are for addtz.

A command that names the time twice names one time: the moment is taken once, when the command starts.

utcnowThe time now

The time now in UTC

The time now in UTC, written as ISO 8601 with the Z that says so — now in every other way.

| utcnow                   2026-08-06T19:04:05Z
| utcnow | strftime %FT%T  2026-08-06T19:04:05

This is the one to stamp anything that leaves the machine: a log line, a commit message, a ticket. now is the one to read off a clock in the room.

strftimeDate format

Reformat a date

Reads the text as a moment in time and writes it back out in the shape you ask for, by the rules of strftime(3).

What it will read: ISO 8601 (2026-08-06T15:04:05Z), a date on its own (2026-08-06), a log line's 2026-08-06 15:04:05, an email or HTTP header's Wed, 06 Aug 2026 15:04:05 +0000, a Unix time (1786000000, or milliseconds when it is thirteen digits or more), and failing all of those, a date written out in words anywhere in the text — August 6, 2026.

The format is the filter's argument, so it can be different every time:

$1 | strftime %A            Thursday
$1 | strftime "%B %e, %Y"   August  6, 2026
$1 | strftime %s            as a Unix time
$1 | strftime %FT%T%z       back to ISO 8601

Several words are one format, so the quotes above are only needed where you want the spaces kept exactly. With no argument it uses the format on the action itself, which is what a trigger uses too — triggers name actions without arguments, so a copy of this action with its own format is how dates get normalised on copy.

Times come out in the Mac's own time zone. Month and day names come out in English, so that a pipeline means the same thing on every machine it is run on.

addtzTime zone

Say which zone a date is in

Says where an unzoned date was written, leaving its clock alone. 2026-08-06 15:04:05 says three minutes past three without saying three o'clock where, so it is read as three o'clock here; addtz UTC says it was three o'clock in UTC all along.

$1 | addtz UTC
  2026-08-06 15:04:05
  → 2026-08-06T15:04:05Z

A date that already says where it is — an ISO 8601 offset, a Z, an email header's GMT, or a Unix time, which is an instant by itself — is an error rather than something to overwrite. Moving one of those to another zone is totz.

Zones can be named as UTC, as America/New_York, as PST — the coast that keeps daylight saving, not a fixed -08:00 — or as an offset like +05:30. With no zone named it uses the one on the action, and failing that the Mac's own.

This is the way to handle a server log, which is usually written in UTC and says so nowhere:

$1 | addtz UTC | totz America/New_York | strftime "%F %T %Z"
  2026-08-06 15:04:05
  → 2026-08-06 11:04:05 EDT

totzTime zone

Move a date to another zone

Moves a date to another zone: the same moment, read off another clock. Where addtz keeps the numbers and changes the moment, this keeps the moment and changes the numbers.

$1 | totz America/New_York
  2026-08-06T15:04:05Z
  → 2026-08-06T11:04:05-04:00

A date that never said which zone it was in is read as this Mac's time, as everything here reads one; addtz is how to say otherwise before converting.

Zones are named as they are for addtz, and with none named it moves the date to the zone on the action, or failing that to the Mac's own — which is how a time in a mail from someone three zones away becomes a time you can act on.

The result carries its new zone, so anything after it works in that zone: strftime writes the clock this left rather than moving it back.

dateaddDate offset

Move a date forwards or back

Moves a date by a span of time: a count and a unit, as many as you like, with an optional sign in front of the lot.

$1 | dateadd +2d       two days on
$1 | dateadd -30min    half an hour back
$1 | dateadd 1h30m     an hour and a half on
$1 | dateadd "-1mo 2d" a month and two days back

The units are s, min, h, d, w, mo and y, spelled out or abbreviated — sec, mins, hrs, days, weeks, months, yr all read as you would expect.

A lowercase m is minutes, as it is in Prometheus, systemd and Go. Months are mo: an uppercase M means months to some tools and minutes to others, and one shift key should not be the difference between a minute and forty-four thousand of them, so it is refused rather than guessed at.

Days and larger are counted on the calendar, not in seconds. A day later is this time tomorrow even where the clocks changed overnight and the day ran to 23 hours, and a month after the 31st is the last day of a month that has no 31st.

A date that said which zone it was in still says so afterwards, and one that did not still does not — moving a date is no answer to a question about zones, which is what addtz is for.

One limit worth knowing: a date carrying an offset-04:00, which is all ISO 8601 can carry — is counted in that offset, since an offset knows nothing of when the clocks change. Across a change, +1d there is 24 hours rather than 23 or 25. Text with no zone is counted in the Mac's own, which does know.

Web

remove-url-trackingSubstitution

Strip URL tracking parameters

Removes the utm_*, fbclid and gclid parameters a link picks up on its way to you, and leaves the rest of the query string alone — including the ? that starts it, so ?utm_source=news&id=7 comes back as ?id=7 rather than as a query nothing can read.

It edits the text rather than parsing a URL, so it works on a paragraph with several links in it.

hostSubstitution

Keep only a URL's host

Reduces https://www.example.com:8080/a/b?c=1 to www.example.com:8080. Text that does not start with a scheme is left as it is.

httpsSubstitution

Upgrade http:// to https://

Rewrites the scheme of every http:// URL in the text. Nothing else changes.

slugScript

URL slug

Lowercases the text and joins the words with hyphens, dropping punctuation — Hello, World! becomes hello-world. The same thing kebab does, under the name you reach for when you are naming a page.

Development

uuidScript

A fresh UUID

Ignores the text entirely and produces a new lowercase UUID. A filter that generates rather than transforms, which makes $1 | uuid a quick way to get one onto the clipboard.

Fun

upside-downScript

uʍop ǝpᴉsdn

Turns the text over. Every letter, digit and bracket is swapped for the character that looks like it standing on its head, and the whole lot is reversed so that it reads from the other end.

Hello, World!  →  ¡plɹoM 'ollǝH

Brackets and quotes turn with everything else — a ( comes back as a ) — so a flipped line still looks like a line rather than like a mistake. Anything with no upside-down twin, emoji included, is left as it is and simply moves to the other end. Several lines are turned as one piece of text, so the last line comes out first, which is what the page would look like held the other way up.

These are ordinary Unicode characters, so the result pastes anywhere that will take an accented letter. Running it twice does not give you back what you started with — nothing turns a Ɩ back into a 1 — but the text as it was copied is still in the entry, behind this, which is the way back:

$1 | undo