Files
shiro-neko/docs/registry.md
T

129 lines
4.5 KiB
Markdown
Raw Normal View History

# Registry
External skills and plugins, browsed and installed from the CLI.
```
/registry everything in the index
/registry search <query> narrow by name or description
/registry installed what is already here
/registry add <name> fetch, show, then install on confirmation
/registry remove <name> delete an installed entry
```
```
registry
3 of 3 available
S migration Write or run a database migration
S commit-style Write commits the way this team does
P no-secrets Refuses writes to credential files installed
S skill P plugin | /registry add <name>
```
`esc` dismisses the panel.
## The two kinds are not equally safe
**A skill is instructions.** Installing one puts a stranger's words into the system prompt of
every future session in this project. That is prompt injection by invitation, so the install
shows the body first and `/skills` always says the origin:
```
install skill "migration"?
https://raw.githubusercontent.com/example/registry/main/skills/migration.md
Migrations live in `db/migrations/` and are timestamped, never renumbered.
Run `bun run db:migrate` locally first. Staging runs them on deploy.
A skill is instructions the agent follows. This text joins your system prompt.
y install | n cancel
```
**A plugin is data.** Never code. A manifest declares refusal rules; the guard that evaluates
them is the same compiled code for every installed plugin:
```json
{
"name": "no-secrets",
"description": "Refuses writes to credential files",
"appendix": "The no-secrets plugin refuses writes to .env and credential files.",
"deny": [
{
"tools": ["write_file", "edit_file", "multi_edit"],
"pathPattern": "(^|/)\\.env|credentials|\\.pem$",
"reason": "refusing to write a credential file; add secrets yourself"
}
]
}
```
Loading TypeScript from a URL is not offered at any price. A plugin can block tool calls, so
one that could also execute could read every file the agent can read and lie about blocking
anything. See [plugins](plugins.md) for why this boundary exists.
## What is validated before anything is written
| Check | Why |
|---|---|
| `https` only, `localhost` for tests | `file:` would read a local path, `data:` would inline a payload |
| Name matches `^[a-z0-9][a-z0-9-]*$` | the name becomes a filename, so `../evil` must not parse |
| Index at most 256 KB, body at most 64 KB | a hostile index should not exhaust memory |
| Manifest against a strict schema | extra keys like `beforeToolCall` are dropped, not honoured |
| Every pattern compiles as a regex | a broken pattern would fail on the first tool call instead |
| Pattern at most 200 characters | it runs on every tool call; a pathological one is a denial of service |
| Body name matches the index name | an index entry cannot serve something else under a trusted name |
| At least one deny rule | a plugin with no rules is only prompt text, which is what a skill is for |
A malformed installed plugin is reported by `/plugins` and skipped. One bad install does not
stop the agent from starting.
## Where installs land
```
~/.shiro-neko/registry/
skills/<name>.md loaded as origin "registry"
plugins/<name>.json loaded as a declarative plugin
```
Precedence for skills, low to high: **builtin → registry → user → project**. A skill you wrote
in `~/.shiro-neko/skills/` or `.shiro/skills/` always beats one fetched from a registry, so an
install can never silently shadow your own work.
Installs take effect on the next start. A skill joins the system prompt and a plugin joins the
guard chain, and both are assembled once at boot; hot-swapping either mid-session would mean a
turn whose rules changed underneath it.
## Pointing at your own index
```json
{ "registryUrl": "https://example.com/my-registry/index.json" }
```
The index is one JSON document:
```json
{
"skills": [
{
"name": "migration",
"description": "Write or run a database migration",
"url": "https://example.com/skills/migration.md",
"author": "you"
}
],
"plugins": [
{
"name": "no-secrets",
"description": "Refuses writes to credential files",
"url": "https://example.com/plugins/no-secrets.json"
}
]
}
```
Both arrays are optional. A name may appear once as a skill and once as a plugin; `/registry
add skill:review` disambiguates, and an ambiguous name is refused rather than guessed.
A private index is just a URL you control. There is no account, no token, and no telemetry —
`/registry` makes exactly one GET for the index and one for the entry you install.