WhipWhip

Build with a chat

Copy everything below, paste it into the chat you already use (ChatGPT, Claude, Gemini…), write what you want at the very end, and send. When the chat answers with whipset JSON, copy it into the import field of the WhipWhip panel. If something needs fixing, the panel gives you a list — paste that list back into the chat.

JSON schema · llms-full.txt

You are going to design a whipset for WhipWhip and write it as one JSON object. Read this whole guide first. At the very end, after "My request", I describe what I want. Ask me at most one short question if something essential is missing; otherwise answer with the JSON right away.

What WhipWhip is

WhipWhip is a browser extension. It puts a panel on top of the web chat I use (ChatGPT, Claude, Gemini, Grok or Perplexity) and runs saved procedures in that chat for me: it types into the chat input box, sends, waits for the answer, reads the answer, stores parts of it, and shows results.

WhipWhip does not call any API. Everything happens in the real chat page, exactly as if I typed it. So a whip can only do what a person could do in that page, one card at a time.

How the cards fit together

Talking to the chat. prompt_write puts text into the input box (it does not send). submit sends it and waits until the answer is complete. After submit, the answer is available.

Keeping the answer. var_save with "valueSource": "var.last.assistant" stores the whole latest answer in a variable. That is the normal way to keep a result. ask_and_extract is for pulling short named values out of an answer (a title, a score, a tag) — it adds an instruction to the prompt asking the model to append tagged values, then reads them. Do not use it to capture a whole long answer. extract_response asks for structured JSON in a separate temporary tab; use it rarely. validate checks that the latest answer has a format (JSON, a regex, or simply not empty) and asks the chat again if it does not.

Asking me. Two ways:

  1. Put a user_input variable into a prompt as a token. Before the whip runs, WhipWhip shows me a small form for every user_input variable the whip needs. This is the default and usually best.
  2. input_prompt asks me in the middle of a run, right where the card is. Use it when the question only makes sense at that moment (for example "name this clipping" after reading an answer), or with "alwaysAsk": true to confirm a value that is already filled in.

choice_prompt lets me pick from options — a fixed list (one option per line in options) or the items of a list variable.

Showing results. show_result_modal opens a window with text built from variables. Use "copyButton": "copy_body" when I will probably copy it.

Moving between conversations. new_chat opens a fresh conversation on the same service. open_service goes to another service. open_url opens an address. tag_current_chat gives the conversation the run is standing in a role. go_to_role moves the run to the conversation holding that role. A role must be given (tag_current_chat) before any whip can go to it (go_to_role), so a two-role set usually has one short "start" whip per role that primes the chat and tags it: prompt_write (who you are in this conversation) → submittag_current_chat.

Lists. A variable with "valueType": "array" holds a list of records. append_item adds a record (fields is a list of name/value pairs, and autoId gives each record an id), upsert_item adds or replaces by id, patch_item changes some fields of one record, remove_item deletes one. derive_index turns a list into text (one line per item, from a template with <<item.field>> tokens) and stores that text in another variable, so a prompt or a result window can show it. Lists are how a set remembers things across many runs (a notebook, a library, a log).

Variables.

Tokens

Inside card text, a variable is written as <<var.key>> — two angle brackets on each side. Inside a list template it is <<item.field>>. <<now>> is today's date. Never write tokens with curly braces. WhipWhip converts <<…>> itself.

The JSON shape

{
  "format": "whipwhip.authoring/1",
  "name": "…",                       set name, in my language
  "description": "…",                optional, one sentence
  "inline_guide": "…",               optional, how to use the set
  "tags": [ { "name": "writer" } ],  roles; 3–10 characters of A–Z a–z 0–9 _ -
  "variables": [ { "key": "…", "label": "…", "source": "…", "required": true } ],
  "whips": [
    {
      "label": "…",                  button text, short
      "description": "…",            optional
      "preflightStyle": "form",      optional: "form" (default) or "none"
      "scopeTags": [ "writer" ],     optional: the role conversation this whip belongs to
      "actions": [ { "card": "prompt_write", "config": { "text": "…" } } ]
    }
  ]
}

Do not write ids, usedVars, requiresVars, producesVars, askInline, keys, signatures or versions. WhipWhip fills those in.

Card reference

prompt_write

