Config guide
Your config is one YAML file: ~/.config/keychord/keychord.yml. Menu bar › Edit config
opens it; saving reloads it. A mistake never breaks a working setup: keychord keeps the last
good config and shows the error with its line number.
Check it any time from Terminal:
keychord check # or: keychord check --jsonchords: - keys: sig text: | Ada Lovelace Analytical Engines Ltd- 1–8 characters,
a-zand0-9. Short and mnemonic:sig,addr,ll. - Matched exactly on release, so
tandttcoexist. - Keys must be unique; a duplicate is a config error.
apps: [ghostty, code]limits a chord to those apps (name or bundle id).
Long text: inline or from a file
Section titled “Long text: inline or from a file”Multiline text works inline with |. For longer text, put it in its own file and point the
chord at it with file:. Both work; use whichever is easier to edit.
chords: - keys: sig # inline text: | Ada Lovelace Analytical Engines Ltd - keys: nda # from a file file: snippets/nda.md- The path is relative to the YAML file that declares the chord;
~works too. - The file is read when you fire the chord (cached), so edits apply at once, with no reload.
- The kind comes from the extension:
.mdpastes formatted,.csv/.tsvas a table,.svg/.pngas a picture,.mmdas a diagram, anything else as text. Addkind:to override. ${vars},{tab}and${secret:NAME}work inside the file. A file with a secret is typed, never pasted, like any secret chord.- The limit is 1 MB. One trailing newline is dropped, so your editor’s last newline doesn’t press Enter.
- The path is followed as written (absolute paths,
../and symlinks too).
Define a value once at the top, use it anywhere as ${name}:
vars: me: Ada Lovelace sign: "${me} · ${work_email}" # vars can use varschords: - keys: em text: "${work_email}"Lookup order for ${name}: vars, then the environment. Cycles are a load error.
${NAME}: environment variables
Section titled “${NAME}: environment variables”${GITHUB_USER} reads an environment variable when the chord fires. A menu-bar app doesn’t
see your shell’s ~/.zshrc, so keychord also reads ~/.config/keychord/env (NAME=value
lines). A missing variable aborts the chord: nothing is typed and the menu bar tells you.
Built-ins: ${date:%Y-%m-%d} (any strftime format), ${clipboard}, ${input:Label} (asks in a
small box when fired). $${ types a literal ${.
{tab}, {enter} and other keys
Section titled “{tab}, {enter} and other keys”Inside text, braces are key presses:
| write | presses |
|---|---|
{tab} {enter} {esc} |
Tab, Return, Escape |
{up} {down} {left} {right} |
arrows |
{backspace} {delete} {home} {end} |
editing keys |
{delay:80} |
waits 80 ms (slow web forms, autocomplete) |
Text is typed as real keystrokes, so web forms (React and friends) see every field.
include
Section titled “include”Split your config into files. Later files add chords and override vars with the same name:
include: - packs/presenter.yml - ~/work/team-chords.ymlSecrets vault: ${secret:NAME}
Section titled “Secrets vault: ${secret:NAME}”Passwords and tokens never go in the YAML. They live in an encrypted vault, secrets.kcv,
next to your config (XChaCha20-Poly1305; the key comes from your vault passphrase via
Argon2id). The file shows names, never values, so it is safe to sync.
keychord secret set WORK_PW # prompts, hidden; stores in the vault - keys: lg text: "${work_email}{tab}${secret:WORK_PW}{enter}"You unlock the vault once per device; the key is then kept in the macOS Keychain. Secret
values are always typed, never pasted, never logged, and shown as •••• in the hint.
Kinds: text, md, table, diagram, shape
Section titled “Kinds: text, md, table, diagram, shape”text is typed. The other kinds are pasted as rich content, then your clipboard is put
back.
chords: - keys: wsig # Word / Outlook / Mail: real bold and italics md: "**Ada Lovelace** · Engines Ltd\n_${work_email}_"
- keys: xt # Excel: real cells. Word / PowerPoint: a real table table: | Name,Team,Score Ada,Core,9 Linus,Mac,8
- keys: flow # a picture of a mermaid flowchart diagram: mermaid source: | flowchart LR Idea --> Build --> Ship
- keys: ok # one shape with a label shape: circle # circle | rect | rounded | diamond | arrow text: Done color: "#e4572e" fill: lightyellowShapes and diagrams paste as a picture everywhere, and as editable shapes in Excalidraw. Diagrams are rendered when you save the config, so firing one is instant.
Shape keys: c r d and 2ca … 9da (built in)
Section titled “Shape keys: c r d and 2ca … 9da (built in)”No config needed. The keys say the shape:
| keys | draws |
|---|---|
| c / r / d | one circle / box / diamond |
| 2ca … 9ca | that many circles in a row, joined by arrows |
| 2ra … 9ra | boxes with arrows |
| 2da … 9da | diamonds with arrows |
Labels come from your selection, or else what you just typed, split on spaces, commas
and lines. Select login api, press super+2ra: you get [login] → [api]. With no
text you get blank shapes. The text is replaced by the picture; ⌘Z brings it back. In a
terminal the chord is refused. Theme them with shape_color: and shape_fill: under
vars:. A chord of your own with the same keys wins over the built-in.
${selection} and ${last}
Section titled “${selection} and ${last}”Act on text that’s already there:
chords: - keys: b text: "**${selection}**" # select "urgent" → **urgent** - keys: tk text: "- [ ] ${last}" # type "fix login" → - [ ] fix login${selection}: the selected text. If nothing is selected, what you just typed.${last}: what you typed on the current line since the last Enter, Tab, click or app switch (200 characters at most).- The original text is deleted and replaced; undo brings it back.
- Neither exists → the chord does nothing.
Privacy: the ${last} buffer lives only in memory, is never logged, is cleared when
you switch apps, and is off in password fields. (macOS only for now.)
run: aos: chords that do things
Section titled “run: aos: chords that do things”A chord can run an action through the bundled aos tool instead of typing:
chords: - keys: vs run: aos open "Visual Studio Code" - keys: lh run: aos window snap left - keys: gh run: aos open https://github.com - keys: b run: aos subtitle show "Thank you everyone" --position=center --size=XL --seconds=4 --detachrun: is not a shell: the first word must be aos, and arguments are passed as-is (vars and
secrets are filled in per argument). Actions run in the background, so typing chords stay
instant. Captions (aos subtitle) need Screen Recording permission.
Packs and import
Section titled “Packs and import”A pack is a YAML file of chords you include:. Two examples ship in packs/:
presenter.yml (on-screen captions) and agent-prompts.yml (a prompt library).
Bring your existing snippets:
keychord import espanso ~/Library/Application\ Support/espanso/match/base.ymlkeychord import csv my-snippets.csv --file packs/mine.ymlkeychord pack new work # an empty packs/work.yml, already includedFormats: CSV, TSV, JSON, YAML, Markdown, Espanso. Or use menu bar › Import…. Import
writes a new pack file and adds it to include:; it never touches your own file. Text that
looks like a password or token is refused — put it in the vault instead.
Quick reference
Section titled “Quick reference”| write | gets |
|---|---|
${name} |
a var, else environment variable |
${secret:NAME} |
a value from the vault (typed, never pasted) |
${date:%Y-%m-%d} · ${clipboard} |
built-ins |
${selection} · ${last} |
selected / just-typed text, replaced |
${input:Label} |
asks when fired |
{tab} {enter} {delay:80} |
keys |
$${ |
a literal ${ |