Skills DirectorySkills Directory
SkillsLearnSecurityCategoriesDocsBlogPro
Sign InSubmit Skill
Skills Directory

Security-tested agent skills for Claude, coding agents, and AI workflows.

Directory

  • Browse Skills
  • All Skills A–Z
  • Claude Skills
  • Claude Code Skills
  • Agent Skills
  • Categories
  • Authors
  • Submit a Skill

Learn

  • Learn Hub
  • Install Claude Skills
  • Write SKILL.md
  • Skills vs MCP
  • Directories Compared

Security

  • Security
  • Methodology
  • Secure Claude Skills
  • Security Badges
  • Chrome Extension
  • Skill Manager

Company

  • About
  • Community
  • Blog
  • API Docs
  • Advertise

2026 Skills Directory. All rights reserved.

ProTermsPrivacyRefunds
Back to skills

Weft Consumers

ASecurity

Read when the user wants their own website, app, bot or extension to list, show, answer, skip or cancel what a program is waiting on, fetch a file it carries, or show what one of its nodes is displaying (a WhatsApp bridge's QR code, a database's minted password): the api token, the doors, the payload shapes, and how a new signal kind reaches a consumer.

1,992 stars
0 votes
0 copies
0 views
Added 10/4/2026
developmentgoshellbashnodeawsapidatabasefrontend

Works with

cliapi

Security Analysis

A100/100

Scanned 10/4/2026

$npx -y skills add WeaveMindAI/weft --skill weft-consumers --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Weft Consumers?

Add the live security badge to your README — it updates automatically with every re-scan.

