Guide
Change wisp itself, by asking: spells
In wisp you change the app itself by asking: “a floating button that opens all my PRs”, “a panel with my tool stats”, “a deploy block for agents”, “compact cards”. An agent writes the change as a spell, a small JSON file wisp layers over itself. You try it in the window, keep it, and share it as a file.
Free and open source · Claude Code, Codex, Cursor CLI, opencode, Antigravity CLI, Grok Build · macOS, Linux, Windows (beta) · Node 20+
Above: a working copy. Pick an ask and the little wisp window changes the way the app does; switch a spell off and it changes back. { } shows the spell file.
Cast one
- Start wisp.
- Say what to change. ⌘K → Cast a spell…, Settings → Spells, or type
/spellin the prompt box:/spell a floating button that opens all my open PRs, with how many there are. Be as specific as you like about where it goes and what it shows. - Look at the preview. The agent works in an empty folder of its own: it reads wisp’s spell reference, samples your real data, checks the spell with wisp until it passes, and hands it back as a card. The card shows its buttons as they will look, its panels drawn with your data, and a plain-words list of what it adds.
- Try it, then keep it. Try it applies the spell to this window only; Undo takes it back. Turn on keeps it. A follow-up on the card changes it (“make it a top bar button”, “only for api-server”), and “remove the … spell” drops it.

