Skip to content

Embed buttons ​

An embed button is a workflow trigger that the Kadmo extension places on a web page. A workflow gets one by carrying an embed block. Clicking the button collects values from the page, maps them to the workflow's parameters, runs the workflow on the local Kadmo runtime and shows the result next to the button.

How it works ​

StageWhat happens
1. Page loadsThe extension takes the page's host name (for example github.com) and asks the runtime for embed workflows: GET http://localhost:9876/api/embed-workflows?domain=<host>.
2. Runtime answersThe runtime reloads its workflows and returns every workflow that has an embed block: its id, title, embed, params and policies. It does not filter by domain; the parameter is ignored.
3. Extension filtersThe extension keeps the workflows whose policies.allowed_domains and embed.org / embed.repo match the page (see Which pages get a button).
4. Buttons are placedFor each remaining workflow, a button is added at every visible element that matches embed.selector, or in a floating dock when there is no selector. A page watcher repeats this when the page changes (debounced by 500 ms), so content that loads later also gets buttons.
5. ClickThe extension builds the page context, maps it through param_map and calls POST /api/workflows/<id>/run?sync=true with the resulting params.
6. ResultThe button shows "Running..." until the run ends, then the result is shown according to embed.result.

The runtime scans three places for workflows: your actions in ~/.kadmo/actions, published workflows in ~/.kadmo/workflows, and the workflows of installed skill packs (~/.kadmo/skills). A fresh install has none, so no buttons appear until you add a workflow with an embed block. The runtime passes the embed block to the extension unchanged; all the fields below are read by the extension.

Example ​

yaml
id: issue_brief
title: Brief this issue
params:
  org: string
  repo: string
  issue_title: string
  issue_body: string
  focus: string

policies:
  allowed_domains:
    - github.com

embed:
  selector: ".issue-header-actions"   # one button per visible match
  position: prepend
  when: ".issue-body"                 # only on pages that have this element
  label: "Brief"
  org: my-org                         # first path segment must be my-org
  extract:
    title_text:
      selector: ".issue-title"
      attribute: textContent
    body_text:
      selector: ".issue-body"
      attribute: innerText
  prompt:
    param: focus
    title: "What should the brief focus on?"
    placeholder: "Risks, open questions..."
  param_map:
    org: "{{_path.0}}"
    repo: "{{_path.1}}"
    issue_title: "{{title_text}}"
    issue_body: "{{body_text}}"
    focus: "{{focus}}"
  result:
    action: bubble

steps:
  - type: llm.generate
    prompt: |
      Write a five-line brief of this issue in {{org}}/{{repo}}.
      Focus: {{focus}}
      Title: {{issue_title}}
      Body: {{issue_body}}
    as: brief
  - type: control.stop
    message: "{{brief}}"

The selectors are placeholders; use selectors from the page you target. The text shown in the result is the run's output message, which a control.stop step's message sets (see Step types).

Field reference ​

embed ​

FieldTypeEffect
selectorCSS selectorElements to attach a button to. Every match that is visible on the page gets its own button. Without selector, the button goes into a floating dock fixed to the right edge of the window.
positionbefore, after, prepend, appendWhere the button container goes relative to each target: before or after the element, or inside it at the start or end. Default append; any other value is treated as append. Several buttons at the same target and position share one container and stack vertically.
whenCSS selectorThe button is placed only if this selector matches something on the page at the time of placing.
labelstringButton text. Default: the workflow title. With prompt set, an ellipsis (…) is added. The button's tooltip is always the workflow title.
orgstringThe page URL's first path segment must equal this (case-insensitive). Empty or absent matches all.
repostringThe page URL's second path segment must equal this (case-insensitive). Empty or absent matches all.
parent_filterobjectPlace the button only at targets whose ancestor passes a test. See parent_filter.
extractmap of name to entryValues read from the page at click time. See extract.
param_mapmap of param name to valueHow the workflow's parameters are filled. See param_map.
promptobjectAsk for free text before the run. See prompt.
resultobjectWhat to do with the result. See result.

extract ​

Each key under extract names a value; the value is added to the page context under that key when the button is clicked.

FieldEffect
selectorElement to read. self, or no selector, reads the button's target element. A selector that starts with # or contains a space is looked up in the whole page; any other selector is looked up inside the target first, then in the whole page. On a floating button (no target), self finds nothing.
attributeWhat to read: textContent or innerText (both trimmed), location.href or location.pathname (the page URL, read only if selector finds an element), or any other name, read as that HTML attribute (href, data-id, ...). Set it on every entry; without it nothing is read.
patternRegular expression applied to the value. The first capture group is used, or the whole match if there is no group. If the pattern does not match, the full value is kept unchanged.
multipletrue reads every element in the whole page that matches selector and joins the non-empty values. pattern is not applied in this mode.
separatorJoin string for multiple. Default: a newline.

An entry that finds no element or reads an empty value adds nothing to the context.

parent_filter ​

FieldEffect
selectorThe target's nearest ancestor (or the target itself) that matches this selector. Without one, no button is placed at that target.
match.child_selectorOptional: test this element inside the ancestor instead of the ancestor itself. Without a match, no button.
match.attributeWhat to read from the tested element, as in extract (textContent, innerText or an attribute name).
match.equalsThe value must equal this exactly. Checked first if both are set.
match.containsThe value must contain this.

Without match, the ancestor only has to exist.

prompt ​

FieldEffect
paramContext key that receives the typed text. Map it in param_map, for example focus: "{{focus}}".
titleHeading of the input box. Default: the button label.
placeholderPlaceholder text. Default: "Add instructions...".

