Source, step, sink
Every action is a one-step pipeline: a source fills it, a step does the work, a sink takes the result.
- Source — placeholders in the action's template: what you typed (
{query}), the text selected in the app you're in ({selection}), the clipboard ({clipboard}), text read off a screen region ({screenshot}), and the clock ({date},{time},{now:…}). - Step — the action's
type: open a URL, ask an AI, run a shell command, fill a text template, run an Apple Shortcut, translate, start a timer, tidy a folder, or open one of Arci's tools. - Sink — the action's
output: show the result in Arci's panel, copy it, paste it into your app, or save it as a note.
You run an action by typing in Arci's bar and pressing ⇥ to pick it, by typing its keyword, or with its own global hotkey.
Where actions live
All your actions are in one file:
~/Library/Application Support/Arci/actions.json
- First run. Arci writes its default actions there (they're listed under the defaults). From then on the file is yours.
- Edit it anywhere. Settings → Actions → Edit JSON opens it in your editor. Arci watches the folder and reloads the moment you save — there's no restart and no import step.
- Settings writes the same file. Turning an action off in Settings writes
"enabled": falseinto it; editing a field there saves it back. Hand edits and Settings edits never fight. - A broken file is never overwritten. If the file isn't valid JSON, Arci keeps running its built-in defaults, Settings says the file has a syntax error, and nothing is saved over it until you fix it. Editors that save in two steps are fine: Arci waits a moment and reads again before deciding a file is broken. Delete the file and Arci writes the defaults again.
- An action Arci can't run is skipped. An unknown
type, or a missing required field, leaves that one action out of the panel; the rest of the file still works. - New built-in actions arrive once. When an update ships a new default action, Arci adds it to the end of your file one time, keeping the previous file beside it as
actions.json.bak. Delete it and it stays deleted.
The document
The file is one object with one key, actions: a list of actions in the order you want them. A menu groups more actions as its children.
{
"actions": [
{
"id": "youtube",
"title": "Search YouTube",
"type": "url",
"url": "https://www.youtube.com/results?search_query={query}",
"keyword": "yt"
},
{
"id": "write",
"title": "Writing",
"type": "menu",
"children": [
{
"id": "write.shorter",
"title": "Make Shorter",
"type": "ai",
"prompt": "Rewrite this in half the words, keeping its meaning:\n\n{query}"
},
{
"id": "write.formal",
"title": "Make Formal",
"type": "ai",
"prompt": "Rewrite this in a formal, polite tone:\n\n{query}"
}
]
}
]
}
Order matters. The panel lists actions in this order, with one exception: searches — url actions that use {query} — are shown together under Search, where the first of them is. The file stays as you wrote it. When what you typed matches nothing and you press ↩, Arci runs the first enabled search — out of the box, Search Google. Move another search above it to make that the fallback.
A document holds at most 200 actions, counting sub-actions, and menus nest at most 3 levels: action › sub-action › sub-sub-action.
Common fields
These fields work on every action, whatever its type. The fields each type adds are under Types.
| field | required | what it is |
|---|---|---|
id | recommended | A short unique name: lowercase letters, digits, dots, dashes and underscores, starting with a letter or digit — youtube, write.shorter. Up to 64 characters. Usage counts and history follow it, so a renamed title keeps its history. Without one, Arci uses the title in lowercase. |
title | yes | What the panel shows. Up to 60 characters; two or three words read best. |
type | yes | The step: url, ai, shell, text, shortcut, translate, files, processes, color, emoji, find, timer or menu. |
enabled | no | false hides the action without deleting it. Leave it out for on. |
keyword | no | One word you type before your text to run this action directly — see Keywords. |
hotkey | no | A global keyboard shortcut that runs it from any app — see Hotkeys. |
match | no | A pattern for the text the action is made for: text that's all of it gets the action first — see Offered by itself. |
children | menus only | The actions a menu groups. |
output | no | Where the result goes, for the types that have a result — see Outputs. |
label | no | With "output": "note": the notes folder it goes in. |
Fields a type doesn't use are a mistake, not a no-op: a file that names a field Arci doesn't know, or gives output to a url action, fails the checks Arci and its tools run. Keep each action to the fields listed for its type.
Placeholders
Placeholders are the sources. Put them in an action's template — its url, prompt, command, text or input — and Arci fills them when the action runs.
| placeholder | becomes | needs |
|---|---|---|
{query} | What you typed in the bar. When you pasted text into an empty bar (it shows as a chip), it's the pasted text. | — |
{selection} | The text selected in the app you were in when you opened Arci. Nothing selected → Arci says so and doesn't run. | Accessibility |
{clipboard} | The text on the clipboard. No text there → Arci says so and doesn't run. | — |
{screenshot} | Text read from a part of the screen: Arci steps aside, you drag over the area, and Apple's Vision framework reads the text on your Mac. The image is deleted straight away; esc cancels. Only the text is used — no image reaches an AI. | Screen Recording |
{date} | Today, as 2026-10-16 — sorts well in file and folder names. | — |
{time} | The time, 24-hour: 14:05. | — |
{now} | Both: 2026-10-16 14:05. | — |
{now:pattern} | The moment in any date pattern, in your Mac's language: {now:EEEE d MMMM} → Friday 16 October, {now:yyyy-MM} → 2026-10. Up to 64 characters of pattern. | — |
The clock placeholders use your Mac's time zone; {date}, {time} and {now} always use the fixed formats above, whatever your language or calendar.
How each type fills them
A value is made safe for where it lands, so you never quote or encode placeholders yourself:
| type | a value goes in |
|---|---|
url | Percent-encoded, including &, +, =, ? and # — tom & jerry stays one search. |
shell | In single quotes, with any quote inside escaped — it reaches your command as one argument. Write echo {query}, not echo "{query}". |
ai, text, shortcut, translate, timer | As it is. |
Arci fills a template in one pass: text that comes in through a placeholder is never scanned again, so selected text that happens to contain {clipboard} stays literal. A brace that isn't one of the placeholders above is left alone.
Outputs
output is the sink. It applies to the types that make a result — ai, shell, text, shortcut and translate — and is one of:
| output | the result |
|---|---|
"panel" | Shows in Arci's panel as it arrives. The default — leave output out for this. ⌘C copies it. |
"clipboard" | Shows in the panel and is copied when it's done. |
"paste" | Replaces your selection in the app you were in. Arci shows the result first and pastes only when you press ↩, so nothing lands in your document unseen. Needs Accessibility. |
"note" | Saved as a new Markdown note in your notes folder, named after its first line. ⌘Z right after takes it back. |
label files a note under one folder inside your notes folder, like "label": "Reading list". It's a folder name: up to 40 characters, no / or :, not starting with a dot, no spaces at either end. It may use the clock placeholders — "label": "Journal {now:yyyy-MM}" starts a new folder each month. label only goes with "output": "note".
{
"actions": [
{
"id": "read-later",
"title": "Read Later",
"type": "text",
"text": "{query}",
"output": "note",
"label": "Reading list"
}
]
}
Keywords
A keyword is one word you type before your text to run the action directly. With "keyword": "yt", typing yt lofi beats and pressing ↩ searches YouTube for "lofi beats".
- One word, no spaces, up to 24 characters. Matching ignores case.
- The keyword alone, then ↩, pins the action: the bar shows its name and whatever you type next is its input.
- An action that doesn't use
{query}runs as soon as you press ↩ on its keyword. A menu's keyword opens its sub-actions. - Your keyword beats Arci's own words for that first word —
clip(clipboard history) and the timer words (timer,alarm,remind,pomodoro,stopwatch) — and the calculator. - Two actions with the same keyword: the first one in the file wins, and Settings → Actions flags the second. It's never an error in the file.
Hotkeys
A hotkey runs the action from any app, without opening Arci first. It's written as modifiers and one key joined by +:
"ctrl+opt+g" "cmd+shift+k" "ctrl+cmd+space" "f5"
- Modifiers:
ctrl(orcontrol),opt(oroption,alt),shift,cmd(orcommand), each at most once. Arci writes them in macOS order — ⌃⌥⇧⌘ — whatever order you type. - The key, last: one character (
g,1,/), a named key —space,return,tab,escape,delete,left,right,up,down— orf1tof12. - At least one of ctrl, opt or cmd — or an F-key, which may stand alone.
Arci then refuses, with the reason in Settings → Actions:
- ⌘ with a single character, and ⇧⌘3, ⇧⌘4, ⇧⌘5, ⇧⌘Z — every app's own shortcuts. Add ⌃ or ⌥.
- A combination macOS or another app already holds.
- Arci's own summon shortcut or its Clipboard History shortcut.
- A combination an earlier action in the file already has — the first one wins.
What a hotkey does. An action that needs nothing typed runs at once — fix the selection in the app you're in, copy screen text, start a preset timer. An action that uses {query} opens Arci pinned to it, so you type and press ↩; if Arci is already open with text in the bar, it runs on that. A menu's hotkey opens its sub-actions. The same hotkey again puts Arci away.
Two defaults ship with hotkeys: Copy Screen Text on ⇧⌘8 and Emoji & Symbols on ⌃⌘Space.
Offered by itself
A match is a pattern — a regular expression — for the kind of text an action is made for. When the text you select, paste or type is all of it, the action comes first: the first chip over a selection and the top row in the list — one click, or ↓ ↩; ↩ on its own still does what it always did, so TCP/IP fitting the GitHub pattern still searches the web. Other text never sees it; type its name to use it anyway.
{
"id": "jira",
"title": "Open Issue",
"type": "url",
"url": "https://yourteam.atlassian.net/browse/{query}",
"match": "[a-z]+-[0-9]+"
}
- Only for an action that reads
{query}— atranslate,shortcutortimeraction reads it unless itsinputsays otherwise. On any other action it's ignored. - The whole text has to fit —
ARCI-42, notsee ARCI-42— and case doesn't matter. Text over 500 characters is never tried. - Up to 200 characters. Write
[0-9]and[.]rather than backslash escapes: in JSON every backslash has to be doubled. - Arci ships four: Open on GitHub for
owner/repo, Open on npm for@scope/package, Open DOI and Look Up IP. Settings → Actions tags them auto; give your own one in an action's Offer it for field, or ask the Builder: "open a selected Jira key on yourteam.atlassian.net".
Types
One section per type. "Required" means the action is skipped without it.
url — open a link
↩ fills the template and opens it the way macOS opens any link: https: in your default browser, other schemes in the app that owns them (mailto:, maps:, an app's own scheme). Arci steps aside. A URL with no placeholder is a bookmark.
| field | required | what it is |
|---|---|---|
url | yes | The URL template, up to 2048 characters. It needs a scheme such as https:; javascript:, data: and vbscript: aren't allowed. Placeholders are percent-encoded. |
{
"actions": [
{
"id": "wikipedia",
"title": "Wikipedia",
"type": "url",
"url": "https://en.wikipedia.org/w/index.php?search={query}",
"keyword": "w"
}
]
}
ai — ask an AI
↩ sends the filled prompt to your AI and streams the answer into the panel, formatted. Type in the bar to ask a follow-up in the same conversation. Which AI answers is your choice in Settings → AI: Apple's on-device model, or a provider with your own key. An action can name a provider or model to override that.
| field | required | what it is |
|---|---|---|
prompt | yes | The prompt template, up to 4000 characters. Placeholders go in as they are. |
provider | no | Who answers this action: "apple" (on this Mac, Apple Intelligence), "anthropic", "openai", "gemini" or "openrouter". The providers use your key from Settings → AI. Leave it out to use your default. |
model | no | A model id for that provider, up to 100 characters, e.g. a specific Claude or GPT model. Leave it out for the provider's default. |
output | no | panel, clipboard, paste or note. |
{
"actions": [
{
"id": "fix-selection",
"title": "Fix Selection",
"type": "ai",
"prompt": "Fix the grammar, spelling and punctuation of the following text. Return only the corrected text, nothing else.\n\n{selection}",
"output": "paste",
"hotkey": "ctrl+opt+g"
}
]
}
shell — run a command
↩ runs the command with /bin/zsh -lc — a login shell, so tools from Homebrew and ~/.local/bin are on the path — and streams what it prints (standard output and errors) into the panel. It gets no input, so it can't wait for you to type. esc stops it. A command that exits with an error shows [exit N] at the end and isn't kept in history.
| field | required | what it is |
|---|---|---|
command | yes | The command, up to 2000 characters. Each placeholder becomes one single-quoted argument — don't add quotes around it. |
output | no | panel, clipboard, paste or note. |
A shell action runs with your permissions, like anything you type in Terminal. Read a command before you add it, especially one written by someone else or by an AI.
{
"actions": [
{
"id": "base64",
"title": "Base64 Encode",
"type": "shell",
"command": "printf %s {query} | base64",
"output": "clipboard"
}
]
}
text — fill a template
The filled template is the result, as written: snippets, boilerplate, date stamps, reformatting what you typed. With "output": "paste" it types into your app; with "note" it saves what you typed as a note.
| field | required | what it is |
|---|---|---|
text | yes | The template, up to 4000 characters. \n in the JSON string is a new line. |
output | no | panel, clipboard, paste or note. |
{
"actions": [
{
"id": "today",
"title": "Insert Date",
"type": "text",
"text": "{now:EEEE, d MMMM yyyy}",
"output": "paste",
"keyword": "today"
}
]
}
shortcut — run an Apple Shortcut
↩ runs one of your shortcuts from the Shortcuts app by name, through macOS's own shortcuts tool, and shows its text output as the result. Shortcuts asks for its own permissions; esc stops the run.
| field | required | what it is |
|---|---|---|
shortcut | yes | The shortcut's exact name, up to 100 characters. Settings → Actions offers a picker of yours. |
input | no | What the shortcut receives as text, up to 2000 characters. Defaults to {query}; an empty result sends no input. |
output | no | panel, clipboard, paste or note. |
{
"actions": [
{
"id": "add-to-things",
"title": "Add to Things",
"type": "shortcut",
"shortcut": "Add to Things",
"input": "{selection}"
}
]
}
translate — translate text
↩ translates and shows the translation; ↩ again copies it. Ending what you type with to <language> — good morning to malayalam — picks the language there and then. Otherwise it's the action's to, or your Mac's language; text that's already in your Mac's language goes into the other language you used last.
By default the text is sent to Google Translate. When Google is limiting requests or can't be reached, your Mac translates it instead with Apple's on-device translation, and the panel says which one did. "engine": "device" never uses Google: nothing leaves your Mac, for the languages Apple translates on-device.
| field | required | what it is |
|---|---|---|
input | no | What to translate. Defaults to {query}; {selection} or {screenshot} translate what's on screen. |
to | no | The language to translate into, as Google writes its code: "ml", "es", "zh-CN", "haw". Up to 16 characters. |
engine | no | "google" (the default, with your Mac stepping in) or "device" (your Mac only). |
output | no | panel, clipboard, paste or note. |
Up to 40,000 characters at a time; long text goes to Google in pieces of about 4,500.
{
"actions": [
{
"id": "to-hindi",
"title": "Hindi In Place",
"type": "translate",
"input": "{selection}",
"to": "hi",
"output": "paste"
}
]
}
files — tidy a folder
↩ shows a plan: the folder's loose files sorted by kind into Images, Videos, Audio, Installers, Archives, Code and Documents. ↩ again moves them; ⌘Z puts them back, then or later. It only ever moves — nothing is deleted or overwritten (a clashing name gets a suffix) — and it leaves alone subfolders, hidden files, kinds it isn't sure of, and downloads still in progress. macOS may ask once to let Arci into Desktop, Documents or Downloads.
| field | required | what it is |
|---|---|---|
folder | yes | A folder inside your home, written ~/… — "~/Downloads", "~/Desktop/Screenshots". Not your home itself, not ~/Library, nothing hidden. Up to 200 characters. |
organize | no | How to sort: "kind", the default and only choice so far. |
{
"actions": [
{
"id": "tidy-downloads",
"title": "Tidy Downloads",
"type": "files",
"folder": "~/Downloads",
"organize": "kind"
}
]
}
processes — quit a process
Opens Arci's Kill Process screen: your running processes by CPU or memory, filtered by what you type. ↩ asks one to quit; ⌥↩ pressed twice forces it. No other fields — the default Kill Process action is this type.
{
"actions": [
{
"id": "kill",
"title": "Kill Process",
"type": "processes",
"keyword": "kill"
}
]
}
color — pick a color
Shows the macOS color loupe. The color you click is copied as hex (or the format you chose in Arci Colors) and kept in Arci Colors. No other fields.
{
"actions": [
{
"id": "pick-color",
"title": "Pick Color",
"type": "color",
"hotkey": "ctrl+opt+c"
}
]
}
emoji — the emoji picker
Opens Arci's Emoji & Symbols picker — emoji, Mac keys, arrows, math and currency signs — searched by what you type; ↩ pastes the one you choose into your app. No other fields. The default one has the hotkey "ctrl+cmd+space", taking over macOS's own emoji shortcut.
{
"actions": [
{
"id": "emoji",
"title": "Emoji & Symbols",
"type": "emoji",
"hotkey": "ctrl+cmd+space"
}
]
}
find — search your files
Opens Arci's Search Files screen: files in your home folder and iCloud Drive with a word in their name that starts with what you type, from Spotlight's index on your Mac. With nothing typed, it lists what you opened lately. ↩ opens one, ⌘↩ shows it in Finder. No other fields. The default Search Files action is this type, and a ~ at the start of the bar opens it too.
{
"actions": [
{
"id": "search-files",
"title": "Search Files",
"type": "find",
"hotkey": "ctrl+opt+f"
}
]
}
timer — timers, alarms, reminders
Starts a timer, alarm or reminder from its filled input, read just like the bar reads one: 25m tea is a timer, alarm 7am an alarm, in 1 hour to stretch and at 5pm call Mom are reminders. It rings with a chime and a notification and counts down in the menu bar.
A fixed input makes a preset ("25m Focus", "in 1 hour to {query}"). With {selection} or {clipboard}, text that names a time — Webinar: Oct 21 at 10:00 AM PT — sets a reminder 10 minutes before it. No output.
| field | required | what it is |
|---|---|---|
input | no | What to start, up to 2000 characters. Defaults to {query}. |
{
"actions": [
{
"id": "remind-selection",
"title": "Remind Me",
"type": "timer",
"input": "{selection}",
"hotkey": "ctrl+opt+r"
}
]
}
Cookbook
Each recipe is a complete file with one action. To use one, copy the action — the part inside the list — into your own actions list.
Explain what's on the clipboard
{
"actions": [
{
"id": "explain",
"title": "Explain Clipboard",
"type": "ai",
"prompt": "Explain this simply, in a few short paragraphs:\n\n{clipboard}"
}
]
}
Summarize the selection, on this Mac only
{
"actions": [
{
"id": "summary-here",
"title": "Summarize Here",
"type": "ai",
"provider": "apple",
"prompt": "Summarize the following in three bullet points:\n\n{selection}"
}
]
}
Draft a reply, ready to paste
{
"actions": [
{
"id": "reply",
"title": "Draft Reply",
"type": "ai",
"prompt": "Write a short, friendly reply to this message. Return only the reply.\n\nMessage:\n{selection}\n\nWhat I want to say: {query}",
"output": "paste"
}
]
}
Translate text on screen
{
"actions": [
{
"id": "screen-translate",
"title": "Translate Screen",
"type": "translate",
"input": "{screenshot}",
"hotkey": "ctrl+opt+t"
}
]
}
Translate privately
{
"actions": [
{
"id": "translate-here",
"title": "Translate Here",
"type": "translate",
"engine": "device"
}
]
}
Paste a signature
{
"actions": [
{
"id": "signature",
"title": "Signature",
"type": "text",
"text": "Best,\nAsha Rao\nasha@example.com",
"output": "paste",
"keyword": "sig"
}
]
}
Keep a journal, a folder a month
{
"actions": [
{
"id": "journal",
"title": "Journal",
"type": "text",
"text": "{time} {query}",
"output": "note",
"label": "Journal {now:yyyy-MM}",
"keyword": "j"
}
]
}
Save a quote from any app
{
"actions": [
{
"id": "keep-quote",
"title": "Keep Quote",
"type": "text",
"text": "{selection}\n\nSaved {date}",
"output": "note",
"label": "Quotes",
"hotkey": "ctrl+opt+q"
}
]
}
Your Mac's address on the network
{
"actions": [
{
"id": "local-ip",
"title": "Local IP",
"type": "shell",
"command": "ipconfig getifaddr en0",
"output": "clipboard"
}
]
}
Count the words on the clipboard
{
"actions": [
{
"id": "word-count",
"title": "Word Count",
"type": "shell",
"command": "pbpaste | wc -w"
}
]
}
A focus timer on one key
{
"actions": [
{
"id": "focus",
"title": "Focus 25",
"type": "timer",
"input": "25m Focus",
"hotkey": "ctrl+opt+f"
}
]
}
Remind me in an hour
{
"actions": [
{
"id": "in-an-hour",
"title": "In An Hour",
"type": "timer",
"input": "in 1 hour to {query}",
"keyword": "later"
}
]
}
Tidy the Desktop
{
"actions": [
{
"id": "tidy-desktop",
"title": "Tidy Desktop",
"type": "files",
"folder": "~/Desktop"
}
]
}
Start an email
{
"actions": [
{
"id": "new-mail",
"title": "New Email",
"type": "url",
"url": "mailto:?subject={query}"
}
]
}
Look up a word
{
"actions": [
{
"id": "define",
"title": "Define",
"type": "url",
"url": "dict://{query}",
"keyword": "def"
}
]
}
Limits and checks
Arci and its tools check a file against these rules. Hand-written files that follow them behave the same everywhere.
| what | limit |
|---|---|
| actions in a file, counting sub-actions | 200 |
| menu depth | 3 levels |
id | 64 characters |
title | 60 characters |
url | 2048 characters |
prompt, text | 4000 characters |
command, input | 2000 characters |
shortcut | 100 characters |
model | 100 characters |
folder | 200 characters |
keyword | 24 characters, one word |
label | 40 characters, one folder name |
to | 16 characters, a language code |
Every text field must be non-empty when present. Leave a field out rather than setting it to "".
The defaults
A fresh install writes this file. It's a good starting point to copy from.
{
"actions": [
{
"id": "google",
"title": "Search Google",
"type": "url",
"url": "https://www.google.com/search?q={query}"
},
{
"id": "ai",
"title": "Ask AI",
"type": "menu",
"children": [
{
"id": "ai.ask",
"title": "Ask",
"type": "ai",
"prompt": "{query}"
},
{
"id": "ai.grammar",
"title": "Fix Grammar",
"type": "ai",
"prompt": "Fix the grammar, spelling and punctuation of the following text. Return only the corrected text, nothing else.\n\n{query}"
},
{
"id": "ai.summarize",
"title": "Summarize",
"type": "ai",
"prompt": "Summarize the following concisely:\n\n{query}"
}
]
},
{
"id": "note",
"title": "Save to Notes",
"type": "text",
"text": "{query}",
"output": "note"
},
{
"id": "translate",
"title": "Translate",
"type": "translate"
},
{
"id": "kill-process",
"title": "Kill Process",
"type": "processes"
},
{
"id": "pick-color",
"title": "Pick Color",
"type": "color"
},
{
"id": "screen-text",
"title": "Copy Screen Text",
"type": "text",
"text": "{screenshot}",
"output": "clipboard",
"hotkey": "shift+cmd+8"
},
{
"id": "emoji",
"title": "Emoji & Symbols",
"type": "emoji",
"hotkey": "ctrl+cmd+space"
},
{
"id": "search-files",
"title": "Search Files",
"type": "find"
},
{
"id": "app-store",
"title": "Search App Store",
"type": "url",
"url": "macappstore://apps.apple.com/search?term={query}"
}
]
}
Writing actions with your own AI
This whole reference is plain text at arci.prayukta.com/llms.txt. Paste it into the AI you use, describe the action you want, and paste the reply into actions.json. Read what it wrote before you save — especially a shell command.
Or let Arci write it: in Settings → Actions, describe what you want and Arci Builder builds it, shows you the changes, and saves nothing until you apply them — 20 builds a month free, unlimited with Arci Pro (coming soon). Turn on Share my builds below the Build field to help improve it; the privacy policy says what's kept.