Write or modify text in the chat window.

submit

Send the current text, and wait for the AI's reply unless you turn waiting off.

input_prompt

Ask the user to provide necessary information.

ask_and_extract

Prompt the AI and extract key information as variables automatically.

extract_response

Analyze the AI response and convert it into structured data. Opens a temporary tab in the foreground while it works.

validate

Check the reply against a FORMAT rule and re-ask if it does not match. Format only, never quality.

send_to_services

Send the same text to the chosen AIs and gather every answer in one variable. Other AIs open in background tabs.

var_save

Save text from the composer or AI response into a variable.

var_flush

Clear all stored variable values.

No settings — use "config": {}.

append_item

Add an item to an array variable (e.g. append a record to an ADR list).

upsert_item

Insert or update an item in an array variable, matched by its "id" field.

patch_item

Update only the named fields of an item matched by id, keeping its other fields (e.g. flip status).

remove_item

Remove an item from an array variable by its "id".

derive_index

Build a summary/index string from an array variable, one line per item.

go_to_role

Move the run to the conversation a role is bound to.

new_chat

Open a new conversation on the current service.

open_service

Switch to a different AI service (ChatGPT, Gemini, Claude…).

open_url

Navigate to any https:// URL.

tag_current_chat

Assign a tag to the current conversation.

choice_prompt

Show the user a list of options and store their pick in a variable.

show_result_modal

Display a result window using stored variable values.

import_whipset

Import a chat-written whipset JSON held in a variable. You confirm it in a preview before anything is saved.

Value sources (var_save.valueSource)

builtin.date_today · builtin.date_iso · builtin.time_now · builtin.timestamp · builtin.day_of_week · builtin.page_url · builtin.page_title · builtin.page_domain · builtin.language · builtin.random_uuid · builtin.selection · composer · var.last.user · var.last.assistant

Common mistakes

Examples

One conversation: polish a draft and show it

{
  "format": "whipwhip.authoring/1",
  "name": "Draft Polisher",
  "description": "Rewrite a rough draft clearly and show the result.",
  "tags": [],
  "variables": [
    { "key": "draft", "label": "Draft", "source": "user_input", "required": true },
    { "key": "polished", "label": "Polished text", "source": "set_by_action", "required": false }
  ],
  "whips": [
    {
      "label": "Polish",
      "actions": [
        { "card": "prompt_write", "config": { "text": "Rewrite the draft below so it is clear and concise. Keep my meaning and tone. Reply with the rewritten text only.\n\n<<var.draft>>", "mode": "replace" } },
        { "card": "submit", "config": {} },
        { "card": "var_save", "config": { "valueSource": "var.last.assistant", "key": "polished" } },
        { "card": "show_result_modal", "config": { "title": "Polished", "body": "<<var.polished>>", "copyButton": "copy_body" } }
      ]
    }
  ]
}

Two roles: a writer and a critic in separate chats

{
  "format": "whipwhip.authoring/1",
  "name": "Writer and Critic",
  "inline_guide": "Open a chat and press Start writer, open another chat and press Start critic. Then run Write and review from anywhere.",
  "tags": [ { "name": "writer" }, { "name": "critic" } ],
  "variables": [
    { "key": "task", "label": "What to write", "source": "user_input", "required": true },
    { "key": "draft", "label": "Draft", "source": "set_by_action", "required": false },
    { "key": "review", "label": "Review", "source": "set_by_action", "required": false },
    { "key": "final", "label": "Final", "source": "set_by_action", "required": false }
  ],
  "whips": [
    {
      "label": "Start writer",
      "actions": [
        { "card": "prompt_write", "config": { "text": "In this conversation you are my writer. You write complete drafts and revise them when I bring criticism. Reply OK.", "mode": "replace" } },
        { "card": "submit", "config": {} },
        { "card": "tag_current_chat", "config": { "tagName": "writer" } }
      ]
    },
    {
      "label": "Start critic",
      "actions": [
        { "card": "prompt_write", "config": { "text": "In this conversation you are a demanding critic. When I show you a draft, list its weakest points as short numbered items. Reply OK.", "mode": "replace" } },
        { "card": "submit", "config": {} },
        { "card": "tag_current_chat", "config": { "tagName": "critic" } }
      ]
    },
    {
      "label": "Write and review",
      "actions": [
        { "card": "go_to_role", "config": { "tagName": "writer" } },
        { "card": "prompt_write", "config": { "text": "<<var.task>>", "mode": "replace" } },
        { "card": "submit", "config": {} },
        { "card": "var_save", "config": { "valueSource": "var.last.assistant", "key": "draft" } },
        { "card": "go_to_role", "config": { "tagName": "critic" } },
        { "card": "prompt_write", "config": { "text": "Task: <<var.task>>\n\nDraft:\n<<var.draft>>", "mode": "replace" } },
        { "card": "submit", "config": {} },
        { "card": "var_save", "config": { "valueSource": "var.last.assistant", "key": "review" } },
        { "card": "go_to_role", "config": { "tagName": "writer" } },
        { "card": "prompt_write", "config": { "text": "Revise your draft using this criticism. Output the full revised version.\n\n<<var.review>>", "mode": "replace" } },
        { "card": "submit", "config": {} },
        { "card": "var_save", "config": { "valueSource": "var.last.assistant", "key": "final" } },
        { "card": "show_result_modal", "config": { "title": "Draft, review, final", "body": "[Draft]\n<<var.draft>>\n\n[Review]\n<<var.review>>\n\n[Final]\n<<var.final>>", "copyButton": "copy_body" } }
      ]
    }
  ]
}

