Typeclick Get early access

Docs

Install Typeclick

One script tag adds Ask AI to your pages. This page covers the tag and its options, and what to change when your pages send a Content Security Policy or your app runs inside an iframe.

Add the script

Put this tag on every page where Ask AI should show up. Your site key comes with your site.

<script src="https://typeclick.apps.clawnify.com/typeclick.js" data-site="tc_your_site_key" defer></script>

Typeclick only answers pages on your site's domains, so list every host the script runs on: app.example.com, or *.example.com for example.com and all its subdomains. Requests from anywhere else are refused with a 403.

Mark your page

  • data-typeclick="skip" goes on anything that isn't your product, like a chat widget, a cookie banner or your own help menu. Typeclick never offers it or reads it.
  • data-typeclick="anchor" goes on the button the Ask AI pill should sit next to.
  • Both at once: data-typeclick="skip anchor".

Your site settings take CSS selectors for the same two jobs, for parts of a page you can't add attributes to.

Tell it about your product

Notes are short lines that go to Jev, the decision model, with every step. Write what your pages don't say: what things are called, where a setting lives, which of two similar buttons to use.

Deals are called opportunities here.
Billing lives under Settings, then Workspace.

Tag attributes

  • data-site: your site key. Required.
  • data-position: bottom-right, the default, or bottom-left for the Ask AI pill.
  • data-api: where Typeclick's API is, for a copy of the script on your own domain. See Content Security Policy.

Content Security Policy

If your pages send a Content Security Policy, add Typeclick's host to two directives:

script-src 'self' https://typeclick.apps.clawnify.com;
connect-src 'self' https://typeclick.apps.clawnify.com;

Nothing else changes. The script sets its styles through the DOM, so style-src stays as it is, and it loads no images or fonts.

If your policy uses a nonce instead of a host list, put your nonce on the Typeclick tag. The script creates no other scripts, so connect-src is the only directive that still needs our host.

Can't add our host to script-src? Serve a copy of typeclick.js from your own domain and point it at our API with data-api. Then only connect-src needs our host. A copy doesn't update itself, so refresh it now and then.

<script src="/js/typeclick.js" data-site="tc_your_site_key" data-api="https://typeclick.apps.clawnify.com" defer></script>

Apps inside an iframe

Typeclick reads the page it runs in, and only that page. When your product runs inside a frame, like an app inside a dashboard, put the tag inside the framed page, not the outer one.

  • List the frame's own host in your domains. Its requests come from there.
  • The pill shows up inside the frame. If the outer page covers the frame's bottom right corner, use data-position="bottom-left".
  • The outer page's menus are out of reach, so a request that needs one won't be guided.

If the outer page is yours too, it can show the pill. Add data-host-origin to the tag in the frame, with the outer page's origin (several separated by spaces). The frame then draws no pill and follows the outer page's requests, while the outlines and the cursor stay in the frame, where the product is.

<script src="https://typeclick.apps.clawnify.com/typeclick.js" data-site="tc_your_site_key" data-host-origin="https://app.example.com" defer></script>

The outer page and the frame talk with postMessage. The frame says ready when it starts, and again when the outer page says hello, so it doesn't matter which loads first. The outer page answers host, then sends requests and shows the state the frame reports. If no host answers within three seconds, the frame draws its own pill.

const frame = document.querySelector("#app-frame");
const origin = new URL(frame.src).origin;
const send = (msg) => frame.contentWindow.postMessage({ target: "typeclick", ...msg }, origin);

addEventListener("message", (e) => {
  if (e.source !== frame.contentWindow || e.origin !== origin || e.data?.source !== "typeclick") return;
  if (e.data.type === "ready") send({ type: "host" });
  if (e.data.type === "state") showPill(e.data.state, e.data.text); // "busy" or "result"
});
send({ type: "hello" });

askForm.onsubmit = () => send({ type: "ask", goal: askInput.value });
stopButton.onclick = () => send({ type: "stop" });

The outer page can offer its own controls too, like a sidebar that switches the app's pages. Send them as controls, and Typeclick uses them like the frame's own:

  • controls (outer page to frame): id, label, role, region (start it with "sidebar" for navigation), state ("currently selected" for the open page) and opens: page when the frame changes in place, app when the frame is replaced, view when the frame goes away.
  • show and clear (frame to outer page): outline that control, or remove the outline.
  • click (frame to outer page): click it, then answer clicked with the same req and ok. When it opens an app, the message carries run: send it to the next frame that says ready, as resume, and the run goes on there.
  • A control that opens a view isn't clicked: Typeclick points at it and stops.

That's how Typeclick runs in Clawnify's demo apps: the tag is in each app's frame, the pill sits in the outer page next to its "Use this app" button, and the workspace sidebar is offered as controls.

Single-page apps

There's nothing to set up. Typeclick follows client-side navigation, and a run in progress picks up again after a full page load in the same tab, as long as the next page loads within a minute.

Start a run from your own buttons

Once the script has loaded, window.typeclick.ask() starts a run as if the request had been typed into the pill. It returns false while another run is going.

document.querySelector("#billing-help").addEventListener("click", () => {
  window.typeclick?.ask("Change my billing email");
});

What it reads and sends

Each step sends the user's request to Jev, with the buttons, fields and text on the page and what was typed into them. Password fields are never read.

For each run, Typeclick keeps the request, how it ended and the labels of what it clicked. It doesn't keep what was typed.

What it won't do

  • Type for your users. Fields are always theirs to fill.
  • Press a button that writes data. Save, send, delete, pay, cancel and the like wait for your user's click.
  • Reach into shadow DOM (web components) or interfaces drawn on a canvas. It can't see those yet.