vivaanshahani.com

Caduceus documentation

Every feature and every setting. The same user reference is built into the app under Settings → Help, so it works offline too.

Plain-language guide for using Caduceus.

The staff

The floating mark and the ring around it.

The staff floats above every window and every space, including another app's full-screen. Hover it and up to six shortcuts fan out on an arc; click one to run it.

  • Single click — opens the Command Center.
  • Double click — starts dictation.
  • Right click — opens Settings.
  • Drag — move it anywhere; the arc flips to open away from the nearest edge.
  • F12 — show or hide it. Caduceus works fine with the staff off; the hotkey and menu-bar icon still reach everything.

Settings → General controls the docked edge, the delay before the ring opens, how long before it folds back, and whether the staff is shown at all.

The Command Center

One bar that searches, launches, calculates and routes.

Press ControlSpace anywhere. What you type is interpreted in this order:

  • An app namechrome launches it. Fuzzy, so chrm works.
  • Maths2+2, 18% of 240. Answers inline; Enter copies the result.
  • A prefix — see below. The longest match wins.
  • Anything else — searches the web with your configured engine.

Drag the bottom-right grip to resize the window; double-click it to hide. Esc closes.

Prefixes

Route a line somewhere specific.

Three ship by default:

  • / — ask your AI backend.
  • /v — search clipboard history.
  • /c — hand the task to an agent that can drive your screen.

Add your own under Settings → Command Center. A prefix can open a URL template ({query} is where your text lands), run a shell command, run AppleScript, or point at any built-in route. The longest matching prefix wins, so /c beats / regardless of list order.

Shortcuts

The single primitive behind the ring and the palette.

A shortcut opens a URL, launches an app, runs a command, runs AppleScript, or opens a built-in view. The same list powers the staff's ring and the Command Center's results — which is why everything is editable, including the ones Caduceus ships with.

  • Show in staff puts it on the ring. The ring draws six; Settings warns rather than silently dropping the rest.
  • Keywords are extra words that should match it in search.
  • Open in sends a URL to a specific browser and profile, overriding the default.
  • Icon is a built-in glyph, an uploaded image, or any emoji.

System monitor

CPU, memory, disks, network — and quitting things.

On the staff ring, or type system in the Command Center.

Live CPU and memory, per-disk free space, network throughput, uptime and load average, plus the heaviest processes sorted by CPU or memory. Hover a row for Quit (asks politely) and Force (does not).

Processes you do not own show "system" instead of buttons — terminating them needs privileges Caduceus does not have, and a button that always fails is worse than no button.

Voice and dictation

Hold to talk, or double-tap the staff.

Push-to-talk records while you hold the key; F1 and double-clicking the staff toggle it instead. A red dot appears on the staff while the microphone is live.

What happens to the transcript is decided by keyword groups — say "search" and it goes to the web, "clipboard" and it searches your history. Anything unmatched falls through to your AI model.

macOS asks for Microphone and Speech Recognition the first time. Both are on-device.

Clipboard history

Everything you copied, searchable.

Off by default. Once on, /v searches everything you have copied — text, images and file paths, with pinning.

It never leaves your machine and can be encrypted at rest. Add your password manager to the exclusion list: a clipboard history is a log of every password you copy.

AI

Optional. Everything but / and /c works without it.

Settings → AI has a Scan this Mac button that checks the default ports for Ollama, LM Studio, llama.cpp, Jan and vLLM, and asks Hermes Agent whether it is configured. One click connects what it finds.

Otherwise add any OpenAI-compatible endpoint by hand, cloud or local. Keys go to your OS keychain, never to a config file, and there is no command to read one back out.

One backend is primary — that is what / talks to. /c uses the computer-use backend, which should be Hermes if you want it to actually control the screen. Leave Ask before an agent controls this Mac on.

Full AI setup guide →

Hotkeys and function keys

What is bound, and what happens when something else holds it.

Two dedicated accelerators — Command Center and push-to-talk — plus the F1F20 table, which is the single place function keys are configured. F12 shows and hides the staff out of the box.

If another app already holds a key, Caduceus moves that action to a free alternative at startup and saves the change, rather than leaving a shortcut that silently does nothing. Settings shows a note when that happens, and Settings → Help always lists what is actually bound.

On a Mac, set Keyboard → Keyboard Shortcuts → Function Keys to use F-keys as standard function keys if you want them to reach Caduceus globally.

Appearance

Theme, accent, and the size of everything.

Light, dark or follow the system, plus the accent colour used across all three windows. The staff's size, ring reach, icon size and idle opacity are all adjustable — turn idle opacity up if you keep losing it on a busy desktop.

Privacy and what leaves your machine

Short answer: almost nothing.

The launcher, calculator, clipboard history, dictation and the system monitor are entirely local. Dictation uses macOS on-device speech.

Two things touch the network, both only when you ask: a web search opens your browser, and the / and /c prefixes talk to whatever AI backend you configured — which can be a model on your own machine.

