Reference

Macros

One macro definition, every Yapture client. This is the full reference — template grammar, primitives namespace, formatters, import/export, sharing, and security.

Overview

A macro is a named replacement: a trigger and an expansion body. Type the trigger in any input that Yapture is watching — the web editor, a system text field on desktop, an input on any web page (Chrome), or a CLI command — and the body lands in its place.

Bodies are templates. They can reference user-supplied variables, live Yapture primitives (your task counts, your active workspace, the time), and use a handful of formatters to massage values into shape.

The grammar is closed by design: no eval, no scripts, no fetches. The evaluator is a pure function over a closed AST.

Lifecycle
  1. Author a macro in any client (or install one from the Market).
  2. Yapture stores it server-side and pushes to every authenticated client.
  3. Type the trigger; the client matches and resolves variables + primitives.
  4. The expansion replaces the trigger inline. Cursor lands at {{|}} if present.

Macro Definition Format (MDF)

JSON is canonical. YAML is sugar accepted by CLI and Library import — it expands to canonical JSON on the way in.

Canonical JSON

email-signature.json json
{
  "id": "mcr_01HXYZ...",
  "ownerId": "usr_...",
  "schemaVersion": 1,
  "name": "Email signature",
  "slug": "email-signature",
  "description": "Standard sign-off with current task count",
  "icon": "✉️",
  "trigger": {
    "value": ";sig",
    "mode": "prefix",
    "caseSensitive": false,
    "explicitOnly": false
  },
  "scopes": {
    "include": ["*"],
    "exclude": ["bundle:com.apple.keychainaccess", "domain:bank.example.com"]
  },
  "variables": [
    { "name": "recipient", "label": "Recipient name", "type": "string", "default": "", "required": true },
    { "name": "tone", "label": "Tone", "type": "enum", "values": ["formal", "casual"], "default": "casual" }
  ],
  "body": "Hi {{recipient}},\n\nThanks — I'll get back to you with the {{yap.tasks.openCount}} open items.\n\n— {{yap.user.name}}",
  "tags": ["email", "work"],
  "enabled": true,
  "source": { "kind": "user" }
}

YAML sugar

email-signature.yaml yaml
name: Email signature
trigger: ;sig
variables:
  - recipient (required)
  - tone: [formal, casual] = casual
body: |
  Hi {{recipient}},
  Thanks — I'll get back to you with the {{yap.tasks.openCount}} open items.
  — {{yap.user.name}}
tags: [email, work]

The source.kind field is one of user, pack, or shared-link. Pack-sourced macros also carry packId + packVersion.

Template grammar

Every token the evaluator recognises. Anything outside this table is rendered as literal text.