Every spell you have is in Settings → Spells, with a switch, its reach, and export, copy and remove.
What a spell can change
| Ask for | What wisp does |
|---|---|
| A floating button that opens all my PRs | A button in a corner with a live count, from a read-only gh search prs wisp runs; one click opens them all |
| My wisp tool stats | A top bar button that opens a dialog of stats and charts from /usage/tools |
| A deploy block for agents | A new block type agents are told about and can end their reports with |
| Quick replies, a second opinion | Chips above the follow-up box, a Second opinion button that reruns the task on Codex in Plan mode |
| Compact cards, PR first, Open PR on hover | Restyles and reorders the card head, adds a hover button on cards that have a PR |
| A bigger, monospace prompt box | Placeholder, rows and font of the prompt box; hides the parts you don’t use |
| A violet wisp | Sets design tokens (--accent, --radius, …), with separate values for dark mode |
| A /explain command | A slash command for the follow-up box that sends a prompt with what you typed after it |
| Send failing tests back once | A hook: when a turn ends with failing tests, wisp replies to the agent, once per task |
| Codex always runs the linter first | A rule added to Codex agents’ prompts |
What’s in a spell file
A spell is one JSON object. Only wispSpell, id and name are required; everything else is a list of things to add or change. This is the whole My PRs spell:
{
"wispSpell": 1,
"id": "my-prs",
"name": "My PRs",
"version": "1.0.0",
"description": "Open every PR you have open on GitHub, from a floating button.",
"data": {
"prs": {
"gh": ["search", "prs", "--author", "@me", "--state", "open", "--json", "url,title,repository"],
"refresh": 120
}
},
"buttons": [
{
"slot": "float",
"icon": "github",
"label": "My PRs",
"badge": {
"count": "prs"
},
"do": {
"openAll": {
"data": "prs",
"field": "url"
}
}
}
]
}
id,version: the file is~/.wisp/spells/<id>.json. A changed spell keeps its id and bumps its version, so installing it again updates it in place.data: named sources wisp fetches for the spell. Hereprsis a read-onlyghcommand, refreshed every 120 seconds.buttons: each names a slot, a label, an icon, an optional badge (here: how many itemsprshas) and what it does.
The other top-level fields:
| Field | What it holds |
|---|---|
description | One sentence, shown in Settings → Spells and on its review |
chips | Small labels on a card head or under the prompt and follow-up boxes, from a template or a script |
panels | Dialogs, drawers and panels above the feed or inside a card, drawn by a script from the spell’s data |
parts | Restyle a named part of wisp, set its props (a monospace prompt box, compact cards) or reorder the card head |
ui | hide named parts; tokens and dark recolour wisp |
blocks | New report block types agents can use |
prompt | Rules added to agents’ prompts, for all tasks or some |
settings | Settings that win over yours while the spell is on (shown as Set by … in Settings) |
slash | /commands for the follow-up box |
commands | ⌘K items, optionally with a single-key shortcut |
hooks | Something wisp does on its own when a turn finishes, fails, starts or waits |
recipes | Saved prompts with {params} |
harnesses, when | Which harnesses, projects, agents and modes its rules, blocks and hooks apply to |
script | JavaScript functions that turn data into blocks, strings or links, run in a sandbox |
Text, labels, links and gh arguments can use {field} templates filled from the card’s task: {title}, {branch}, {project}, {agent}, {pr.number}, {pr.url} and more. In slash commands, {args} is what you typed after the command.
Where things can go
Spells never touch wisp’s page directly. They name a slot, and wisp puts the button, chip or panel there with its own markup, so wisp updates don’t break them.
| Slot | Where |
|---|---|
topbar | The top bar, next to Automations and Search: a labelled button with an optional count. |
float | Floating buttons, stacked in a bottom corner of the window. Buttons with the same group fold into one that fans out. |
palette | An item in the ⌘K palette. |
feed.top | Above the feed: a panel pinned there (panels only). |
launcher.bar | In the prompt box bar, before Send. |
launcher.chips | Chips under the prompt box (chips and buttons). |
composer.bar | In a card's follow-up box bar, before Send. |
composer.chips | Chips above a card's follow-up box (quick replies). |
card.head | A chip in a card's head, before the time. |
card.hover | A small icon button on a card head, shown on hover. |
card.menu | An item in a card's right-click menu. |
card.top | A panel at the top of an open card (panels only). |
card.bottom | A panel at the bottom of an open card, above the follow-up box (panels only). |
sidebar.top | Split view: a button at the top of the task list, next to New task. |
split.head | Split view: a button in the open task's header, next to its title (acts on that task). |
Panels open as a dialog, a side drawer, pinned above the feed, or inside an open card. To restyle or hide something, a spell names a part (card.title, launcher.input, topbar.machines, …) and sets a short list of allowed CSS properties, props such as density or monospace, or the order of the card head.
The split view (tasks in a sidebar, the open one beside it) is a spell’s too: settings: { "view": "split" } switches to it, the sidebar part takes width, side (left, the default, or right) and details (full shows the model, machine and project on every row), split.main takes a readWidth, split.home a greeting over the prompt box, and the sidebar.top and split.head slots take buttons.
What buttons can do
Each action has a reach, and a spell’s reach is the furthest of its actions. You see it before you turn the spell on.
| Action | Reach | Does |
|---|---|---|
open | Safe | Open one http(s) link (a {field} template) |
openAll | Safe | Open every link in a data source (at most 20). Or { "fn": "name" }: a script function returning links, each of which must appear in the spell's data |
panel | Safe | Open one of this spell's panels |
insert | Safe | Put text into the box at the caret (prompt box or follow-up box) |
copy | Safe | Copy text |
toast | Safe | Show a short message |
setLaunch | Safe | Set the prompt box pickers |
send | Acts for you | Send a follow-up to the card's task right away |
reply | Acts for you | Same as send, used by hooks |
launchWith | Acts for you | Launch what is in the prompt box with these settings |
launch | Acts for you | Start a new task (cwd empty = the current project) |
rerun | Acts for you | Run the card's task again as a new task on another harness |
recipe | Acts for you | Run a recipe |
ship | Acts for you | Open Ship it for the card's task |
done | Acts for you | Mark the card's task done |
archive | Acts for you | Archive the card's task |
notify | Acts for you | A desktop notification / toast (hooks: your phone, if linked) |
webhook | Runs commands | POST the task summary as JSON to a URL (hooks) |
exec | Runs commands | Run a shell command in the task's folder (asks you the first time) |
Data and scripts
A spell can read two kinds of data, and wisp fetches both; spell code never touches the network.
- wisp’s own routes, read-only:
/sessions,/sessions/:id,/usage,/usage/tools,/projects,/recipes,/triggers,/machines,/agents,/preferences,/spells. - Read-only
ghcommands:gh pr list|view|status|checks|diff,gh search prs|issues|repos|commits,gh issue list|view|status,gh run list|view,gh repo view|list,gh release list|view,gh workflow list|view,gh api.gh apimay only read: no-X,-f,-For--input.
To draw a panel, a spell carries a script: plain JavaScript functions that get the data and return wisp’s report blocks (stats, charts, checklists, callouts, tables of files, …). wisp checks what comes back and draws it with its own renderer. This is the panel behind Tool stats:
{
"wispSpell": 1,
"id": "tool-stats",
"name": "Tool stats",
"version": "1.0.0",
"data": {
"tools": {
"wisp": "/usage/tools?days=30"
}
},
"buttons": [
{
"slot": "topbar",
"icon": "stats",
"label": "Tools",
"do": {
"panel": "tools"
}
}
],
"panels": [
{
"id": "tools",
"title": "Tool calls · last 30 days",
"as": "dialog",
"data": ["tools"],
"render": "tools"
}
],
"script": "export function tools({ tools: t }) {\n return [\n { type: 'stats', items: [{ label: 'tool calls', value: t.total.calls }, { label: 'turns', value: t.total.turns }, { label: 'spend', value: Math.round(t.total.cost * 100) / 100, unit: '$' }] },\n { type: 'chart', title: 'By kind', items: t.kinds.map((k) => ({ label: k.kind, value: k.calls })) },\n { type: 'chart', title: 'By harness', items: t.agents.map((a) => ({ label: a.agent, value: a.calls })) },\n ];\n}"
}
Blocks, prompt rules and hooks
Spells also change what agents do, for every harness or only some (harnesses), and for every task or only some projects, agents or modes (when).
- Blocks. A new report block type. wisp tells agents about it while the spell is on, and the spell’s script draws it. The
docline is what agents read. - Prompt rules. Lines added to agents’ system prompts: “Codex runs
npm run lintbefore reporting”. - Hooks. On
turn.finished,turn.failed,session.started,session.waiting, ifstatus,testsFailed,agent,project,hasPr,modematch, reply, mark done, archive, notify or call a webhook.maxcaps how often per task (default once).
{
"wispSpell": 1,
"id": "fix-failing-tests",
"name": "Fix failing tests",
"version": "1.0.0",
"hooks": [
{
"on": "turn.finished",
"if": {
"testsFailed": true
},
"max": 1,
"do": {
"reply": "Some tests fail. Fix them, rerun the suite and report again."
}
}
]
}
Why it’s safe to keep one
- Spell code can’t reach anything. It runs in a sandboxed frame with no network, no page and no wisp API. It gets the data wisp fetched and returns blocks, which wisp checks and draws itself.
- Data is read-only. Spells read wisp’s own routes and read-only
ghcommands. They can’t merge, push or write. - You see how far it reaches. Each spell is Safe, Acts for you, Reads GitHub or Runs commands, with every command listed. A command asks you before its first run.
- Some things stay put. Questions, approvals and Settings → Spells can’t be restyled or hidden, so a spell can’t lock you out of turning it off.
- Off means gone. Turning a spell off puts wisp back exactly as it was.
WISP_SAFE=1starts wisp with every spell off.
Share it with your team
Settings → Spells exports a spell as a .spell.json file, or copies its JSON for a chat. A colleague drops the file on wisp, pastes it (or a link) into Settings → Spells, or runs:
wisp spell install ./my-prs.spell.json --on
wisp spell install https://example.com/my-prs.spell.json
wisp spell install gist:<id>
They see the same review first, and it stays off until they turn it on. Commit a team’s spells to a repo and everyone installs the same set.
From the terminal
| Command | Does |
|---|---|
wisp spell | List your spells: ● on, ○ off, and any that are broken |
wisp spell cast "…" | An agent writes a spell for it; keep it in the app. --wait blocks until it’s done; --agent, --model, --effort pick who writes it |
wisp spell install <file|url> | Check and add a spell (a file, an https link or gist:<id>). Off until you add --on |
wisp spell on|off <id> | Turn one on or off; wisp spell off --all turns every spell off |
wisp spell rm <id> | Remove it |
wisp spell export <id> | Print the spell file, to save or send |
wisp spell cast "a /explain command that asks for a plain-words explanation" --wait
wisp spell export my-prs > my-prs.spell.json
wisp spell off --all
Over HTTP the same lives under /spells; see Integrations.
When something looks wrong
- wisp looks odd and you don’t know why. Settings → Spells → Turn every spell off, then turn them back on one at a time. If wisp won’t even open properly, start it with
WISP_SAFE=1. - A spell is listed as broken.
wisp spellprints! <id>: <why>. Usually it names a slot, part or action this version of wisp doesn’t have; ask for a follow-up on its card, or remove it. - A button doesn’t show. Card buttons can have a
when(only cards with a PR, only Codex tasks, …), and hidden parts take their buttons with them. Ask the agent which applies. - The data is empty.
ghsources need the GitHub CLI installed and signed in (gh auth status) on the machine wisp runs on.
Questions
- Do spells change wisp’s code?
- No. A spell is a JSON file in ~/.wisp/spells that wisp reads at runtime. Your wisp install stays as it is, updates keep working, and turning a spell off removes everything it added.
- Which agents can cast spells?
- Any harness wisp runs: Claude Code, Codex, opencode, Cursor, Antigravity or Grok. The agent works in an empty folder of its own and uses wisp’s spell tools to read the reference and check its spell.
- Can a spell I get from someone else run code on my computer?
- Only if it says so: a spell whose buttons run commands is marked Runs commands and lists each command, and asks before the first run. Spell scripts run in a sandbox with no network or file access.
- Can I write a spell by hand?
- Yes. Write the JSON, then run wisp spell install ./my.spell.json: wisp checks it the same way it checks a cast spell and tells you, in plain words, what it does or what is wrong.
- What if I ask for something spells can’t do?
- The agent says so in its report and does the closest thing spells can. Spells can only use the slots, parts, actions and data sources in wisp’s reference, so they can’t reach further than that.
Related guides
- Run coding agents in the backgroundHeadless runs that can still ask you, notify you, and report back in one screen.
- Multi-agent coding across Claude Code, Codex and moreA lead agent with a team on other harnesses, working in parallel.
- Run Claude Code and Codex in parallelSame task on both agents, or different tasks at once, each on its own branch.