API keys live in the OS keychain. There is no command to read one back out, so a compromised webview cannot exfiltrate them.

Write your own extension

One JavaScript file — metadata in a header comment, no build step.

Extensions add commands to the Command Center. Each extension is a single .js file you drop onto Settings → Extensions (or save into the extensions folder Caduceus opens from that tab). There is no separate manifest, no npm install, and no folder layout to get wrong.

1. Create the file

The @caduceus block must be the first comment in the file. Caduceus reads it without running your code, so you see the name and permissions before anything executes.

/**
 * @caduceus 1
 * name: Word Count
 * description: Count the words on your clipboard
 * author: you
 * permissions: clipboard
 */
export default async function (input, ctx) {
  const text = await ctx.clipboard.read();
  return `${text.trim().split(/\s+/).length} words`;
}

input is whatever the user typed after the command name. Return a string for a toast-style message, or an array of { title, subtitle?, action? } objects to show a list.

2. Declare permissions

Only these values are allowed — a typo fails at install time instead of widening access silently.

PermissionWhat you get
clipboardctx.clipboard.read() and .write(text)
networkctx.fetch(url, init)
selectionctx.selection() — current Finder selection
notificationsctx.notify(text)
shellctx.shell.run(command, input?, timeoutSecs?) — an arbitrary shell command
automationctx.automation.runAppleScript(script), .runShortcut(name, input?)
filesctx.files.read(path) and .write(path, content), under ~ or Caduceus app data
settingsctx.settings.get() and .set(settings) — all of them
commandsctx.commands.dispatch(paletteLine), .runTool(toolId, input)
aictx.ai.ask(prompt) — your configured backend, and your credits
shortcutsctx.shortcuts.run(shortcutId, query?)

Read this line before installing something. shell and automation mean the file can run commands and drive every app on your Mac — whatever you can do, it can do. Each permission is checked in Rust on every call, against the header on disk, so an extension cannot reach past what it declared. What it declared is the thing to look at.

Omit the permissions line entirely if you only need the APIs below that do not require one.

3. Use the ctx API

This is the complete surface — no require, no import, no ambient fetch. Anything not listed here has no implementation to reach.

ctx.clipboard.read()          → Promise<string>
ctx.clipboard.write(text)     → Promise<void>
ctx.fetch(url, init)          → Promise<Response>   // needs network
ctx.selection()               → Promise<string[]>   // needs selection
ctx.notify(text)              → void                // needs notifications
ctx.storage.get(key)          → Promise<any>
ctx.storage.set(key, value)   → Promise<void>
ctx.open(url)                 → Promise<void>

ctx.shell.run(cmd, input?, timeoutSecs?)     // needs shell
ctx.automation.runAppleScript(script)        // needs automation
ctx.automation.runShortcut(name, input?)     // needs automation
ctx.files.read(path) / write(path, content)  // needs files
ctx.settings.get() / set(settings)           // needs settings
ctx.commands.dispatch(input)                 // needs commands
ctx.commands.runTool(toolId, input)          // needs commands
ctx.ai.ask(prompt)                           // needs ai
ctx.shortcuts.run(shortcutId, query?)        // needs shortcuts

ctx.storage is scoped per extension — one JSON file each, so two extensions cannot read each other's keys. ctx.fetch resolves to a Response-like object (ok, status, headers.get(), text(), json()) rather than a real Response: the request happened in Rust, so there is no stream left to wrap.

4. Install

  • Drag the .js file onto Settings → Extensions, or
  • Save it in ~/Library/Application Support/com.caduceus.desktop/extensions/ (use Open extensions folder in Settings).

No store, no signing, no review — but also no escape hatch around the permission list.

Prefer not to write it yourself?

On the Extensions tab, describe what you want in one sentence and use Copy prompt. Paste that into any assistant; the prompt already includes the header format, permission list, and API rules. Save the reply as something.js and drag it in.

5. Run it

Type the extension's name in the Command Center. Anything after the name is passed as input, so word count the quick brown fox runs Word Count with the quick brown fox. Enter opens the extension's page: a string comes back as text with a Copy button, an array as a clickable list, and errors are shown there rather than swallowed.

Extensions execute in a Web Worker — no DOM, and no Tauri IPC, so an extension cannot call a Caduceus command directly. The network globals are removed before your code runs, and the CSP allows connect-src 'self' and nothing else; ctx.fetch is a Rust call that re-reads your header and checks the network permission first, as every other capability does. A call gets 120 seconds before the worker is terminated. The worker bounds what an extension reaches by accident; the permissions line is what bounds what it reaches by asking.

Installing and running both ship as of 3.1.2. The format has not changed since 2.0.0. Full spec in EXTENSIONS.md on GitHub.

Contact

Questions, feedback, or something broken?

Email caduceus@vivaanshahani.com, or use GitHub issues for bugs you can reproduce. The same links are in the app under Settings → Help.