{
  "schema": "leumas.docs.page/1",
  "id": "skill:leumas-studio",
  "slug": "skills/leumas-studio",
  "kind": "tools",
  "bucket": "skill",
  "title": "leumas-studio",
  "name": "Leumas Studio",
  "eyebrow": null,
  "chip": null,
  "summary": "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.",
  "keywords": [
    "leumas-studio",
    "telling",
    "say claim making",
    "user",
    "install",
    "tabs",
    "generated written",
    "page"
  ],
  "audience": "both",
  "funnel": {
    "product": null,
    "cta": null
  },
  "body": "\n# Driving Studio\n\n`leumas-domains` tells you how to BUILD a domain. This tells you how to USE one — the question a user\nactually asks: *\"set up a site for me\"*, *\"make a chatbot\"*, *\"add an API key\"*, *\"schedule that\"*.\n\n## Read exactly two files\n\n1. **`reference/index.md`** - it opens with a task router: what a person actually SAYS, and which lane\n   answers it. Match on the user's words, then take the lane it names.\n2. **`reference/domains/<id>.md`** - that domain's live catalogs, its recipes, its tabs, the tools each\n   tab reads and writes with, and its traps.\n\nDo not read the whole `domains/` folder. One lane is a few KB; all of them is far more, and most of it\nis about work this session will never touch. If the router does not obviously name the right domain,\n`pnpm atlas find \"<the thing>\"` is cheaper than opening lanes to look.\n\nA lane is ordered so the first thing you read is the thing that goes stale:\n\n| Section | Why it is there |\n|---|---|\n| **Fetch this, never remember it** | catalogs that differ per install and per release. Call them. |\n| How to actually do things here | the ordered procedures, with the trap for each |\n| Tabs / **Tabs in focus** | every tab, and a full section for the ones carrying real jobs |\n| Tools an agent can call here | live page capabilities, which exist only while that surface is open |\n| Ways in | declared cross-links, each address proven against the real route table |\n\n## Never write a live catalog down\n\nThe render modes, the providers, the installed apps, the adapter list - each of these differs per\ninstall and grows per release. A number or a list beside one of them is a future lie, and it will be\nbelieved: the mode catalog went from twenty-nine to thirty-three while four separate places, including\na live tool's own description, went on saying twenty-nine.\n\nSo a lane declares which catalogs must be FETCHED, and `pnpm check:skillbook` fails when a domain\nwrites a count of one of them. Call for the list. Then act on what came back.\n\n## Say which claim you are making\n\n**\"Leumas can do X\" and \"you can do X\" are different claims.** Studio is a product other people run.\nA domain can be uninstalled, gated by an entitlement, or absent from an edition, and the generated\nlanes describe the SOURCE TREE — not the install in front of you. Before telling a user what they can\ndo, ask the install:\n\n| Question | Ask |\n|---|---|\n| What is installed here? | `GET /marketplace/installed` → `{appIds, domains}` |\n| What does the plan unlock? | `GET /entitlements` |\n| Who am I, and which tenant? | `GET /api/tenant` |\n| What server tools exist? | `GET /api/adapters/tools` |\n| What is published to the catalog? | `GET /index/tools` |\n| Which hosted MCP servers? | `GET /mcp/hosted` |\n\n## The three ways to make something happen\n\nA lane lists tools; choosing the wrong KIND of call is the usual mistake.\n\n**1. An API route** — headless, scriptable, works with no browser. The default for setup work: create\na site, mint a key, write a schedule. The route table is\n`leumas-capabilities/reference/api-routes.md`.\n\n**2. An adapter / functioncall** — `<system>.<fn>`, callable at `POST /api/adapters/<system>/<fn>`,\nand the same registration is also a Leviathan functioncall and an MCP tool. This is where an installed\napp's actions land: `defineLeumasApp`'s `actions` ARE its adapter functions ARE its functioncalls.\nReach here when the work is a discrete operation with arguments.\n\n**3. A live page tool** — `useRegisterCapability` / `usePageCapabilities`. These exist **only while\nthat surface is open** and they call the page's own mutators, so the operator sees the change happen.\nRight for stateful open-document work — editing a site, a board, a reasoning map. Wrong for anything\nheadless, because there is no page.\n\nThe rule that keeps these from rotting into each other: **a server-side twin belongs in an adapter,\nnot a second page tool.** See `leviathan-page-capabilities` before adding either.\n\n## What is generated and what is written\n\nEverything structural in `reference/` is read from the tree on `pnpm skills:sync` — tabs and their\nblurbs from the nav manifests, tools from the surfaces that register them, entity kinds from\n`shared/apps/domain-entities.js`. Never hand-edit a file carrying the GENERATED banner.\n\nThe part no file can derive - which catalogs are live, the ORDER things happen in, and the trap that\ncosts an afternoon - is authored in\n`products/leumas-studio/src/admin/<domain>/model/capabilities.js` and merged into the lane. Pure data,\nno React: `skills:sync` imports it in bare Node, the same constraint `model/tabs.js` already lives\nunder.\n\n```js\nexport const HOSTING_CAPABILITIES = {\n  summary: 'One line, the index row.',\n  answers: ['make me a website', 'publish my site'],   // how a PERSON phrases it, for the task router\n  live: [{ what: 'render modes', tool: 'list_modes', call: 'GET /index/modes', why: 'grows per release' }],\n  recipes: [{ goal: 'Create a site', steps: ['...'], traps: '...' }],\n  tabs: {\n    content: {                        // every tab needs `does`; job-carrying tabs get the rest\n      does: 'The page this site serves at its root.',\n      reads: ['get_site_overview'],   // tools, or 'GET /api/...' routes\n      writes: ['set_site_content', 'save_site'],\n      steps: ['...'],\n      traps: '...',\n    },\n    analytics: { does: 'Who visited, and when.' },\n  },\n  traps: 'Domain-wide, in prose.',\n};\n```\n\n**Every claim in that file is checked.** `check:skillbook` resolves each `METHOD /path` against the\nreal route table, each tool name against what surfaces actually register, and each tab key against the\nreal tab list - because the first version of this system shipped a sentence naming the wrong endpoint,\nand an agent that reads a wrong fact does not ask, it acts. Key a tab by its plain id, or by\n`<subItem>:<tab>` where two tabs share a name.\n\nAdd or change one, then `pnpm skills:sync && pnpm check:skills` **in the same edit**.\n\n## Traps\n\n- **A lane's tool list is what the SOURCE declares, not what your install offers.** A persona's\n  capability policy (`bot.capabilities`) filters the offer server-side, so a tool that exists here can\n  still be withheld from a given agent.\n- **A tool named at runtime cannot be listed.** Where a lane says so, call `listTools` on the live page\n  instead of concluding the surface has none.\n- **`parameters:` is the only schema key that reaches the model.** `input`, `inputSchema`, `schema`,\n  `args` are silently dropped by `useRegisterCapability`, shipping an argument-less tool that every\n  guard passes. The index reports any it finds — treat that section as a bug list.\n- **Tabs are URLs, but a scoped tab's path is written without its subject**\n  (`/admin/hosting/sites/domains`, not `.../<siteId>/domains`). The running app supplies the subject;\n  the manifest states the shape.\n- **Never hand-roll a rate limiter, CORS layer, `express.json()` or helmet call** — `applySecurity(app)`\n  from `@leumas/security` is the one stack.\n\n## Prove it\n\n`pnpm skills:sync && pnpm check:skills && pnpm check:skillbook` · `pnpm smoke:skillbook-recipes`\nwhen a recipe changed · `pnpm check:nav` when a tab moved.\n",
  "source": {
    "path": ".claude/skills/leumas-studio/SKILL.md",
    "blobSha": "",
    "commit": "",
    "committedAt": "",
    "provenance": "no-git",
    "bytes": 7412,
    "hash": "752681b01d71bce3d7925b3b4fba930d65532fed"
  },
  "urls": {
    "html": "/p/skills/leumas-studio",
    "json": "/docs/skills/leumas-studio.json",
    "md": "/docs/skills/leumas-studio.md"
  },
  "links": {
    "composes": [],
    "usedBy": [],
    "product": [],
    "howTo": [],
    "skills": []
  }
}