A list that grows: a notebook of answers

{
  "format": "whipwhip.authoring/1",
  "name": "Answer Notebook",
  "tags": [],
  "variables": [
    { "key": "clip", "label": "Clipped answer", "source": "set_by_action", "required": false },
    { "key": "clip_title", "label": "Title", "source": "set_by_action", "required": false },
    { "key": "notes", "label": "Notes", "source": "set_by_action", "valueType": "array", "required": false },
    { "key": "notes_index", "label": "Note list", "source": "set_by_action", "required": false },
    { "key": "notes_full", "label": "All notes", "source": "set_by_action", "required": false },
    { "key": "summary", "label": "Summary", "source": "set_by_action", "required": false }
  ],
  "whips": [
    {
      "label": "Save this answer",
      "actions": [
        { "card": "var_save", "config": { "valueSource": "var.last.assistant", "key": "clip" } },
        { "card": "input_prompt", "config": { "title": "Save this answer", "selectedVars": [ { "key": "clip_title", "question": "Give it a short title" } ], "inputStyle": "inline", "alwaysAsk": true } },
        { "card": "append_item", "config": { "key": "notes", "autoId": true, "idPrefix": "note-", "fields": [ { "name": "title", "value": "<<var.clip_title>>" }, { "name": "text", "value": "<<var.clip>>" }, { "name": "date", "value": "<<now>>" } ] } },
        { "card": "derive_index", "config": { "sourceKey": "notes", "key": "notes_index", "itemTemplate": "<<item.id>> — <<item.title>> (<<item.date>>)" } },
        { "card": "show_result_modal", "config": { "title": "Notes", "body": "<<var.notes_index>>" } }
      ]
    },
    {
      "label": "Summarize my notes",
      "actions": [
        { "card": "derive_index", "config": { "sourceKey": "notes", "key": "notes_full", "itemTemplate": "### <<item.title>>\n<<item.text>>" } },
        { "card": "new_chat", "config": { "openMode": "current" } },
        { "card": "prompt_write", "config": { "text": "Summarize what these notes have in common and what they add up to.\n\n<<var.notes_full>>", "mode": "replace" } },
        { "card": "submit", "config": {} },
        { "card": "var_save", "config": { "valueSource": "var.last.assistant", "key": "summary" } },
        { "card": "show_result_modal", "config": { "title": "Summary", "body": "<<var.summary>>", "copyButton": "copy_body" } }
      ]
    }
  ]
}

Output rules

  1. Think about which whips I will press and in what order, then design the smallest set that works.
  2. Write names, labels, questions, prompts and result titles in the language I write my request in. Card ids, field names, variable keys and tag names stay as in this guide.
  3. Answer with one JSON code block containing the whole whipset. You may add at most two short sentences after the block telling me which button to press first.
  4. If I paste back a list of problems from WhipWhip, fix every item and send the complete corrected JSON again, not a partial patch.

My request