Security grade badge for Weft Consumers
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/weavemindai-weft-consumers/badge)](https://www.skillsdirectory.com/skills/weavemindai-weft-consumers)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
Files
SKILL.md
---
name: weft-consumers
description: "Read when the user wants their own website, app, bot or extension to list, show, answer, skip or cancel what a program is waiting on, fetch a file it carries, or show what one of its nodes is displaying (a WhatsApp bridge's QR code, a database's minted password): the api token, the doors, the payload shapes, and how a new signal kind reaches a consumer."
---

# Your own consumer of a weft program

A [signal] is one thing a running program waits on: a parked question, a
trigger that starts a run, a webhook, a timer. Every [signal] has a
[signal token], the credential that fires it (the `token` of its listing
entry). An [api token] lets a [consumer] list the [signal]s it may see. A
[consumer] is any client of the [door]s below: the weft browser extension
is one, for the form kind; anything that can send HTTP is another, for that
kind or any kind that renders a consumer payload. A frontend under `front/`
(the `weft-frontend` skill) is one more [consumer] of exactly these
[door]s, for the human steps the program parks; the program's own `Route`
nodes serve its ordinary data calls (the `weft-api` skill).

## The token

Whoever runs the project mints the [api token]:

```bash
weft token mint --name "my website"
weft token mint --name "reviewer" --projects <id> --tags approvals
weft token mint --name "my website" --display whatsapp
```

It prints, once, an address of the form
`http://host:port/signal-token/<token>`: the part before `/signal-token/`
is the dispatcher the [consumer] talks to, the last segment is the token.
The bare token follows on the second line. A script reads it with `--json`
and takes `.token` (`weft token mint ... --json | jq -r .token`): the
"Copy it now" note goes to stderr, and a shell that merges the two streams
puts it last, so `tail -n 1` would read the note. The server keeps a hash only, so a lost
token is revoked (`weft token ls`, `weft token revoke <id>`) and a new one
minted. A token with no scope sees every [signal] of the tenant;
`--projects` and `--tags` narrow it, both repeat.

**The display scope reads the other way round.** `--display` and
`--displays` GRANT what a node is showing, and a token that names neither
reads none of them. A QR code pairs the account to whoever scans it, so it
takes an explicit word rather than arriving with the wildcard. Ask which
nodes the [consumer] should watch before minting, and mint the narrow one.

`--display` takes the node the way the user writes it (`whatsapp`, or
`test.whatsapp` for a node of a file included as `test`), and the CLI resolves
it against the project the user is standing in, so it is run from that folder.
An address is only a name inside one project, and the grant the dispatcher
stores carries both halves. Mint refuses a name no display in that project
answers to, and lists the ones it has.

Every [door] below except firing takes the [api token] as
`Authorization: Bearer <token>`. It never goes in a URL: proxies and access
logs would keep it. When the [consumer] is a website, it stays server side:
a browser page holding it can list every [signal] it sees.

## The four doors

A [door] is one HTTP endpoint of the dispatcher. The [door]s know nothing of
any kind: they list, fire, skip, cancel and link the same way whatever the
[signal] is.

| [door] | What it does |
|---|---|
| `GET /signal-token/signals` | the [signal]s this [api token] may see and that render a consumer payload, one [entry] each (shape below) |
| `POST /signal/{signal token}` | fire one [signal] with a JSON body: answer a question, start a trigger's run, deliver a webhook's payload. No bearer: the [signal token] is the credential |
| `POST /signal/{signal token}/skip` | fire it with no payload: a parked question resumes unanswered (the node's outputs close and downstream skips). No bearer, same rule as firing |
| `GET /signal-token/signals/{signal token}/files/{field}` | a fresh link for a stored file a [signal]'s field shows, good for an hour. You ask every time you render and never store the link |

Two more, for a [consumer] that manages [signal]s:
`DELETE /signal/{signal token}` cancels the run behind a [signal] (answers
204), and
`DELETE /signal-token/signals` cancels every run the [api token] sees and
drops its triggers (answers `{ execution_ids_cancelled, entry_signals_dropped }`).
Both need an [api token] with **no tag scope** and full project view; a
narrowed token gets 403, because a cancel reaches sibling questions of the
same run that the token may not see. `GET /signal-token/health` answers
`{ "ok": true }` for a valid [api token].

## Showing what a node is showing

A [display] is what one node shows about itself while it runs. Three more
[door]s, the [api token] as bearer on all three:

| [door] | What it does |
|---|---|
| `GET /signal-token/displays` | the displays this token may watch: `{ project_id, project_name, node, node_type, kind, label?, status? }` each, `kind` being `infra` or `trigger`. An `infra` entry's `status` is where the copy behind it stands: `none` (never started), `provisioning` (starting), `running`, `flaky`, `stopping`, `stopped`, `terminating` or `failed`. `node` is spelled the way a person writes it, and it is what `{node}` takes in the two doors below |
| `GET /signal-token/displays/{project}/{node}` | what that node is showing right now |
| `POST /signal-token/displays/{project}/{node}/action` | press a button one of its items carried: `{ "kind": "<actionKind>", "payload": ... }`. Only an `infra` node has buttons; a `trigger`'s [display] is read-only and answers 400 |

A display is `{ "items": [...] }`, each item `{ type, label, data, action? }`:

```json
{ "items": [
  { "type": "image", "label": "Scan with WhatsApp", "data": "data:image/png;base64,..." },
  { "type": "text",  "label": "Phone", "data": "not paired",
    "action": { "label": "Disconnect phone", "actionKind": "unpair",
                "confirm": "Detach the paired phone?" } }
] }
```

`type` is one of four and says how to draw it:

| `type` | `data` is | draw it as |
|---|---|---|
| `text` | a string | plain text, copyable |
| `image` | a `data:` URI or a URL | straight into an `<img src>` |
| `progress` | a number from 0 to 1 | a bar |
| `secret` | a string | masked until the reader asks to see it, and copyable without revealing |

`label` is on every item. `action` is at most one per item and draws a
button: show `confirm` first when it is there, POST `actionKind` to the
action door, and read the display again right after, because the press is
what changed it.

```js
const res = await fetch(`${base}/signal-token/displays/${project}/${node}`, {
  headers: { Authorization: `Bearer ${token}` },
});
if (!res.ok) throw new Error(await res.text());
const { items } = await res.json();

// pressing an item's button
await fetch(`${base}/signal-token/displays/${project}/${node}/action`, {
  method: 'POST',
  headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
  body: JSON.stringify({ kind: item.action.actionKind, payload: null }),
});
```

`base` is the part of the minted address before `/signal-token/`, the same
one every other door uses.
If the page is part of a website, its browser never calls that address: it
calls `/weft` on its own site, whose server passes the call on (the
pass-through in the `weft-frontend` skill), so there `base` is `/weft`.

**Read the display on every render, never store it.** A QR code expires in
under a minute, and a bridge that got paired in between shows a phone number
where the code was. Poll it every few seconds while the reader is looking at
it, and stop when they navigate away.

Who serves it depends on `kind`, and a client drawing items does not care: an
`infra` node's own container serves it, a `trigger`'s signal kind serves it
from the listener. What differs is what shows up. A container shows whatever
its author wrote, buttons included. A trigger with a public entry shows the
whole address a caller sends to (host, methods and all, ready to use as it is)
and how the door checks them, which is either open or a named connection and
never a secret; one that fires from inside the runtime shows what it is doing
(a timer its schedule, a poll the URL it reads and how often).

A node the token was not given, a project it cannot see and a node with
nothing to show all answer 404, so a caller walking ids learns only "nothing
here". A token that reaches no display at all gets a 403 naming the flag that
would have given it one, so ask the operator to mint a token with `--display
<node>` rather than guessing that the project shows nothing. While there
is nothing behind the display yet the door answers 404 as well, so read the
listing's `status` to say which: `provisioning` is "starting" (a copy can take
minutes, so show that it is on its way, and read the feed again once it says
`running`), `none` or `stopped` is "not started" with the button that starts
it, and `failed` is the error. A trigger's display 404s until the project is
activated.

## What a listed signal looks like

An [entry] is one object of the listing:

```json
{
  "token": "…",              // the per-task credential, used in the answer doors
  "nodeId": "ask",
  "kind": "form",
  "consumerKind": "human_in_the_loop",
  "title": "Approve this reply?",
  "description": "…",         // absent when the node set none
  "isResume": true,           // true: a parked question; false: a trigger to start a run
  "formSchema": { "fields": [ … ] }
}
```

`token`, `nodeId`, `kind`, `isResume` and `title` are on every [entry]; the
rest is the kind's own payload, and `kind` says how to read it. `isResume`
splits the list: a parked question disappears once answered (a second
answer gets 404 once it is gone, 409 while the project is parked and the
first answer waits in its queue); a trigger stays listed while the project
is active and fires again and again, each fire a new run.

**Which kinds appear.** A kind reaches the listing when its listener
handler renders a consumer payload. Today the form kind is the only one that
does, so any node registering a form signal is listed (`HumanQuery` is one)
and every other kind stays out of the listing, though its [signal token]s
fire through the same [door]. A new kind is language work:
a `Signal` type in `weft-core/src/signal/`, a handler in
`weft-listener/src/kinds/` whose `render` builds the payload (and keeps
weft's internal file references out of it), `Signal::stored_file` when the
payload shows files (the files [door] asks the kind, so it needs no
knowledge of any), and the node that registers it setting `consumerKind`,
the free label a [consumer] filters on. Once it renders, every [consumer]
that knows its `kind` shows and fires it; nothing else changes.

## The form kind

A form's payload is `formSchema.fields`, each field:

```json
{
  "fieldType": "approve_reject",
  "key": "ok",
  "label": "Approve?",
  "render": { "component": "buttons", "source": "static", "multiple": false, "prefilled": false },
  "value": null,             // the prefill, when the kind has one
  "config": { "approveLabel": "Yes", "rejectLabel": "No", "options": ["a", "b"] }
}
```

`render.component` is what you draw; `key` is what you answer under.

| Kind | Draw | Prefill in `value` | Answer under `key` |
|---|---|---|---|
| `display` | `readonly`: show the value, no input | the wired value (a string, or any JSON to pretty-print) | nothing |
| `display_image` | `image` | a file (below) | nothing |
| `approve_reject` | `buttons`, labels from `config.approveLabel` / `rejectLabel` | none | `true` or `false` (`"approve"` and `"reject"` also work; anything else, null included, fails the form's node) |
| `select` / `multi_select` | `select`, options from `config.options`; `render.multiple` for several | none | one option string, or an array of them |
| `select_input` / `multi_select_input` | `select`, options from the wired `value` (a list of strings) | the option list | as above |
| `text_input` / `textarea` | `text` / `textarea`, `config.placeholder` when set | none | a string |
| `editable_text_input` / `editable_textarea` | the same, `render.prefilled` true | the wired string, shown for editing | the edited string |

The answer body is one JSON object, every answerable field present:
`{ "ok": true, "reason": "looks fine", "tone": ["formal"] }`. A field left
out lands as null on its port, which a typed port refuses: the port closes
with a warning in the journal and whatever reads it skips, so you send every
field. The dispatcher answers 200 on a live project, and also 200 when the
project is parked (the answer is queued and played when it wakes); 404 for
an unknown or already-answered [signal token]; 409 for a second answer
while the first waits in a parked project's queue (you show it as "someone
else answered"); 410 when the project no longer accepts anything (you show
it as "the project is gone"); 429 when a trigger's queue is full while
parked. On any other failure you show the message text.

## A file in a form

A `display_image` field's `value` is one of two plain shapes, never weft's
internal file marker and never a storage key:

- `{ "url": "https://…", "mimeType": "image/png", "sizeBytes": 9, "filename": "cat.png" }`
  for a file that lives at a URL: you use the `url` as it is.
- `{ "mimeType": "image/png", "sizeBytes": 7, "filename": "cat.png" }` for a
  file weft stores: no link. You call the files [door] with the [signal
  token] and the field's `key`; it answers the first shape with a link
  minted at that moment, good for an hour.

You ask the [door] each time you show the form and each time the page
reloads, so a form answered a month after it parked still shows its
picture. The [door] is scoped like the listing: a [signal] the [api token]
lists, a field the form declares, a file that belongs to that [signal]'s
project or run; anything else is 404. A file that expired or was deleted
answers 404 with the store's message ("no longer available…"); you show
that text where the picture would be, never a broken image.

## How the reference extension behaves

`extension-browser/` in the weft checkout is the worked example: a
background poll of the listing every 30 seconds per [api token], a
notification per [signal token] it has not notified before (remembered
across polls, so a flaky poll does not re-notify), a page that draws each
field by `render.component`, fires `POST /signal/{token}`, offers skip, and
asks the files [door] for every stored image as it renders. Failures are
per [api token]: a dispatcher that is down or answers the wrong shape puts
that token in a failed bucket with the reason, and the other tokens'
[signal]s still show. Its `src/lib/api.ts` is the smallest complete client
of these [door]s; you copy its shape.

## Writing one

1. Mint an [api token] scoped to what the [consumer] sees.
2. Poll the listing; read each [entry] by `kind`; draw by
   `render.component`, answer by `key`.
3. Fire with the [signal token]; show 409, 410 and other failures as above.
4. For a stored image, ask the files [door] on every render.
5. For a node's display, read it on every render and draw by `type`.
6. Check `GET /signal-token/health` on connect and after failures.

Attribution

WeaveMindAIWeaveMindAI
View sourceSee grades on GitHubMore from WeaveMindAI →
SSkills DirectorySkills Directory

Ship a skill? Prove it's safe.

Free 120-pattern security scan, letter grade, and an embeddable README badge.

Submit a skill

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments (0)

No comments yet. Be the first to comment!

SSkills DirectorySkills Directory

Ship a skill? Prove it's safe.

Free 120-pattern security scan, letter grade, and an embeddable README badge.

Submit a skill

Related Skills

Clean Code

Pragmatic coding standards - concise, direct, no over-engineering, no unnecessary comments

304955 votes

Browser Extension Developer

Use this skill when developing or maintaining browser extension code in the `browser/` directory, including Chrome/Firefox/Edge compatibility, content scripts, background scripts, or i18n updates.

286712 votes

Seo Optimizer

SEO optimization with keyword analysis, readability assessment, technical validation, content quality. Use for search rankings, blog posts, content audits, or encountering keyword density, readability scores, meta tags, schema markup errors.

2222 votes

Google Official Seo Guide

Official Google SEO guide covering search optimization, best practices, Search Console, crawling, indexing, and improving website search visibility based on official Google documentation

1862 votes

Writing Plans

Use when you have a spec or requirements for a multi-step task, before touching code

2927051 votes
View all in development →