Token Meaning Notes
{{name}} Variable Resolved from the macro's variables[] definition. Prompt UI fills it at expansion time.
{{yap.<path>}} Primitive Read-only reference to live Yapture data. See § primitives.
{{now}} Date helper Current ISO timestamp at expansion time.
{{now+3d}} Date helper Relative date arithmetic (+/- d, w, m).
{{today}} Date helper YYYY-MM-DD in user TZ.
{{today | iso}} Date helper Pipe-formatted date — see formatters.
{{|}} Cursor sentinel Optional. Marks where the cursor lands after expansion.
{{#if expr}} ... {{/if}} Conditional Comparison only (>, <, ==). No arithmetic, no chained logic.
{{#choice}}A|B|C{{/choice}} Choice Picks one at random; clients with a prompt UI may offer interactive selection.
{{! comment }} Comment Ignored at evaluation.
{{!slug}} Deferred ref Stored verbatim. Renders as a chip everywhere Yapture displays the text.

What is not allowed

  • Loops (planned for v1.1 — {{#each yap.tasks.recent}}…{{/each}} over a capped iterable).
  • Arithmetic beyond comparison (>, <, ==).
  • Filesystem, fetch, eval, or Function.
  • Nested macro calls. Compose with packs instead.

Formatters

Pipe-applied pure functions. The set is fixed; v1 ships these ~20.

Name Signature Example Result
upper {{ value | upper }} {{ "yapture" | upper }} YAPTURE
lower {{ value | lower }} {{ "Yapture" | lower }} yapture
titlecase {{ value | titlecase }} {{ "yap once daily" | titlecase }} Yap Once Daily
trim {{ value | trim }} {{ " hi " | trim }} hi
default {{ value | default: "Unknown" }} {{ blockers | default: "none" }} none (when blockers is empty)
date {{ value | date: "YYYY-MM-DD" }} {{ yap.now | date: "YYYY-MM-DD" }} 2026-05-14
iso {{ value | iso }} {{ today | iso }} 2026-05-14T00:00:00Z
pluralize {{ value | pluralize: "task" }} {{ yap.tasks.openCount | pluralize: "task" }} 4 tasks
maskEmail {{ value | maskEmail }} {{ yap.user.email | maskEmail }} a***@yapture.com

Yapture primitives namespace

Read-only references under yap.*. Resolved at expansion time against the current authenticated session — fetched from /api/macros/primitives or a local cache.

namespace overview text
yap.user.{ id, name, email, alias, avatarUrl, timezone }
yap.now                                 // ISO timestamp
yap.today                               // YYYY-MM-DD in user TZ

yap.tasks.length
yap.tasks.openCount
yap.tasks.completedCount
yap.tasks.dueTodayCount
yap.tasks.overdueCount
yap.tasks.recent[0..n].{ id, text, priority, dueDate, completed }
yap.tasks.next.{ id, text, dueDate }    // next due task or null

yap.workspaces.length
yap.workspaces.active.{ id, name }

yap.goals.length
yap.goals.active[0..n].{ id, name, progress }

yap.tags.list[0..n].{ id, name, color }

yap.notes.length
yap.notes.recent[0..n].{ id, title }

yap.clipboard                           // (desktop + chrome)
yap.selection                           // current selection text (where available)
yap.url                                 // current page URL (chrome only)
yap.appBundleId                         // foreground app ID (desktop only)

A primitive that is unavailable in the current client (e.g. yap.url on desktop) evaluates to an empty string. The evaluator never errors on a missing primitive.

yap.user.*

Path Description Availability
yap.user.id Stable user ID. all
yap.user.name Display name. all
yap.user.email Authenticated email address. all
yap.user.alias Public handle (no @). all
yap.user.avatarUrl Profile avatar URL. all
yap.user.timezone IANA TZ ID (e.g. America/Los_Angeles). all

yap.now / yap.today

Path Description Availability
yap.now Server ISO timestamp at expansion time. all
yap.today YYYY-MM-DD in user TZ. all

yap.tasks.*

Path Description Availability
yap.tasks.length Total tasks across visible lists. all
yap.tasks.openCount Tasks not marked complete. all
yap.tasks.completedCount Tasks marked complete. all
yap.tasks.dueTodayCount Tasks with a due date of today. all
yap.tasks.overdueCount Tasks past due date and still open. all
yap.tasks.recent[0..n] Recent tasks. Each has id, text, priority, dueDate, completed. all
yap.tasks.next Next due open task, or null. Has id, text, dueDate. all

yap.workspaces.*

Path Description Availability
yap.workspaces.length Total workspaces. all
yap.workspaces.active Currently selected workspace. Has id, name. all

yap.goals.*

Path Description Availability
yap.goals.length Total goals. all
yap.goals.active[0..n] Active goals. Each has id, name, progress (0–1). all

yap.tags.* / yap.notes.*

Path Description Availability
yap.tags.list[0..n] Available tags. Each has id, name, color. all
yap.notes.length Total notes. all
yap.notes.recent[0..n] Recent notes. Each has id, title. all

Context-dependent

Path Description Availability
yap.clipboard Current clipboard text. desktop, chrome
yap.selection Currently selected text (where available). web, desktop, chrome
yap.url Current page URL. chrome only
yap.appBundleId Foreground app ID (e.g. com.apple.mail). desktop only

Variable prompt UX

When a macro fires and has unresolved variables, every client renders the same modal — web popover, Chrome shadow-DOM popover, desktop floating window, CLI Ink form. The live preview re-runs the evaluator on every keystroke.

Shared modal (ASCII mock) text
┌───────────────────────────────┐
│ Email signature                  │
│ ─────────────────────────────── │
│  Recipient *  [_____________]    │
│  Tone         (•) casual         │
│               ( ) formal          │
│ ─────────────────────────────── │
│ Preview:                         │
│ "Hi Bob, Thanks — I'll get back  │
│  to you with the 4 open items.   │
│  — Alec"                        │
│ ─────────────────────────────── │
│               [Cancel]  [Insert] │
└───────────────────────────────┘

Import / export

Two file shapes — a single macro and a pack. Both round-trip losslessly.

Single macro

  • Export: Macro JSON minus id, ownerId, and timestamps. MIME: application/json.
  • Import: accepts JSON or YAML. The validator runs first; on slug collision the importer prompts you to rename or overwrite.

Macro pack (.yapack)

A single JSON file (or tarball) with a manifest plus the contained macros.

recruiter-starter.yapack json
{
  "schemaVersion": 1,
  "kind": "macro-pack",
  "manifest": {
    "name": "Recruiter starter pack",
    "slug": "recruiter-starter",
    "description": "Outreach, follow-ups, scheduling.",
    "icon": "mail",
    "version": "1.0.0",
    "author": "Yapture",
    "compatibility": {
      "minYapture": "0.12.0",
      "clients": ["app_v2", "browser_v2", "cli_v2", "desktop", "launcher"]
    },
    "changelog": [
      {
        "version": "1.0.0",
        "date": "2026-05-24",
        "notes": ["Initial outreach and follow-up macros."]
      }
    ]
  },
  "macros": [
    {
      "slug": "candidate-follow-up",
      "name": "Candidate follow-up",
      "description": "Polite follow-up after a recruiter screen.",
      "tags": ["recruiting", "email"],
      "trigger": {
        "value": ";cfu",
        "mode": "prefix",
        "caseSensitive": false,
        "explicitOnly": false
      },
      "scopes": {
        "include": ["domain:mail.google.com", "bundle:com.apple.mail"],
        "exclude": ["input:password"]
      },
      "variables": [
        { "name": "candidate", "label": "Candidate name", "type": "string", "required": true },
        { "name": "role", "label": "Role", "type": "string", "required": true }
      ],
      "body": "Hi {{candidate}},\n\nThanks for speaking with us about {{role}}. I will follow up with next steps shortly.\n\n- {{yap.user.name}}"
    }
  ]
}

yap macro pack import <file> and the web Library both accept this shape.

Macro pack manifest

A macro pack is a versioned bundle of MDF objects. The pack manifest owns the pack identity - slug, name, author, and version - plus included macros, changelog entries, and client compatibility.

Pack macros do not include user ownership fields in the pack file. Those are assigned when the pack is installed and materialized into user macro records.

recruiter-starter.yapack json
{
  "schemaVersion": 1,
  "kind": "macro-pack",
  "manifest": {
    "name": "Recruiter starter pack",
    "slug": "recruiter-starter",
    "description": "Outreach, follow-ups, scheduling.",
    "icon": "mail",
    "version": "1.0.0",
    "author": "Yapture",
    "compatibility": {
      "minYapture": "0.12.0",
      "clients": ["app_v2", "browser_v2", "cli_v2", "desktop", "launcher"]
    },
    "changelog": [
      {
        "version": "1.0.0",
        "date": "2026-05-24",
        "notes": ["Initial outreach and follow-up macros."]
      }
    ]
  },
  "macros": [
    {
      "slug": "candidate-follow-up",
      "name": "Candidate follow-up",
      "description": "Polite follow-up after a recruiter screen.",
      "tags": ["recruiting", "email"],
      "trigger": {
        "value": ";cfu",
        "mode": "prefix",
        "caseSensitive": false,
        "explicitOnly": false
      },
      "scopes": {
        "include": ["domain:mail.google.com", "bundle:com.apple.mail"],
        "exclude": ["input:password"]
      },
      "variables": [
        { "name": "candidate", "label": "Candidate name", "type": "string", "required": true },
        { "name": "role", "label": "Role", "type": "string", "required": true }
      ],
      "body": "Hi {{candidate}},\n\nThanks for speaking with us about {{role}}. I will follow up with next steps shortly.\n\n- {{yap.user.name}}"
    }
  ]
}

Materialized user macro

After install, each included macro receives user-specific IDs and source pack metadata. Clients use this metadata to show pack origin, keep pack-sourced macros read-only, and remove them when the pack is uninstalled.

pack-sourced-user-macro.json json
{
  "id": "mcr_01J0PACKSOURCED",
  "ownerId": "usr_01J0USER",
  "slug": "candidate-follow-up",
  "name": "Candidate follow-up",
  "trigger": {
    "value": ";cfu",
    "mode": "prefix",
    "caseSensitive": false,
    "explicitOnly": false
  },
  "body": "Hi {{candidate}},\n\nThanks for speaking with us about {{role}}.",
  "enabled": true,
  "source": {
    "kind": "pack",
    "packSlug": "recruiter-starter",
    "packVersion": "1.0.0",
    "macroSlug": "candidate-follow-up",
    "readonly": true
  }
}

Install, sync, and conflicts

Macro pack install state is canonical in GET /api/user/installed. The macro definitions themselves sync through GET /api/macros. A pack install appears in inventory as macro_pack:<slug>.

Install

Market creates a UserMacroPack record, validates the pack, and materializes each included macro into the user macro list with source.kind = "pack".

Sync

Clients fetch GET /api/macros after auth and cache pack-sourced macros locally for offline expansion.

Inventory

GET /api/user/installed includes the pack as key macro_pack:<slug>, so app_v2, browser_v2, cli_v2, desktop, and launcher show the same installed state.

Uninstall

DELETE /api/market/macros/:slug/install removes the pack install and deletes unchanged pack-sourced macros on the next client sync.

Forks

If a user edits a pack macro, clients should fork it into source.kind = "user" or block editing; uninstall does not remove user-owned forks.

Conflict behavior

Case Behavior
Macro slug conflicts with a user macro Skip the pack macro and record an install warning. The user-owned macro wins.
Trigger conflicts with an enabled macro Keep the pack macro disabled until the user changes one trigger or disables the conflicting macro.
Pack uninstalled while a client is offline Remove pack-sourced macros the next time that client syncs.
Pack version changes Update unchanged pack-sourced macros; preserve user-owned forks and locally disabled state.
Primitive unavailable in a client Resolve to an empty string and keep expansion deterministic.

Cross-client availability

Client Macro behavior
app_v2 Authoring, Market install/uninstall, macro list, and server-side sync source.
browser_v2 After auth, syncs GET /api/macros into extension storage and expands from local cache.
cli_v2 Lists, syncs, imports, exports, and expands macros from ~/.yapture/macros.json.
desktop System text expansion, macro manager, and pack source metadata.
launcher Reads Desktop exported macro cache for searchable macro entries.
site_v2 Public documentation and Market listings only.

Sharing

POST /api/macros/:id/share mints a short share token. A configured macro-share URL can preview the macro and offer an install action after the authenticated product flow is verified.

  • Tokens are URL-safe random, 22 characters.
  • Tokens are revocable and can be capped at N uses or set to expire.
  • Rate-limited to 60 mints / minute per user.
  • Installed shared macros carry source.kind = "shared-link".

Quotas & security

The macro system is designed to be useful and boring at the same time.

  • No code execution. The evaluator is a pure function over a closed AST — no Function, no eval, no dynamic require.
  • Primitives are read-only. Macros can never mutate user data; they only render it.
  • Per-user quota: 500 macros, 50 KB per macro body, 20 variables per macro. Per pack: 100 macros, 1 MB manifest. Enforced at the API.
  • Scoped expansion. scopes.exclude is honoured on desktop + Chrome. Defaults exclude banking domains, password fields (<input type="password">), and inputs marked with sensitive heuristics.
  • Chrome content scripts never read field values upstream. Macro definitions and primitives flow to the field; field values never leave the page.
  • Pack validator rejects packs containing undeclared variables, banned formatters, or oversize manifests. Abuse reports surface in the admin portal.

Example library

A few starting points. Copy, paste, edit.

yapture-macros.yaml yaml
# ~/Documents/yapture-macros.yaml
- name: Address
  trigger: ;addr
  body: |
    123 Main St
    San Francisco, CA 94110

- name: Standup
  trigger: ;standup
  body: |
    Yesterday: {{yesterday}}
    Today: {{#if yap.tasks.dueTodayCount > 0}}{{yap.tasks.dueTodayCount}} due ({{yap.tasks.next.text}}){{/if}}
    Blockers: {{blockers | default: "none"}}
  variables:
    - yesterday (required)
    - blockers

- name: Meeting note header
  trigger: ;mtg
  body: |
    # {{title}} — {{today | date: "YYYY-MM-DD"}}

    Attendees: {{attendees}}
    Workspace: {{yap.workspaces.active.name}}

    ## Notes
    {{|}}
  variables:
    - title (required)
    - attendees (required)

Looking for the feature page?

See the marketing landing for screenshots, the cross-client matrix, and a quick tour of what macros feel like in practice.

Features → Macros