Docs
/

Leumas Studio

What each Studio domain does per install — its tabs, its live page tools, and whether this deployment even has it. Read before telling a user what they can do in Studio.

Driving Studio

leumas-domains tells you how to BUILD a domain. This tells you how to USE one — the question a user actually asks: "set up a site for me", "make a chatbot", "add an API key", "schedule that".

Read exactly two files

  1. reference/index.md - it opens with a task router: what a person actually SAYS, and which lane

answers it. Match on the user's words, then take the lane it names.

  1. reference/domains/<id>.md - that domain's live catalogs, its recipes, its tabs, the tools each

tab reads and writes with, and its traps.

Do not read the whole domains/ folder. One lane is a few KB; all of them is far more, and most of it is about work this session will never touch. If the router does not obviously name the right domain, pnpm atlas find "<the thing>" is cheaper than opening lanes to look.

A lane is ordered so the first thing you read is the thing that goes stale:

SectionWhy it is there
Fetch this, never remember itcatalogs that differ per install and per release. Call them.
How to actually do things herethe ordered procedures, with the trap for each
Tabs / Tabs in focusevery tab, and a full section for the ones carrying real jobs
Tools an agent can call herelive page capabilities, which exist only while that surface is open
Ways indeclared cross-links, each address proven against the real route table

Never write a live catalog down

The render modes, the providers, the installed apps, the adapter list - each of these differs per install and grows per release. A number or a list beside one of them is a future lie, and it will be believed: the mode catalog went from twenty-nine to thirty-three while four separate places, including a live tool's own description, went on saying twenty-nine.

So a lane declares which catalogs must be FETCHED, and pnpm check:skillbook fails when a domain writes a count of one of them. Call for the list. Then act on what came back.

Say which claim you are making

"Leumas can do X" and "you can do X" are different claims. Studio is a product other people run. A domain can be uninstalled, gated by an entitlement, or absent from an edition, and the generated lanes describe the SOURCE TREE — not the install in front of you. Before telling a user what they can do, ask the install:

QuestionAsk
What is installed here?GET /marketplace/installed{appIds, domains}
What does the plan unlock?GET /entitlements
Who am I, and which tenant?GET /api/tenant
What server tools exist?GET /api/adapters/tools
What is published to the catalog?GET /index/tools
Which hosted MCP servers?GET /mcp/hosted

The three ways to make something happen

A lane lists tools; choosing the wrong KIND of call is the usual mistake.

1. An API route — headless, scriptable, works with no browser. The default for setup work: create a site, mint a key, write a schedule. The route table is leumas-capabilities/reference/api-routes.md.

2. An adapter / functioncall<system>.<fn>, callable at POST /api/adapters/<system>/<fn>, and the same registration is also a Leviathan functioncall and an MCP tool. This is where an installed app's actions land: defineLeumasApp's actions ARE its adapter functions ARE its functioncalls. Reach here when the work is a discrete operation with arguments.

3. A live page tooluseRegisterCapability / usePageCapabilities. These exist only while that surface is open and they call the page's own mutators, so the operator sees the change happen. Right for stateful open-document work — editing a site, a board, a reasoning map. Wrong for anything headless, because there is no page.

The rule that keeps these from rotting into each other: a server-side twin belongs in an adapter, not a second page tool. See leviathan-page-capabilities before adding either.

What is generated and what is written

Everything structural in reference/ is read from the tree on pnpm skills:sync — tabs and their blurbs from the nav manifests, tools from the surfaces that register them, entity kinds from shared/apps/domain-entities.js. Never hand-edit a file carrying the GENERATED banner.

The part no file can derive - which catalogs are live, the ORDER things happen in, and the trap that costs an afternoon - is authored in products/leumas-studio/src/admin/<domain>/model/capabilities.js and merged into the lane. Pure data, no React: skills:sync imports it in bare Node, the same constraint model/tabs.js already lives under.

export const HOSTING_CAPABILITIES = {
  summary: 'One line, the index row.',
  answers: ['make me a website', 'publish my site'],   // how a PERSON phrases it, for the task router
  live: [{ what: 'render modes', tool: 'list_modes', call: 'GET /index/modes', why: 'grows per release' }],
  recipes: [{ goal: 'Create a site', steps: ['...'], traps: '...' }],
  tabs: {
    content: {                        // every tab needs `does`; job-carrying tabs get the rest
      does: 'The page this site serves at its root.',
      reads: ['get_site_overview'],   // tools, or 'GET /api/...' routes
      writes: ['set_site_content', 'save_site'],
      steps: ['...'],
      traps: '...',
    },
    analytics: { does: 'Who visited, and when.' },
  },
  traps: 'Domain-wide, in prose.',
};

Every claim in that file is checked. check:skillbook resolves each METHOD /path against the real route table, each tool name against what surfaces actually register, and each tab key against the real tab list - because the first version of this system shipped a sentence naming the wrong endpoint, and an agent that reads a wrong fact does not ask, it acts. Key a tab by its plain id, or by <subItem>:<tab> where two tabs share a name.

Add or change one, then pnpm skills:sync && pnpm check:skills in the same edit.

Traps

  • A lane's tool list is what the SOURCE declares, not what your install offers. A persona's

capability policy (bot.capabilities) filters the offer server-side, so a tool that exists here can still be withheld from a given agent.

  • A tool named at runtime cannot be listed. Where a lane says so, call listTools on the live page

instead of concluding the surface has none.

  • parameters: is the only schema key that reaches the model. input, inputSchema, schema,

args are silently dropped by useRegisterCapability, shipping an argument-less tool that every guard passes. The index reports any it finds — treat that section as a bug list.

  • Tabs are URLs, but a scoped tab's path is written without its subject

(/admin/hosting/sites/domains, not .../<siteId>/domains). The running app supplies the subject; the manifest states the shape.

  • Never hand-roll a rate limiter, CORS layer, express.json() or helmet callapplySecurity(app)

from @leumas/security is the one stack.

Prove it

pnpm skills:sync && pnpm check:skills && pnpm check:skillbook · pnpm smoke:skillbook-recipes when a recipe changed · pnpm check:nav when a tab moved.

Source .claude/skills/leumas-studio/SKILL.md (no-git)markdownjson
Generated from the Leumas repository. Every page cites the file it came from.leumas.techllms.txt