/SKILL.md
Use when the user has page annotations from the Missouri Chrome extension, or asks you to look at, highlight, mark up, or comment on elements in a web page. Provides guidance on using Playwright page.evaluate() to read annotations, create markers, focus elements, and interact with Missouri's page API.
npx skillsauth add chrisvoncsefalvay/missouri missouriInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Missouri is a Chrome extension that lets users place visible annotations (markers, highlights, drawings) on any web page. When you browse with Playwright, you can read, create, and interact with these annotations via page.evaluate() -- no server, no configuration.
Missouri injects an element with id mo-marker-root into every page. Check if Missouri is active:
await page.evaluate(() => !!document.getElementById('mo-marker-root'))
The dispatch API is available at window.__moDispatch(command, params). It returns a Promise. If __moDispatch times out (10s), fall back to reading the DOM directly -- see "DOM fallback" below.
__moDispatchAll commands are invoked via page.evaluate():
const result = await page.evaluate(() => window.__moDispatch('command_name', { ...params }))
| Command | Params | Returns |
|---------|--------|---------|
| list_annotations | none | { ok, data: Annotation[] } |
| get_annotation | { id } | { ok, data: Annotation } |
| get_page_info | none | { ok, data: { url, title, annotationCount } } |
| Command | Params | Returns |
|---------|--------|---------|
| create_annotation | { type, selector?, note?, pageX?, pageY?, colorIndex?, authorName? } | { ok, data: Annotation } |
| update_annotation | { id, note?, authorName? } | { ok, data: Annotation } |
| delete_annotation | { id } | { ok } |
| Command | Params | Returns |
|---------|--------|---------|
| focus_annotation | { id } | { ok } -- scrolls to and pulses the marker |
| highlight_element | { selector } | { ok, data: { selector, tagName } } -- temporary blue highlight |
If __moDispatch times out or errors, you can read annotations directly from the DOM. Missouri renders markers inside #mo-marker-root with data attributes on each marker element.
const info = await page.evaluate(() => {
const hasRoot = !!document.getElementById('mo-marker-root');
const hasDispatch = typeof window.__moDispatch === 'function';
return { hasRoot, hasDispatch };
});
const annotations = await page.evaluate(() => {
const markers = document.querySelectorAll('[data-mo-marker-id]');
return Array.from(markers).map(el => {
const noteEl = el.querySelector('.mo-marker-note');
const strongEl = noteEl?.querySelector('strong');
const noteText = noteEl
? noteEl.textContent?.replace(strongEl?.textContent || '', '').trim()
: '';
return {
id: el.getAttribute('data-mo-marker-id'),
type: el.getAttribute('data-mo-marker-type'),
resolved: el.getAttribute('data-mo-marker-resolved'),
note: noteText,
label: el.querySelector('button')?.textContent?.trim() || null,
rect: el.getBoundingClientRect()
};
});
});
This gives you the annotation ID, type, note text, label and position. It works even when the extension's service worker is unresponsive.
Limitations of DOM fallback:
1. Check discovery: mo-marker-root exists?
2. Try __moDispatch('list_annotations') with a short wrapper:
try { return await page.evaluate(() => window.__moDispatch('list_annotations')) }
catch { /* fall through to DOM fallback */ }
3. If that times out or errors, use the DOM fallback query above
4. Parse annotation notes to understand user intent
5. Make changes, then reply via create_annotation or update_annotation
(these require working __moDispatch -- if dispatch is broken, tell the user)
Each annotation contains:
free (placed anywhere), element (attached to a DOM element), highlight (text selection), draw (freehand)"Claude (via Playwright)")selector (CSS path), tagName, and text (element content, truncated to 120 chars){ pageX, pageY } -- where the marker is on the pagelet annotations;
try {
const res = await page.evaluate(
() => window.__moDispatch('list_annotations')
);
annotations = res.data;
} catch {
// Dispatch unavailable -- read DOM directly
annotations = await page.evaluate(() => {
const markers = document.querySelectorAll('[data-mo-marker-id]');
return Array.from(markers).map(el => {
const noteEl = el.querySelector('.mo-marker-note');
const strong = noteEl?.querySelector('strong');
return {
id: el.getAttribute('data-mo-marker-id'),
type: el.getAttribute('data-mo-marker-type'),
note: noteEl?.textContent?.replace(strong?.textContent || '', '').trim() || '',
label: el.querySelector('button')?.textContent?.trim() || null,
};
});
});
}
await page.evaluate(() => window.__moDispatch('create_annotation', {
type: 'element',
selector: '#login-button',
note: 'This button needs an aria-label',
authorName: 'Claude (via Playwright)',
colorIndex: 3
}))
await page.evaluate(() => window.__moDispatch('create_annotation', {
type: 'free',
pageX: 200,
pageY: 400,
note: 'Layout breaks below this fold',
authorName: 'Claude (via Playwright)'
}))
await page.evaluate((id) => window.__moDispatch('focus_annotation', { id }), annotationId)
await page.evaluate(() => window.__moDispatch('highlight_element', { selector: '.hero-banner' }))
await page.evaluate((id) => window.__moDispatch('update_annotation', {
id,
note: 'Updated: this is now fixed'
}), annotationId)
await page.evaluate((id) => window.__moDispatch('delete_annotation', { id }), annotationId)
1. page.evaluate(() => window.__moDispatch('get_page_info'))
2. page.evaluate(() => window.__moDispatch('list_annotations'))
3. Read each annotation's note + anchor to understand context
1. highlight_element with the CSS selector (temporary)
OR
1. create_annotation with a note explaining what you found (persistent)
2. focus_annotation to scroll it into view
1. list_annotations -> find the annotation with letter "B"
2. update_annotation -> edit the note with your response
OR
2. create_annotation -> place a new marker nearby with your reply
__moDispatch times out after 10 secondsThis is the most common issue. Causes:
--user-data-dir: a fresh debug profile may not fully initialise the extension's service worker. The DOM fallback still works for reading.Do not waste time debugging CDP targets, isolated execution contexts, or service worker internals. If dispatch fails, use the DOM fallback for reading and tell the user if write operations are unavailable.
mo-marker-root not foundMissouri is not installed or not enabled on this page. Check that the extension is loaded in the browser.
The markers are in the DOM under #mo-marker-root. If querySelectorAll('[data-mo-marker-id]') returns nothing, try querying inside the root element's shadow root:
const root = document.getElementById('mo-marker-root');
const shadow = root?.shadowRoot;
const markers = shadow
? shadow.querySelectorAll('[data-mo-marker-id]')
: document.querySelectorAll('[data-mo-marker-id]');
list_annotations first to see what the user has marked before taking actiontype: "element" with a selector when you know the CSS selector -- the marker tracks the element even if the page reflowstype: "free" with pageX / pageY for pixel-precise placementcolorIndex: 3 (purple) by default for agent-created annotations to distinguish them from user markersauthorName to identify your annotations (e.g. "Claude (via Playwright)")resolved: false mean the DOM has changed since the annotation was placedanchor.text field gives visible text content, often more useful than the CSS selector for understanding intentletter labels -- users assign these for easy referencechrome.storage.local and survive page reloadstools
Use when work should span one or more detached tasks but still behave like one job with a single owner context. TaskFlow is the durable flow substrate under authoring layers like Lobster, ACPX, plugins, or plain code. Keep conditional logic in the caller; use TaskFlow for flow identity, child-task linkage, waiting state, revision-checked mutations, and user-facing emergence.
tools
# Lobster Lobster executes multi-step workflows with approval checkpoints. Use it when: - User wants a repeatable automation (triage, monitor, sync) - Actions need human approval before executing (send, post, delete) - Multiple tool calls should run as one deterministic operation ## When to use Lobster | User intent | Use Lobster? | | ------------------------------------------------------ | --------------------------
tools
# Lobster Lobster executes multi-step workflows with approval checkpoints. Use it when: - User wants a repeatable automation (triage, monitor, sync) - Actions need human approval before executing (send, post, delete) - Multiple tool calls should run as one deterministic operation ## When to use Lobster | User intent | Use Lobster? | | ------------------------------------------------------ | --------------------------
tools
A CLI tool for making authenticated requests to the X (Twitter) API. Use this skill when you need to post tweets, reply, quote, search, read posts, manage followers, send DMs, upload media, or interact with any X API v2 endpoint.