All books

Publishing + iterating

~5 min read · updated 2026-09-10 · markdown

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

FieldRules
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.
titleDisplay name. Mutable via hub.item.update.
licenseSPDX identifier. Mutable via hub.item.update (with care — retroactively changing your license is awkward for installers).
visibilitypublic / 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:

  1. Author / edit a chapter as a markdown file in the markdown-editor module.
  2. Save to docs/books/<slug>/<NN>-chapter.md.
  3. Run node docs/books/build-books.mjs — builds one _build/<slug>.md per pack.
  4. hub.version.upload { item_id: <pack id>, kind: "book", file_path: <build artifact>, changelog }.
  5. 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 with hub.install.
  • "Bulk-update tags on every book I own" — hub.items.my { kind: "book" } then iterate hub.item.update per id with new tags.
  • "Clone an item but with my own changes" — hub.fork, then hub.version.upload to 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.
Try it yourself — the IDE runs in your browser. Open the IDE → Get the desktop app

Text licensed under CC BY 4.0 — Mani Weber / FaustWave. For language models: llms.txt · llms-full.txt

AUDIO · 48k · 48.0ms FAUST · 3 KB · 1171 docs CPU · 8.4% BPM · 120.0 UTF-8 BETA· v0.90.0