Basic Cohub abilities. Most agents already have these mounted; listed here for reference.
Platform Published by the Neta team.
Official Made by the Neta team.
Verified Reviewed by the Neta team.
Community Shared by a Cohub user. Not reviewed — read SKILL.md before you use it.
interaction-components
Platformneta/interaction-components
Use it in Cohub
Paste this into your agent's chat — it fetches the skill and follows it.
Use the skill: interaction-components. Run `npx -y @neta-art/neta-search-cli@latest skills use neta/interaction-components --via site` and follow what it prints. Read its complete output, redirecting it to a temporary file first if necessary, and resolve relative paths from the skill directory it prints.
Install from a terminal
npx -y @neta-art/neta-search-cli@latest skills use neta/interaction-components
Summary
Tappable cards in the chat that open your materials, works, files, plans or Discord, or bring in a ready-made world.
When a reply hands you something on its own, you get a tappable card: a character, a finished work, a file or an outside web page.
If the world you name already exists, such as Harry Potter, you get a card to bring it in and reshape as your own take.
If a run is blocked for credits, a button opens the subscription plans screen. Ask where the official Discord is and a button takes you there.
Cards show the real name and cover of your material or work, so you can recognise it before you tap.
Names mentioned inside a sentence stay plain text, and nothing is linked unless it truly exists.
SKILL.md
# Entry points the creator can tap
The system prompt says when to hand something over; this file says what that entry point looks
like. A Link block takes a line of its own — it cannot sit inside a sentence. So it is for
something you are handing over on its own, not for a name mentioned mid-prose: "the outer bar is
now a place in this world" stays exactly that, a name inside a sentence. Where a Link block is
right, it replaces whatever Markdown link stood there — never write both.
REQUIRED: read `references/protocol.md` before you emit a block. It holds the envelope, the
limits and the fallback rules that every kind shares.
## Which form
| Situation | Form |
| --- | --- |
| `template search` found a world that genuinely is the IP the creator named | Recommendation |
| They asked to see plans, or a run is blocked for credits and they need that screen | Button |
| They asked where the official Discord community is | Button |
| The target is not certain | leave it in prose |
## Recommendation
Emit this card only when `template search` (run by create-world's Step 1) turns up a world that
genuinely already is the IP the creator named — the same bar create-world uses elsewhere: a world
under a different name or a different adaptation is a different world, not this one, and gets no
card. Offer it just the same when they want their own fan take: bringing it in is a starting
point, not a demand to stay canon.
```text
<interaction-json>
{
"version": 1,
"fallback": "Harry Potter — a world another creator has already built out in full.",
"component": {
"kind": "recommendation",
"target": { "type": "world", "worldId": "world_01KWGG3ATZWGT4XR375PJ202Y8" },
"presentation": {
"title": "Harry Potter",
"subtitle": "Wizarding-school fantasy"
}
}
}
</interaction-json>
```
Rules:
- `target.worldId` is that search result's own `id`, copied character for character. It is an
opaque key, not a readable name: adding to it, shortening it or tidying it up leaves the card
pointing at nothing.
- `presentation.title` is the result's `worldName`, unchanged.
- `presentation.subtitle` is the card's one-line label, and the card gives it about three words
before it clips — so write those three words yourself, in the creator's language: the world's
genre and flavour as you just read it ("Wizarding-school fantasy", "仙侠 · 宗门恩怨"). Never
paste a sentence from the result into it; a copied `description` or `prologue` overruns the
card and drops the whole block to its fallback.
- Say your own line first, then the block **on its own line**. That line carries three things:
another creator has already
built this world; bringing it in is a starting point they can reshape from, not a commitment
to stay canon; and building a fresh one instead is equally available. Write it in the
creator's language, freshly each time — never "I found a matching template," and never the
same sentence you used the last time you offered one.
- Ask, then stop: emit the block and end the turn there — do not also start Step 2 in the same
turn.
- Offer it once; if they decline or move on, build the world yourself from the canon you hold
and don't raise the match again.
### Running it — tapped or told
A tap sends a follow-up turn carrying a `<neta-interaction-action>` directive (inside a
`<system-reminder>`, per the Harness section of the system prompt) shaped like:
```json
{"command": {"name": "recommendation.use", "target": {"type": "world", "worldId": "world_01ABC…"}}}
```
A clear yes in plain words — "use it", "that one" — counts exactly the same; you already hold
that world's id from your own search result, so you don't need the directive to know which world.
`--source` takes that same `worldId`, unchanged. Then, in that same turn, run in order:
```bash
node /mods/neta/neta narrating seed --source '<that worldId>'
node /mods/neta/neta narrating save
```
Do not also stream a `<narrating-json>` block for this turn — the seed already carries the full
world, including its own visual style. Speak about the reveal the same way you would any other
world's first showing — never mention "seed," a file path, or the search API.
## Link
Link the thing itself, not the file it happens to live in: a material is a `material` target, a
Work is a `work` target. A `world-file` is for an ordinary file that is neither — a design doc, a
plan, a storyboard. It shows the filename and the folder it sits in, so reaching for it to link a
material puts `world/characters` on screen and loses that material's own name and cover.
Only link to a target you have confirmed exists. Confirmed means an id or path a tool handed you
this turn, or a file you yourself wrote successfully this turn. A value you put together from a
name, a type or a path pattern is not confirmed, and a link built that way opens to nothing. The
block sits in a code fence here **only so this file can show it**.
```text
<interaction-json>
{
"version": 1,
"fallback": "[Creation guide](https://example.com/creation-guide)",
"component": {
"kind": "link",
"target": {
"type": "external-url",
"url": "https://example.com/creation-guide"
},
"presentation": {
"name": "Creation guide",
"subtitle": "The full walkthrough",
"visual": { "type": "icon", "name": "external" }
}
}
}
</interaction-json>
```
`target` is exactly one of:
```json
{ "type": "world-file", "path": "works/storyboard.json" }
{ "type": "material", "materialId": "character-01" }
{ "type": "work", "workId": "work-01" }
{ "type": "studio-route", "route": "work", "params": { "worldId": "world_01ABC…", "workId": "work-01" } }
{ "type": "external-url", "url": "https://example.com/reference" }
```
Rules:
- `world-file.path` is a real relative path inside this world.
- `materialId` is the real `Atom.id` — no `worldId`, and no `atom:` prefix, which is a canvas
item id, not a material id. A material of another world has no shape here; leave it in prose.
- A Work in the current world is a `work` target: `{ "type": "work", "workId": "<id>" }`.
`workId` is the Work id or the `cohubWorkId` a tool returned (the publish output, `work list`).
This holds even when you also hold a neta.art address for that Work — the `work` target opens it
in place and fills in its own name and cover. Reach for `studio-route` only when you
specifically need the route form; never hand a current-world Work over as an `external-url`.
- `studio-route.params.worldId` is the current world's id — always `$NETA_WORLD_ID`, which looks
like `world_01…`. It is never a `space_id`, even though `@` references in the creator's message
carry one; a space id here leaves the card unresolved and drops it to its fallback.
- `studio-route` params match the shapes above exactly; add nothing.
- An external URL is a complete absolute HTTPS address.
- `neta.art` and its real subdomains open directly. Every other host shows Studio's own
off-site confirmation first; do not try to route around it.
- `presentation` and each of its fields are optional. A `name`, `subtitle`, custom image or
differently-typed icon you supply wins; anything you leave out is filled from the current
Material, Work, Studio route or the `world-file` path.
- A material's or Work's own cover is filled in for you. Supply `visual` only when you have a
genuine custom image, or genuinely need a different type of icon than the one it would pick.
- A `world-file` gets its name from the filename, and its subtitle from the parent directory
unless you give one — so give a `presentation.subtitle` saying what is inside it. Its icon
follows the file type. Never put a `world-file` path in an image URL.
- `name` is at most 120 user-perceived characters, `subtitle` at most 240.
- An external URL has no local entity to draw from: give a `presentation` with the real name.
Leave it out and Studio prints the bare hostname.
- Never fetch or invent an off-site title, thumbnail or OpenGraph data.
- Studio shows no "Link" type label and no trailing icon — do not simulate those with an
emoji, arrow or symbol inside `name`.
- A Link's fallback is what the reply would have said without it: `[filename](/workspace/<path>)`
for a file, an ordinary Markdown link for a safe address, and for a material or Work simply
its name in a sentence — it has no path to link.
- If a Work's fallback does include its public URL, copy that URL byte for byte from this turn's
tool output (e.g. the publish `Deliverable link`). Never retype it, rebuild it from its parts,
or carry it over from an earlier turn — a URL that is off by one path segment opens to a 404.
## Button
Use a Button when a run this turn came back blocked for credits, when the creator asked to see
subscription plans, or when they asked for the official Discord community.
```text
<interaction-json>
{
"version": 1,
"fallback": "You can open billing and pick what fits.",
"component": {
"kind": "button",
"label": "Open billing",
"action": { "type": "billing.open" }
}
}
</interaction-json>
```
```text
<interaction-json>
{
"version": 1,
"fallback": "Join the Neta Discord community.",
"component": {
"kind": "button",
"label": "Join Discord",
"action": { "type": "discord.join" }
}
}
</interaction-json>
```
Rules:
- `label` is at most 80 user-perceived characters.
- The action is exactly one of two cases: a bare `billing.open` — Studio opens plans or credits
from the creator's own subscription state — or `discord.join`. Anything else is not a Button.
- A `discord.join` action carries only `type`. Studio holds the invite address itself and
opens it as a trusted product destination with no off-site confirmation. Never add `url`,
`surface` or any other field. A Discord URL **you** write in prose or in a Link is still an
ordinary non-`*.neta.art` external link and still shows the confirmation.
- Buttons show the label only — no icon.
- A Button cannot send a Session message, open an arbitrary link, call an API, or modify a
World, Material or Work.
- The label never decides behaviour; only the exact action does.
- A Button's fallback says what the creator can do — open plans, or join the community.
## Visual
An icon is exactly this shape, and the name is one of the seven:
```json
{ "type": "icon", "name": "file" }
```
```text
world | material | work | file | image | video | external
```
An image is exactly this shape:
```json
{
"type": "image",
"url": "https://router-files.neta.art/path/to/real-image.png",
"alt": "A readable description"
}
```
- `url` is an absolute HTTPS URL you already know.
- `alt` is required, at most 160 user-perceived characters.
- Use the `url` field directly — no `source`, no `https-url`, no other wrapper.
## Do not use
Use plain Markdown when:
- the target id, file path, URL or image address is unknown and you would have to guess
or use a placeholder;
- the action you want is neither of the two Button cases;
- you would be inventing a link the reply never needed, just to have a card.
An entry point never replaces a permission check, a confirmation, running the tool, or
verifying the result.
## Before you send — a Link, Button or Recommendation
1. Would plain Markdown already do? Does this entry point actually help them continue?
2. Is every id and path one a tool handed you this turn, or a file you wrote this turn?
3. Does the target match what the thing actually is — material, Work, ordinary file, or a
genuinely-matching world?
4. The three protocol checks in `references/protocol.md`.