# 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.
2. **`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:

| Section | Why it is there |
|---|---|
| **Fetch this, never remember it** | catalogs that differ per install and per release. Call them. |
| How to actually do things here | the ordered procedures, with the trap for each |
| Tabs / **Tabs in focus** | every tab, and a full section for the ones carrying real jobs |
| Tools an agent can call here | live page capabilities, which exist only while that surface is open |
| Ways in | declared 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:

| Question | Ask |
|---|---|
| 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 tool** — `useRegisterCapability` / `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.

```js
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 call** — `applySecurity(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
Canonical: https://docs.leumas.tech/p/skills/leumas-studio
