Studio handlers
@mountaya/studio declares 253 verbs, each with a name, a Zod input and a capability. @mountaya/studio/handlers implements them, on an opt-in subpath so a read-only surface never pulls a write path in.
Register every handler
createStudioToolBus() builds the studio's bus in one call, with every default handler registered. Every dependency a handler needs arrives on the tool context at dispatch time.
That bus answers every published verb. A dispatch validates its input, checks the capability, raises any confirmation the verb declares, and returns a structured result. A refusal is a result, not a throw.
It is typed by StudioToolInputs, what each verb takes, and StudioToolOutputs, what each answers, so a dispatch by name returns that verb's own data: read a member of it after ok, with no cast. The studio and mountaya namespaces are reserved on it, so unregister never takes a studio verb off.
Find the handler for a job
A tool name opens with the noun it acts on.
Publish and withdraw a zone
studio.zone.publish freezes a zone's latest saved version into the copy that collection embeds and collection share links serve, and studio.zone.unpublish withdraws that copy. Both take { zoneId } and are the zone's twins of studio.itinerary.publish and studio.itinerary.unpublish. Both are hidden from the co-pilot, since what the public sees is the reader's decision.
Publishing refuses before anything is sent: a zone the session knows is personal with forbidden, a viewer not granted publish with upgrade-required, a zone a collaborator holds with collab-locked, and an edit of anything else, or a reshape of the zone changed and not saved, with edit-in-progress. The reader's own reshape, armed and saved, is no refusal. A first publish past the agreement's published-zones limit is the API's to refuse, and arrives as limit-reached.
Withdrawing checks no lock, no open edit and no entitlement: it freezes nothing, and an organization takes down what it published whatever its agreement holds now. It asks first, behind an acknowledgement, because every collection embed and share link showing the zone goes blank at once.
A zone is public or private apart from its publication. studio.zone.create and the create branch of studio.zone.editCommit take isPrivate, true when left out, so a create lands private. studio.zone.update takes it to change a saved zone's visibility, which takes effect at once, published or not.
Replace or wrap one handler
The handlers are a plain object keyed by tool name, so replacing one is a spread. The other 252 stand. Hand the map to createStudioToolBus as handlers:
A bus you build yourself with new ToolBus takes the same map through registerStudioTools:
Wrapping keeps the default: log the dispatch, refuse it on a condition of your own, then delegate. Use overrideStudioHandler. A default is a bare function or a definition with policy beside one. A wrapper written against the first shape drops the second's requiresConfirmation.
Classify the errors they raise
A handler that cannot recognise a failure re-throws it, and the bus classifies it with its recognizers. createStudioToolBus holds STUDIO_HANDLER_RECOGNIZERS, the subset a handler applies on its own, and asks yours after them, so yours never shadow one of the studio's.
That set omits one arm: telling a 402 that needs a card from one the agreement does not cover. Add your own where those route differently.
What else the subpath publishes
acquireCollabLockandreleaseCollabEntity: a collaborative edit's soft lock.createVideoDirectorBus: a camera-scoped bus advertising onlystudio.video.*.
The handlers write through a client you supply and a cache you bind, and choose neither. Every dependency has a defined absence. A studio mounted without one still runs, and refuses the verbs that needed it.