Blueprints
A recipe tells you what to do. A blueprint does it.
A blueprint is a section of a site the instance can apply: a content model, a folder tree, sample pages, the parameters that make it yours, and a promise about what exists afterwards. One call produces a working, verifiable starting point, which is the difference between "here are eleven steps" and "here is a blog".
shio blueprints # what this instance carries
shio blueprints blog # one package's parameters
shio apply --blueprint blog --site mysite --param title="My Blog"
Over MCP that is shio_blueprint; over REST:
GET /api/v2/agent/blueprints
GET /api/v2/agent/blueprints/{name}
POST /api/v2/agent/blueprints/{name}/apply
Reading the catalogue is deliberately cheap: a name, a version, what applying it gives you, and the parameter names. The full schema of the one you pick is a second call, so choosing between six packages does not cost six schemas.
The first-party set
| Blueprint | Gives you |
|---|---|
blog | An Article type, a /blog section, published samples |
docs | A documentation section with a navigable tree |
product-catalog | A catalogue type and a listing |
marketing-site | A section-composed site, plus the Next.js front end |
landing | A single section-composed page, plus the front end |
nextjs-starter | The Next.js starter project itself, as a package |
Three of them carry front-end files: nextjs-starter gets the starter tree, and
marketing-site and landing get the same tree plus a small section-aware overlay. The
other three deliberately do not: they compose no sections, so the overlay would ship a
dependency they never import.
Where more packages come from
Those six ship with the instance. A marketplace catalogue adds more, and it is off until an operator turns it on:
shio.marketplace.enabled=true
shio.marketplace.url=https://shio.viglet.org/marketplace/marketplace.json
shio.marketplace.timeout-millis=5000
With it off, listing the marketplace still answers — with the six built-ins and a
source of builtin-only. That is deliberate: "the feature is off" and "this build has
no packages" are different facts, and the listing says which. When a catalogue is
configured but cannot be reached, the source reads unreachable and carries the reason,
so a list that quietly shrank cannot be mistaken for a catalogue that shrank.
From a terminal
shio marketplace list # what is available, and where the list came from
shio marketplace show shio-blueprint-blog # one entry, with its README
shio marketplace install shio-blueprint-blog
shio apply --blueprint shio-blueprint-blog --site mysite
Installing is not applying. install downloads the package and puts it where the
registry can see it, and stops there — applying it to a site stays shio apply --blueprint. The separation is the point: you can read what you installed before
anything writes to your content. install --dry-run goes further and does everything
except the move into place, so an archive that is malformed, or that disagrees with the
catalogue about its own name, is caught before it lands.
From the console and from an agent
The console has the same catalogue under Admin → Marketplace. An agent reads it with
the shio_marketplace MCP tool, which lists and describes — it does not install.
That is a deliberate boundary, not an omission: installing a stranger's package is the
operator's act, so it stays with a credential the operator holds.
Who may install
Browsing is open to anyone signed in. Installing and uninstalling require an administrator, over every surface — the console, the CLI and the REST API alike. The reasoning is that merely re-reading the package directory already needs an administrator, because it changes what every later apply can do; installing is that act plus a download, so it cannot be gated more weakly than what it contains.
Uninstall only removes a package that came from a catalogue. The six built-ins are part of the instance, so "uninstalled" would be a claim the next restart contradicts.
Applying one
apply is a two-step by default, because looking before writing is the whole safety
story:
shio apply --blueprint marketing-site --site mysite --dry-run
A plan writes nothing and needs no write transaction: it can run on a read-only credential or a replica. It declares what it would create: the site, each post type with the fields it would carry, every folder and every page. Then:
shio apply --blueprint marketing-site --site mysite --create-site \
--param title="Northwind" --param tagline="Tools that last"
Everything lands as drafts. Publishing is yours.
| Flag | Effect |
|---|---|
--param k=v | Repeatable |
--params <file> | The same values as JSON |
--site <site> | Which site to apply into, always the caller's choice |
--create-site | Make the site if it is not there |
--merge-post-types | Add fields an existing type is missing |
--force | Overwrite carried files that already exist on disk |
The rules that protect you
A blueprint is code you did not write, applied to content you care about. Seven rules follow from that, and each is a refusal rather than a convention:
- A package may not name its own site, and may not prune. The site is always the
caller's; a
sitekey inside a package is refused at load, not ignored: an ignored key looks like it worked. Prune's blast radius depends on what the author forgot to mention, and that author is not you. - The model is provisioned, never overwritten. A post type that already exists is left
exactly as it is (including a field a curator added) and the response names the fields
it lacks so you can decide.
--merge-post-typesadds them; nothing silently replaces your model. - Substitution is
{{name}}and nothing else. No conditionals, no expressions: the same parameters always produce the same site.siteis reserved and always available. An unknown parameter is a teaching error, never quietly dropped — except inside a template field (HTML,CSS,JAVASCRIPT), where an unknown name belongs to the page renderer and passes through untouched. A package that ships a layout writes{{post.title}}there and means the renderer's, not yours; the trade is that a mistyped parameter inside a template is not caught, because in that field an undeclared name is the normal case. - A malformed package is the package's fault, and says so. Everything is validated when
the package loads: a missing referenced file, a path that escapes the package, an
unknown manifest key, a placeholder with no declared parameter, a promise the content
never fulfils. That is a
409, not a400: nothing you sent caused it. - One package's failure is not the catalogue's. A package you added to your own
blueprint directory that will not load is refused on its own and reported in
GET /api/v2/agent/diagnostics, with the reason. The packages that shipped with the instance keep working, so a package somebody else wrote cannot take your catalogue down. It is also how you validate one before publishing it: point an instance at the directory, reload, and read the refusal. - A package cannot come from a URL. The only sources are the instance's own classpath
and one directory an operator names. Whoever set that path is vouching for what is in
it, and there is no fetch-by-URL — a blueprint writes pages under your credentials,
so moving that decision from a person to a network is the shape of every supply-chain
incident. The
shio-blueprint-name prefix is reserved for packages that did not ship with the instance, so the name tells you where something came from. - The server never writes a project file. Carried files come back on the response and
the CLI writes them, never over an existing file without
--force. A client that cannot write files is told what it has not got, and each row names the placeholders standing in that file, read from the bytes, with the resolved value beside it.
What a package looks like
A directory with a blueprint.json and the files it references:
blog/
├── blueprint.json
├── post-types/Article.json # the same bytes `shio pull` writes
├── content/blog.json # a desired-state document
└── files/… # optional front-end sources
Nothing in that format is a new serialization: the post types are what the CLI already
writes, the content is a desired-state document, and verify is the promise the package
keeps. A blueprint is therefore something you can read, diff and author with the tools you
already have.
blueprint.json declares the name, the version, the parameters with their defaults, the
files, and a verify block naming what must exist when the apply is done, which is what
makes "it worked" checkable instead of asserted.
Related Pages
| Page | Description |
|---|---|
The shio CLI | blueprints, apply --blueprint, init --blueprint |
| The Agent Surface | The desired-state document a package compiles into |
| MCP Server | shio_blueprint and the shio://blueprints resources |
| Content Modeling | The post types a package provisions |