# Registry External skills and plugins, browsed and installed from the CLI. ``` /registry everything in the index /registry search narrow by name or description /registry installed what is already here /registry add fetch, show, then install on confirmation /registry remove 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 ``` `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/.md loaded as origin "registry" plugins/.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.