Skip to content
Tadoru
English
Esc
navigateopen⌘Jpreview
On this page

Event reference

The event taxonomy, the JSON envelope shared by all events, and per-browser URL capture support.

Every event Tadoru records — regardless of source — shares one OS-independent JSON envelope. The stable schema is the core asset of the project: capture backends may change per OS, but the shape your tools consume does not.

Event envelope

One event per line (NDJSON) in raw outputs (query --format jsonl, record, export):

{
  "v": 1,
  "id": "evt_01J...",
  "ts": "2026-08-16T12:34:56.789Z",
  "mono_ns": 128374651234,
  "source": "macos.ax",
  "type": "window.focus",
  "app":    { "name": "Safari", "bundle_id": "com.apple.Safari", "pid": 501 },
  "window": { "title": "Design doc", "id": 42 },
  "element":{ "role": "AXButton", "title": "Send", "value": null },
  "data":   { },
  "redaction": { "applied": true, "rules": ["email"] }
}
Field Description
v Envelope schema version (currently 1)
id ULID-based event ID, unique and time-sortable
ts Wall-clock timestamp (RFC3339, millisecond precision)
mono_ns Monotonic clock in nanoseconds — reliable ordering even across wall-clock changes
source Capture backend, e.g. macos.ax, macos.workspace, macos.eventtap, macos.applescript
type Event type (see the taxonomy)
app App name, bundle ID, and PID
window Window title and ID (when applicable)
element UI element role/title/value (when applicable; secure fields never appear)
data Type-specific payload, e.g. url / tab_title / mode for browser.navigate
redaction Whether redaction rules (email / credit_card / token) were applied to this event, and which. Secure fields leave no redaction trace — they are excluded before capture

This envelope is also published as a machine-readable JSON Schema at /schema/event.schema.json — the single contract all surfaces share, and the file the Rust core’s types are tested against.

Event taxonomy

The current event types. The Permission column shows what must be granted for the type to be captured (see the permissions guide).

Type Source What it records Permission
app.activate macos.workspace Frontmost app changed None
app.launch / app.terminate macos.workspace App started / quit None
window.focus macos.ax Focused window changed Accessibility
window.title macos.ax Window title changed Accessibility
ui.focus macos.ax Focused UI element changed Accessibility
ui.click macos.ax Click on a UI element Accessibility (+ Input Monitoring)
ui.value macos.ax Element value changed (field edits, …) — content only when text_content is on Accessibility
input.key macos.eventtap Key / shortcut. Default: fact of typing + field type; content is opt-in Input Monitoring
input.scroll macos.eventtap Scrolling Input Monitoring
browser.navigate macos.applescript URL / tab change. Chrome only, currently; Incognito always excluded Automation
clipboard.copy / clipboard.paste macos.eventtap Clipboard use; content is opt-in Input Monitoring

In --types filters, a trailing wildcard selects a whole family: browser.*, ui.*, and so on.

type is deliberately an open set, not a closed enum: future platforms and versions may add types without a version bump, and consumers must skip unknown types rather than error.

Per-type payloads

What each type carries in data, and whether the envelope’s window / element are present (✓) or null (—). The normative definitions live in the JSON Schema as per-type conditions.

Type window element data
app.activate prev_bundle_id — previously frontmost app, null on first event
app.launch / app.terminate empty
window.focus empty (the focused window is in the envelope)
window.title prev_title — the new title is in the envelope
ui.focus field_kind
ui.click button (left/right/other), click_count
ui.value field_kind, value_len — content itself is in element.value, opt-in
input.key kind, modifiers, count, combo, text, field_kind (details below)
input.scroll direction, amount, count — coalesced totals
browser.navigate url, tab_title, mode (always "normal"), transition (navigate/tab_switch/null)
clipboard.copy content_kind (text/image/file/other), size_bytes, text
clipboard.paste same as copy, plus field_kind of the paste target

field_kind classifies the focused input field: text / search / url / email / number / other, or null when focus is not on a text-like element. There is no password value — secure fields are excluded before an event exists.

input.key in detail

The exact shape of “fact of typing + field type by default, content opt-in”:

kind Meaning combo text Coalesced
text Printable typing null typed string, opt-in only yes
shortcut Modifier combo (cmd+s, …) always recorded null no
navigation Arrows, PageUp/Down, Home/End, Tab null null yes
delete Backspace / Delete null null yes
other Esc, F-keys, media keys null null no

A shortcut is an action, not content — so combo is recorded without opt-in. It is a primary timeline ingredient (saves, commits, tab switches).

Coalescing guarantees

Events are coalesced before they reach the store, so consumers never see raw key-repeat or scroll floods:

Events Grouped by Window Result
input.key (text/navigation/delete) app + window + field_kind + kind gap ≤ 2s one event; count summed, text concatenated (opt-in)
input.scroll app + window + direction gap ≤ 1s amount and count summed
window.title window 500ms debounce only the final title is emitted
ui.value element 1s debounce only the final value is emitted

Shortcuts and ui.click never coalesce (one action = one event). Coalescing buffers are always flushed on pause, stop, and batch flush — no events are lost.

What text_content changes

Only content (what you typed or copied) is opt-in. Facts of use (shortcuts, clicks, URLs, titles) are recorded by default:

Field Default (false) Opt-in (true)
element.value always null recorded (after redaction)
input.key.text always null recorded (after redaction)
input.key.combo (shortcuts) recorded recorded
clipboard.*.text / size_bytes always null recorded (after redaction)
ui.value.value_len recorded recorded
window.title / tab_title / url recorded (after redaction) recorded

Schema versioning

  • Additive changes (new fields, new types, new sources) do not bump v. Consumers must ignore unknown fields and skip unknown types.
  • Breaking changes (removal, rename, meaning change) bump v, with a store migration to match.

Browser URL capture

browser.navigate (URL capture) currently supports Chrome only. The reason is a privacy guarantee: Chromium exposes each window’s mode (normal / incognito) deterministically via AppleScript, which lets Tadoru make a hard promise — Incognito windows are always excluded from URL capture, with no configuration knob needed.

Browser URL capture (current) Private windows
Google Chrome ✅ Supported mode detects Incognito deterministically → always excluded
Brave / Edge / Vivaldi (Chromium family) ❌ Not supported — the collector only launches for Chrome’s bundle ID No URL capture, so no private-window handling applies
Safari ❌ Not supported No private-mode property; menu-diff heuristics are brittle, so deliberately not attempted
Firefox ❌ Not supported AppleScript has no URL API
Arc ❌ Not supported (unverified) Chromium-based but limited AppleScript support

Safari/Firefox support may come later, but only if a genuinely reliable private-mode detection mechanism exists.

Two layers of data

  • Raw event layer — complete and machine-readable; what query, record, and export return.
  • Timeline layer — human/LLM-readable; session splitting, dedup, coalescing, token-budgeted serialization. What timeline and get_timeline return.

Timeline sessions carry event_ids back-references to the underlying raw events. They are included only in the JSON format (--format json), and are dropped when the output must be coarsened to fit the token budget.

Was this page helpful?