A click opens a small box with a text area marked "Optional" and a Run button. Ctrl+Enter or Cmd+Enter also runs; Escape or the close button cancels without running. Empty text is allowed and is passed as an empty string.

result ​

actionBehaviour
bubble (default)Show the output message as plain text in a box under the button, titled "Summary". It closes with its close button or a click elsewhere.
typeWrite the output message into the element at result.selector: an editable area or input is cleared and filled, with input events fired so the page notices. If the element is not found or writing fails, the bubble is shown instead.
noneShow nothing; the button reads "Done" for two seconds. Use it when the workflow produces its own output.

Errors are always shown in a bubble titled "Error". A run that succeeds without an output message also shows "Done" for two seconds.

Page context and param_map ​

At click time the extension builds a context from these keys, plus every extract key that produced a value and the prompt.param key:

KeyValue
_urlFull page URL
_domainHost name, for example github.com
_pathnameURL path, for example /my-org/app/issues/42
_titlePage title
_path.NPath segment N, counting from 0: for the path above, _path.0 is my-org and _path.3 is 42

param_map is keyed by workflow parameter name. Each value is either:

  • a template that is exactly {{key}}, which takes the context value of key (if the key has no value, the parameter is left out of the run); or
  • anything else, passed through as a literal string. Mixed text such as "Issue {{_path.3}}" is not filled in; it is sent as written.

Keep extract keys free of dots: a dotted key is read only as name.index into a list such as _path.

Every parameter listed under the workflow's params needs an entry in param_map, and a template entry must point at a built-in key, an extract key or the prompt.param key. If any parameter is unmapped, the extension places no button for that workflow, and logs nothing about it. When there is no param_map at all and the workflow has no params, the whole context is sent as parameters.

Which pages get a button ​

CheckRule
policies.allowed_domainsIf the list is not empty, the page's host must equal an entry or be a subdomain of it. github.com also matches gist.github.com; *.github.com matches github.com and its subdomains. If the list is empty or absent, every site matches.
embed.org / embed.repoCompared with the first and second URL path segments, as described above.
whenMust match an element when the buttons are placed.
selectorMust match at least one visible element (unless omitted, which uses the dock).
parent_filterMust pass for the target.
ConnectionButtons are placed only while the extension is connected to the runtime. Browser pages such as chrome:// and the Chrome Web Store never get buttons.

What a click runs ​

The run goes through the same endpoint as any other run, and it is saved to run history like other runs. The run log records it as triggered by the dashboard. The extension waits for the run to finish; there is no time limit on the wait.

Browser steps in the workflow run in the tab the extension is attached to (see Browser extension), which may not be the tab where you clicked. Pass the page's data in through extract and param_map rather than reading it again with browser steps. If the workflow has browser steps and no tab is attached, the run is refused and the bubble shows an error that contains Browser not connected.

The Embed Buttons switch and privacy ​

The extension popup has a switch labelled Embed Buttons, described as "Show action buttons on sites". It is on by default.

  • On: for each site you open, the extension sends the site's host name to the local runtime at http://localhost:9876 to ask for buttons. The request does not leave your computer. Pages from the extension's restricted list (browser pages, the Chrome Web Store) are skipped.
  • Off: no host names are sent, and buttons already on open pages are hidden in every tab. When you switch it back on, those buttons reappear; pages loaded while the switch was off get buttons after a reload.

Known issue: the switch is restored from browser storage after the extension's background worker starts. A page that wakes the worker can be handled before that, so with the switch off its host name may still be sent once and its buttons shown. Also, on a page that keeps changing, the page watcher can add new buttons after the switch was turned off; reload the page to clear them.

When changes show up ​

The extension asks once per host name per connection to the runtime, and caches the answer, including an empty one. Reloading a page on the same host reuses the cached list, so a workflow you add or edit does not appear until the cache is cleared. That happens whenever the extension reconnects to the runtime (for example after you restart kadmo serve, see CLI) and whenever Chrome restarts the extension's background worker. If the runtime is not running, nothing is cached and the next page load asks again. The runtime address is fixed at port 9876, so only the runtime that holds that port provides buttons, and only kadmo serve answers the request: a kadmo mcp started by an MCP client serves no /api, so no buttons appear while it holds the port.

When the connection drops, the active tab hides its buttons. Other open tabs keep theirs, and a click there shows the error "App not connected".

Editing in the dashboard ​

The dashboard marks actions that have an embed block or a non-empty allowed_domains list with an "Embeddable" badge, and shows an "Embedded Button" section on the action's page. Its editor is limited:

  • It saves only selector, position, label and a "URL Pattern" field. Saving replaces the whole embed block, so extract, param_map, prompt, result, org, repo and parent_filter are lost.
  • It offers the positions Before, After and Inside. The extension treats "Inside" as append, and nothing reads the URL pattern.
  • It needs a selector, so floating buttons cannot be made there.

Edit the YAML file for anything beyond a simple button. The look of the buttons is fixed and cannot be styled.

Troubleshooting ​

SymptomCheck
No buttonThe Embed Buttons switch is on; the extension is connected; allowed_domains, org, repo, when and selector match the page; every workflow parameter has a valid param_map entry; the workflow loads: it is listed by kadmo list (see CLI).
A new or changed workflow has no buttonThe host's list is cached. Restart the runtime, then reload the page.
A parameter is emptyThe extract entry has an attribute; its selector finds the element at click time; the param_map value is exactly {{key}}.
pattern seems ignoredWhen the pattern does not match, the full value is kept. With multiple: true the pattern is not applied.
Button reads "Done" but shows no textThe workflow set no output message; end it with control.stop and a message.