Skip to main content
Glama

earmark

Klicke in deiner laufenden App auf ein Element, sag, was sich ändern soll, und dein Coding-Agent erhält den CSS-Selektor, die Quelldatei und Zeile, den Komponentenpfad, die berechneten Stile und die Box-Geometrie – statt „der Button rechts sieht falsch aus“.

Funktioniert mit jedem Framework. Für das Overlay ist kein Build-Schritt erforderlich.

┌─ browser ──────────────┐        ┌─ broker ────────┐        ┌─ agent ─────────┐
│ click → annotate       │ POST   │ store + SSE     │  MCP   │ list / watch    │
│ pins, panel, markdown  │───────▶│ long-poll       │◀──────▶│ ask / resolve   │
│                        │◀───────│ .earmark/*.json │        │ dismiss         │
└────────────────────────┘  SSE   └─────────────────┘        └─────────────────┘

In 30 Sekunden ausprobieren

npm install && npm run example

Öffne http://127.0.0.1:5173/examples/vanilla/, klicke auf den Pfeil in der Symbolleiste (unten rechts) oder drücke alt+a, und klicke dann auf irgendetwas auf der Seite.

Die Landingpage und die vollständige Anleitung werden unter http://127.0.0.1:5173/site/ mit ausgeliefert – der Quellcode in site/index.html, eine einzige in sich geschlossene Datei ohne Abhängigkeiten.

Für die Live-Agent-Synchronisierung führe den Broker in einem zweiten Terminal aus:

npm run server

Related MCP server: vibe-annotations

Installation

npm install -D earmark
import { createEarmark } from 'earmark';

if (import.meta.env.DEV) {
  createEarmark();
}

Ohne Bundler:

<script type="module" src="/node_modules/earmark/src/index.js" data-earmark-auto></script>

Optionen

createEarmark({
  endpoint: 'http://127.0.0.1:7331', // or false for copy-paste only
  hotkey: 'alt+a',
  theme: 'auto',                     // 'auto' | 'light' | 'dark'
  persist: true,                     // keep annotations across reloads
  onAnnotate: (annotation) => {},
});

Standardmäßig zeigt der Endpunkt auf den lokalen Broker und fällt stillschweigend ab, wenn nichts zuhört – das Overlay funktioniert weiterhin, nur der Sync-Punkt wird grau.


Verwendung

Tool

Was es tut

Klicke auf ein Element. Shift-Klick, um weitere hinzuzufügen, dann Klick zum Abschließen.

T

Text auswählen – der exakte String ist das am besten grepbare, was du einem Agenten geben kannst.

Ziehe einen Bereich auf. Meldet jedes enthaltene Element oder markiert einen leeren Bereich.

Friert alles Bewegende ein – CSS-Animationen, element.animate(), <video>, <audio>.

Panel: prüfen, löschen, dem Agenten antworten, Markdown kopieren.

⌘↵ speichert eine Annotation, esc bricht ab, alt+a schaltet das Auswählen um. Jede Annotation kann als hoch, normal oder niedrig priorisiert markiert werden; high wird für den Agenten zuerst sortiert.


Copy-Paste-Modus

Klicke im Panel auf Copy markdown und füge es in deinen Agenten ein:

## UI feedback — 1 annotation

- **Page:** http://localhost:5173/dashboard
- **Viewport:** 1440×900 @2x, dark mode
- **Framework:** react

### 1. Export button padding is too tight — needs 10px 16px

- **Element:** `<button>` <ExportButton>
- **Selector:** `[data-testid="export-btn"]`
- **Source:** `src/components/Card.tsx:42:7`
- **Component path:** App › Dashboard › Card › ExportButton
- **Text:** "Export"
- **Box:** 66×37 at (194, 376)
- **Computed:** padding: 7px 13px; border-radius: 8px; font-size: 13px
- **Ancestors:** div.row ← section.card ← main

Agenten-Synchronisierungsmodus (MCP)

claude mcp add earmark -- npx -y earmark-mcp

Oder um es in die .mcp.json des Projekts zu schreiben:

npx earmark-mcp init

Dieser eine Prozess führt den MCP-Server und den Broker aus, mit dem der Browser spricht. Wenn etwas nicht funktioniert, frag ihn, warum:

npx earmark-mcp doctor
✓ Node version: v24.12.0
✓ sqlite backend: available
✓ MCP registration: earmark is registered in .mcp.json
✓ Broker: responding on http://127.0.0.1:7331 — 1 annotations, 2 sessions
✓ Browser overlay: http://localhost:5173/ (1 annotations)

Jede fehlgeschlagene Prüfung gibt den Befehl aus, der sie behebt, und doctor wird mit einem Non-Zero-Exit beendet, damit CI es verwenden kann.

Tools

Tool

Zweck

earmark_list_annotations

Offene Arbeit als Markdown (oder format: "json"); eingrenzen mit session

earmark_watch_annotations

Blockiert, bis der Mensch etwas annotiert

earmark_get_annotation

Eine Annotation mit ihrem vollständigen Antwortthread

earmark_list_sessions

Welche Browser-Tabs offen sind und welche Routen annotiert wurden

earmark_get_session

Ein Tab mit jeder Annotation, die er erzeugt hat

earmark_acknowledge

„Ich habe es gelesen, ich bin dran“ – der Pin wird blau

earmark_ask

Stelle eine klärende Frage – der Pin wird bernsteinfarben

earmark_resolve

Als erledigt markieren mit einer Zusammenfassung – der Pin wird grün

earmark_dismiss

Ablehnen mit einem Grund, den der Mensch sieht

earmark_clear

Alles löschen

earmark_status

Ist das Overlay verbunden? Welchen Endpunkt soll es verwenden?

Die damit ermöglichte Korrekturschleife:

watch → acknowledge → read the source path → edit the file → resolve → watch

acknowledge ist bei allem Langsamen wichtig: Ohne es sieht ein Agent, der mitten in einem Refactoring steckt, genauso aus wie ein Agent, der dich ignoriert hat. Blauer Pin bedeutet aufgenommen, grün bedeutet tatsächlich erledigt.

Wenn das Feedback mehrdeutig ist, frage mit ask, anstatt zu raten. Die Frage erscheint auf dem Pin; die Antwort des Menschen weckt den nächsten watch.

Status

openacknowledgedresolved, mit needs-input, wenn der Agent auf einen Menschen wartet, und dismissed, wenn er ablehnt. Pins sind farbcodiert: orange, blau, grün, bernstein, grau.

Sessions

Eine Session ist ein Browser-Tab, kein Seitenladevorgang – die ID lebt in sessionStorage, übersteht also Neuladungen. Annotationen tragen ihre eigene page.url, sodass eine Session, die über drei Routen gewandert ist, einem Agenten eine Gruppe mit drei unterschiedlich gerouteten Elementen liefert.

Auch SPA-Navigation wird verfolgt: pushState, replaceState, popstate und hashchange aktualisieren alle die Routenliste der Session. Ein Tab gilt genau so lange als verbunden, wie sein SSE-Stream offen ist.

curl http://127.0.0.1:7331/sessions

Quelldateipfade

Selektoren sagen einem Agenten, wonach er mit grep suchen soll. Quellpfade sagen ihm genau, wo er suchen muss – das ist der Unterschied zwischen einer Änderung und drei Greps.

React 19 hat das Laufzeit-Feld _debugSource der Fiber entfernt, daher geschieht dies zur Build-Zeit:

// vite.config.js
import earmark from 'vite-plugin-earmark';

export default {
  plugins: [react(), earmark()],
};

Jedes intrinsische JSX-Element erhält während vite dev data-earmark-src="src/Card.tsx:42:7". Das Plugin injiziert auch das Overlay, sodass createEarmark() in deinem Anwendungscode optional wird.

earmark({
  inject: false,        // do not auto-mount the overlay
  endpoint: '…',        // passed through to createEarmark
  applyInBuild: true,   // also stamp production builds (off by default)
})

Ohne das Plugin funktioniert weiterhin alles – du erhältst Selektoren, Komponentennamen und Text, nur kein file:line. Du kannst data-earmark-src auch von Hand hinzufügen.

Einfaches HTML und CSS – kein Build-Schritt

Eine statische Website hat keinen Build, den man stempeln könnte, daher löst earmark die Quelle stattdessen zur Annotationszeit auf:

  • HTML – das Dokument wird erneut abgerufen und mit Positionsverfolgung geparst, dann wird der Child-Index-Pfad des Elements im Quelltext abgegangen. Jeder Schritt wird gegen den Live-Tag-Namen geprüft, sodass eine framework-gerenderte Seite (bei der das ausgelieferte HTML nur eine Hülle ist) nichts meldet, anstatt eine Zeile zu erfinden.

  • CSS – jede Regel, die auf das Element passt, wird auf die Datei und Zeile zurückgeführt, die sie deklariert. Diese Funktion funktioniert überall, mit oder ohne Framework.

- **Source:** `index.html:101:11` _(resolved from the served HTML)_
- **CSS rules that style it:**
  - `button` → `index.html (inline <style>):49`
    - padding: 7px 13px; border-radius: 8px; border: 1px solid var(--line);
  - `button.primary` → `index.html (inline <style>):59`
    - background: var(--accent); color: rgb(255, 255, 255);

Der Agent weiß jetzt, dass das Padding, das er ändern muss, in Zeile 49 der generischen button-Regel liegt, nicht in .primary. Inline-<style>-Blöcke werden in ihr Host-Dokument versetzt; externe Stylesheets melden ihren eigenen Pfad; Cross-Origin-Stylesheets werden übersprungen, weil ihre Inhalte nicht lesbar sind.


Eigenständiger Broker

npx earmark-server --port 7331
curl http://127.0.0.1:7331/markdown

Route

GET /health

Lebendigkeit + Zähler

GET /annotations?status=open&session=ID

Liste

POST /annotations

erstellen (Stapel)

GET /annotations/wait?since=N&timeout=30000

Long-Poll

PATCH /annotations/:id

Status aktualisieren

POST /annotations/:id/replies

an den Thread anhängen

DELETE /annotations/:id · DELETE /annotations

entfernen · leeren

POST /session

einen Tab registrieren / eine Routenänderung aufzeichnen

GET /sessions · GET /sessions/:id

Tabs mit Zählern und Annotationen

GET /events?session=ID

SSE-Stream; auch das Lebendigkeitssignal des Tabs

GET /markdown

das agentenorientierte Dokument

Flags: --host --store --file --no-persist --webhook --token --quiet.

Storage

--store json (Standard) schreibt bei einem 250-ms-Debounce eine lesbare .earmark/annotations.json. --store sqlite schreibt jede Änderung sofort über node:sqlite in .earmark/annotations.db, sodass ein Absturz höchstens die gerade laufende Anweisung verliert – keine Abhängigkeit, Node 22.5+, und es fällt auf JSON zurück, wenn nicht verfügbar. --store memory behält nichts.

Webhooks

npx earmark-server --webhook https://hooks.example/earmark

Außerdem EARMARK_WEBHOOK_URL und EARMARK_WEBHOOKS (durch Kommas getrennt). Jedes Annotation-Ereignis wird mit einem x-earmark-event-Header per POST gesendet. Die Zustellung ist Fire-and-Forget mit einem 5-s-Timeout und einem erneuten Versuch, sodass ein toter Endpunkt die Annotation-Schleife nicht aufhalten kann.


Sicherheit

Dies ist ein Entwicklungswerkzeug.

  • Der Broker bindet nur an 127.0.0.1. Binde ihn nicht an 0.0.0.0.

  • CORS ist absichtlich offen – dein Dev-Server befindet sich auf einem beliebigen Origin.

  • Jede in deinem Browser geöffnete Seite kann einen Loopback-Port erreichen. Übergib --token SECRET, falls das auf deinem Rechner eine Rolle spielt.

  • Webhooks senden Annotationsinhalte von deinem Rechner – Seiten-URLs, Elementtext und alles, was du getippt hast. Konfiguriere nur Endpunkte, die du kontrollierst.

  • Die Quellauflösung ruft deine eigene Seite und Stylesheets erneut vom selben Origin ab. Es wird nichts irgendwohin gesendet.

  • Führe es nicht auf einem gemeinsamen oder öffentlichen Host aus.


Tests

npm test

Sieben Suiten, 88 Tests: Store- und HTTP-Verhalten, die MCP-Oberfläche, gesteuert von einem echten Stdio-Client, der Sync-Client des Overlays, beide Persistenz-Backends, Webhook-Zustellung, die init/doctor-CLI und die Quellauflöser.


Nicht unterstützt

Nur Desktop-Browser. Keine Iframes, keine Canvas/WebGL-Interna, keine Screenshots. Siehe plan.md für die vollständige offene Liste und die Begründung hinter jeder Designentscheidung.


Lizenz

MIT. Clean-Room-Implementierung – nicht aus dem Quellcode eines anderen Werkzeugs abgeleitet.

F
license - not found
-
quality - not tested
C
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Servers

View all related MCP servers

Related MCP Connectors

  • MCP server for AgentDocs (agentdocs.eu): read, search, write, comment on & share Markdown docs.

  • A paid remote MCP for AI agent browser approval MCP, built to return verdicts, receipts, usage logs,

  • A paid remote MCP for AI agent browser DevTools MCP, built to return verdicts, receipts, usage logs,

View all MCP Connectors

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/nahar-strativ/Agentic'

If you have feedback or need assistance with the MCP directory API, please join our Discord server