Publishing + iterating
You have an instrument, a sample, a knowledge bundle, a full project. You want to share it. This section covers the publish flow, version bumps, forks, the My Items view, and the safety rails for deletion + account housekeeping.
The canonical publish path
The UI path: Hub tab → My Items → Upload. Pick a kind, point at the source file, fill in slug + title + license + visibility + description + tags, hit Upload. ONE door, deliberately — the IDE used to have "Publish" buttons in module footers (Builder, editors), they got pulled because three doors invited inconsistency. One form, one path.
The MCP twin:
hub.publish {
file_path: "asset:assets/my-instrument.builder",
kind: "builder",
slug: "my-saturator-lead",
title: "Saturator Lead",
license: "CC0-1.0",
visibility: "public" | "unlisted" | "private" | "friends" | "team",
description?: "...",
tags?: ["lead", "saturator", "synth"],
changelog?: "v1 — initial release",
}
Reads the file, validates the content for the requested kind, creates the item metadata row, uploads the content as v1. Returns the new ItemDto.
A publish chain end-to-end
A Builder → publish workflow, all MCP:
actions:
- action_id: documents.add
input: { type: builder, title: "My instrument" }
save_as: b
label: "▶ 1. Spawn a Builder"
- action_id: builder.graph.node.add
input: { module_id: "{{b.module_id}}", kind: osc }
label: "▶ 2. Add an oscillator"
- action_id: builder.graph.node.add
input: { module_id: "{{b.module_id}}", kind: output }
label: "▶ 3. Add an output"
- action_id: documents.save
input: { module_id: "{{b.module_id}}" }
save_as: saved
label: "▶ 4. Save the DSP (returns the .builder ref)"
- action_id: hub.publish
input:
file_path: "{{saved.filePath}}"
kind: builder
slug: my-osc-test
title: "My osc test"
license: CC0-1.0
visibility: unlisted
description: "Tiny osc → output test DSP"
tags: [test, osc]
label: "▶ 5. Publish as v1, unlisted"
Slug + title + license + visibility — the required fields
| Field | Rules |
|---|---|
| slug | [a-z0-9-]+, max 64 chars, unique per owner across kinds. Immutable after publish — you'd have to fork into a new slug to change. |
| title | Display name. Mutable via hub.item.update. |
| license | SPDX identifier. Mutable via hub.item.update (with care — retroactively changing your license is awkward for installers). |
| visibility | public / unlisted / private / friends / team. Mutable. Start unlisted if you want a soft launch. |
Optional but recommended: description (what it does, who it's for), tags (a handful, surfacing in search), changelog (what's in this version).
Uploading a new version
You shipped v1, improved it, want to push v2:
hub.version.upload {
item_id: "<id from hub.items.my>",
file_path: "asset:assets/my-DSP-v2.builder",
kind: "builder",
changelog: "v2 — replaced moog_vcf with a steeper SVF"
}
kind is optional — omit it and the action does a server roundtrip to resolve the kind from the item record. Pass it to save the roundtrip.
Server assigns the version number (v(n+1)). Users on "latest" automatically get v2 on next install. They can browse to v1 if they want.
The user manual you're reading uses this loop — every chapter rebuild is one hub.version.upload per affected pack.
Updating metadata without uploading
No new content, just tweaks?
hub.item.update {
item_id: "<id from hub.items.my>",
title: "Saturator Lead (refined)",
description: "Updated description with the new param list",
tags: [lead, saturator, synth, fm],
visibility: public
}
Slug and kind are immutable; everything else is mutable. Sends only the fields you pass — omitting tags leaves them alone (it doesn't blank them).
Forking somebody else's work
Found a great DSP you want to evolve as your own variant?
hub.fork { item_id: "<source id from hub.search>" } → fresh ItemDto in YOUR namespace
Returns a fresh ItemDto in YOUR namespace. v1 of the fork = the source's latest version, server-side copied. You own the fork; the parent stays untouched. Same-kind only — you can't fork a builder into a dsp.
Forks are how community remixes happen — somebody publishes a great FM bass DSP, you fork it + add an envelope + upload as YOUR fork v2. Parent + fork co-exist.
My Items — your dashboard
The Hub tab's My Items view shows every item you've published — public, unlisted, private — as one list. Filter by kind. Edit metadata in-place. Upload new versions. Delete items.
actions:
- action_id: hub.items.my
input: { kind: book }
save_as: my_kbs
label: "▶ List my books"
Deleting items
Destructive operation — confirm before firing:
hub.item.delete { item_id: "<id from hub.items.my>" } → permanent; bytes go too
Gone is gone. Versions, artifacts, install URLs — all invalidated. People who installed older versions keep their local copies; new install attempts after delete fail.
Account housekeeping
A single safety-rail action covers the heaviest user op:
hub.account.delete— PERMANENT, IRREVERSIBLE: deletes your account, all published items, your bearer token. Signs you out and returns you to the signed-out shell.
The UI path has a type-to-confirm modal; the MCP path doesn't. The AI assistant is the safety net — it refuses to call hub.account.delete unless your most recent message is unambiguous (e.g. "delete my account"). Casual phrasing like "clean up my Hub" gets a confirm-first response, not an immediate call.
How the FaustWave manual is itself published
Meta moment — the manual you're reading right now lives on the Hub as 10 book items (one per domain). Loop:
- Author / edit a chapter as a markdown file in the markdown-editor module.
- Save to
docs/books/<slug>/<NN>-chapter.md. - Run
node docs/books/build-books.mjs— builds one_build/<slug>.mdper pack. hub.version.upload { item_id: <pack id>, kind: "book", file_path: <build artifact>, changelog }.hub.install { item_id }to re-index locally so the Reader picks up the new version.
Author + reader are the same FaustWave instance. The manual evolves at the speed of the IDE.
Common moves
- "Publish a draft I'll share with one friend" — visibility
unlisted, send them the item id. They install withhub.install. - "Bulk-update tags on every book I own" —
hub.items.my { kind: "book" }then iteratehub.item.updateper id with new tags. - "Clone an item but with my own changes" —
hub.fork, thenhub.version.uploadto push your improved version on top of the source's v1. - "Roll back a broken version" — no rollback action; instead upload the previous content as v(n+1) with a "reverting to vX content" changelog.
Where to go from here
- FaustWave — Builder for the DSP side of
hub.publish kind: "builder". - FaustWave — Recorder § 2 for the round-trip into sample-packs.
- FaustWave — AI Assistant for asking the assistant to publish on your behalf.