@klhapp/skillmux 1.1.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,612 +2,251 @@
2
2
  <img src="https://raw.githubusercontent.com/klhq/skillmux/main/docs/assets/logo.png" alt="skillmux" width="400">
3
3
  </p>
4
4
 
5
- A local, read-only [MCP](https://modelcontextprotocol.io) stdio server that gives agents **on-demand skill discovery**: route a natural-language task description to the right skill in your vault and deliver its `SKILL.md` byte-for-byte, verified by SHA-256.
6
-
7
- Built for agents that lack native skill triggering (Goose recipe workers, opencode, and friends). Agents that already trigger skills natively (e.g. Claude Code) don't need it.
8
-
9
- - [How it works](#how-it-works)
10
- - [Tiers: routed vs. pinned](#tiers-routed-vs-pinned)
11
- - [Install](#install)
12
- - [Quick start](#quick-start) — the fastest path to seeing it respond
13
- - [Pinning skills across surfaces](#pinning-skills-across-surfaces) — optional: statically load a curated set across multiple agents
14
- - [Docker Usage](#docker-usage)
15
- - [Configuration](#configuration) — inference modes, security scanning, installing skills, env vars
16
- - [CLI & Automation](docs/cli.md) — context management, remote target resolution, policy calibration, JSON envelopes
17
- - [Benchmarks & Evaluation](#benchmarks--evaluation)
18
- - [FAQ & Troubleshooting](#faq--troubleshooting)
19
- - [Guarantees](#guarantees)
20
- - [Development](#development)
21
-
22
- ## How it works
23
-
24
5
  <p align="center">
25
- <img src="docs/assets/architecture.svg" alt="skillmux Architecture &amp; Hybrid Routing Flow" width="100%">
6
+ <a href="https://github.com/klhq/skillmux/actions/workflows/ci.yml"><img alt="CI" src="https://github.com/klhq/skillmux/actions/workflows/ci.yml/badge.svg"></a>
7
+ <a href="https://github.com/klhq/skillmux/releases/latest"><img alt="GitHub release" src="https://img.shields.io/github/v/release/klhq/skillmux"></a>
8
+ <a href="https://www.npmjs.com/package/@klhapp/skillmux"><img alt="npm" src="https://img.shields.io/npm/v/@klhapp/skillmux"></a>
9
+ <a href="LICENSE"><img alt="License: MIT" src="https://img.shields.io/badge/license-MIT-blue.svg"></a>
26
10
  </p>
27
11
 
28
- ```
29
- resolve_skill("convert this spreadsheet to markdown")
30
-
31
-
32
- hybrid recall: SQLite FTS5 (BM25) ∪ embedding cosine (brute-force)
33
-
34
-
35
- reciprocal-rank fusion → shortlist
36
- optional reranker → matched | ambiguous | no_match
37
- ```
38
-
39
- - **matched** — one skill clearly wins: full `SKILL.md` delivered inline, `sha256(body) == content_sha256 ==` hash of the file on disk at delivery time. Stale index? It re-indexes and delivers fresh bytes — never stale ones.
40
- - **ambiguous** — up to 5 candidates (id, title, description). The calling LLM picks and calls `fetch_skill`.
41
- - **no_match** — proceed under your normal workflow; don't load an unrelated skill.
12
+ Skillmux manages [`SKILL.md`](https://agentskills.io) collections across AI
13
+ coding clients. Keep one canonical vault, pin a small set into native skill
14
+ directories, and retrieve the rest through MCP.
42
15
 
43
- If embeddings are unavailable, the router remains ready with FTS5 lexical retrieval. If an optional reranker is unavailable, it preserves the hybrid shortlist instead of failing.
16
+ The same Skillmux CLI manages native skills and can serve local stdio MCP.
17
+ Choose a setup by the job:
44
18
 
45
- ### Tools
19
+ 1. Need native skills or local MCP for one client? Install the **Skillmux CLI**.
20
+ 2. On Linux without Bun? Install the standalone Linux executable instead; it is
21
+ the same CLI.
22
+ 3. Need one shared HTTP MCP service? Deploy the **full Docker image**—the
23
+ self-contained default with GTE-small.
24
+ 4. Already have remote embeddings, or intentionally want lexical-only
25
+ retrieval? Use the **slim** image; see [Deployment](docs/deployment.md).
26
+ 5. Need native pins and shared retrieval? Run the CLI on the machines that own
27
+ client directories and one shared server for routed retrieval.
46
28
 
47
- | Tool | Input | Returns |
48
- | --------------- | ---------- | ---------------------------------------------------------------------------------------------------------------- |
49
- | `resolve_skill` | `query` | outcome + metadata in `structuredContent`; on match the verbatim body as text content (exactly once on the wire) |
50
- | `fetch_skill` | `skill_id` | verbatim body, `content_sha256`, supporting-file paths |
29
+ A shared service focuses on routed retrieval; manage its mounted vault on the
30
+ host with the CLI.
51
31
 
52
- The full contract lives in [`docs/schema.json`](docs/schema.json) (JSON Schema 2020-12, language-neutral).
32
+ ## One vault, three ways to use it
53
33
 
54
- ## Tiers: routed vs. pinned
34
+ <p align="center">
35
+ <img src="docs/assets/architecture.svg" alt="Three ways to use Skillmux: manage native skills, add local MCP retrieval, or run a shared MCP service" width="100%">
36
+ </p>
55
37
 
56
- Skills live in one vault, but there are two ways an agent gets one:
38
+ “Local” describes where Skillmux runs. “Local inference” means the embedding
39
+ model runs in the Skillmux process. Both stdio and HTTP expose the same
40
+ `resolve_skill` and `fetch_skill` MCP tools.
57
41
 
58
- - **routed** the default, described above. Nothing is loaded up front; the agent calls `resolve_skill` on demand and gets back exactly the skill that matches. This scales to hundreds of skills at zero standing cost.
59
- - **pinned** (`core` / `project`) — a small, hand-picked set of skills symlinked directly into an agent's own skill directory (e.g. `~/.claude/skills`), so they load the same way any other skill on that agent does — no MCP round-trip, no query. `core` pins apply everywhere; `project` pins apply only inside one repo.
42
+ ## Install the CLI
60
43
 
61
- Pinning is optional and orthogonal to serving: `skillmux init`/`sync` manage what's pinned; `skillmux serve` is what answers `resolve_skill` for everything else. Most single-agent setups never need pinning — reach for it once you're running the same small set of skills across multiple agent surfaces (Claude Code, opencode, ...) and don't want to maintain that list by hand in each one. See [Pinning skills across surfaces](#pinning-skills-across-surfaces).
44
+ The Bun package supports macOS, Linux, and Windows and requires
45
+ [Bun 1.3 or newer](https://bun.sh/docs/installation):
62
46
 
63
- ## Install
47
+ ```sh
48
+ bun add -g @klhapp/skillmux
49
+ skillmux --help
50
+ ```
64
51
 
65
- ### Linux Binary
52
+ Native target sync needs permission to create directory symlinks on Windows.
66
53
 
67
- Download the latest release for your architecture:
54
+ On Linux, you can install a compiled AMD64 or ARM64 binary instead:
68
55
 
69
56
  ```sh
70
- # AMD64
71
- gh release download --repo klhq/skillmux \
72
- --pattern 'skillmux-linux-*'
73
-
74
- # Optional: verify build provenance
57
+ gh release download --repo klhq/skillmux --pattern 'skillmux-linux-*'
75
58
  gh attestation verify skillmux-linux-amd64 --repo klhq/skillmux
76
-
77
- # Install the binary matching your machine (amd64 or arm64)
78
59
  chmod +x skillmux-linux-amd64
79
60
  sudo install skillmux-linux-amd64 /usr/local/bin/skillmux
80
- skillmux config show
81
61
  ```
82
62
 
83
- Release assets are also available at <https://github.com/klhq/skillmux/releases/latest>.
84
-
85
- Requirements at runtime:
86
-
87
- - A skill vault: one directory per skill with a `SKILL.md` in [agentskills.io](https://agentskills.io) format. Default: `~/skills`.
88
- - Optional remote OpenAI-compatible embeddings and Infinity-native reranking. The full binary uses local GTE-small embeddings by default.
63
+ Use `skillmux-linux-arm64` on ARM64. See [Deployment](docs/deployment.md) for
64
+ the full and slim Docker images.
89
65
 
90
- ## Quick start
66
+ ## Quick starts
91
67
 
92
- No config is required — the vault defaults to `~/skills`, and the full binary embeds its own local model, so there's nothing to download or provision first.
68
+ Skillmux uses `~/skills` as its default vault:
93
69
 
94
- ### 1. Put a skill in your vault
95
-
96
- A skill is just a directory with a `SKILL.md`. Author one by hand to try against:
97
-
98
- ```sh
99
- mkdir -p ~/skills/csv-formatter
100
- cat > ~/skills/csv-formatter/SKILL.md <<'EOF'
101
- ---
102
- name: CSV Formatter
103
- description: Converts CSV or spreadsheet data into clean, aligned Markdown tables. Use whenever the user asks to convert, format, or clean up tabular/CSV/spreadsheet data into Markdown.
104
- ---
105
-
106
- # CSV Formatter
107
-
108
- Given raw CSV input, emit a well-aligned Markdown table: infer column headers
109
- from the first row, right-align numeric columns, left-align text columns.
110
- EOF
70
+ ```text
71
+ ~/skills/
72
+ └── csv-formatter/
73
+ └── SKILL.md
111
74
  ```
112
75
 
113
- (Or fetch an existing skill from a git repo instead — see [Installing skills](#installing-skills) below.)
76
+ ### Manage native skills
114
77
 
115
- ### 2. Index and verify
78
+ Run the setup planner, then verify its managed links:
116
79
 
117
80
  ```sh
118
- skillmux index
81
+ skillmux init
82
+ skillmux sync
119
83
  skillmux doctor
120
84
  ```
121
85
 
122
- `doctor` should report `routing capability: hybrid` and every check `ok`. If something's `fail`, the `detail` column names the exact path or setting to fix.
123
-
124
- ### 3. Serve it
125
-
126
- ```sh
127
- skillmux serve
128
- ```
129
-
130
- Register with your MCP client directly, e.g.:
131
-
132
- ```json
133
- {
134
- "mcpServers": {
135
- "skillmux": {
136
- "command": "skillmux",
137
- "args": ["serve"]
138
- }
139
- }
140
- }
141
- ```
142
-
143
- ### Try it without an MCP client
144
-
145
- To see `resolve_skill` respond without wiring up a client, run the HTTP transport instead (`skillmux serve --transport http`, default port `3000`) and speak MCP's Streamable HTTP protocol directly:
146
-
147
- ```sh
148
- # 1. Initialize a session, capture the session id from the response header
149
- SESSION=$(curl -sS -D - -o /dev/null http://127.0.0.1:3000/mcp \
150
- -H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" \
151
- -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"try-it","version":"1.0.0"}}}' \
152
- | grep -i '^mcp-session-id:' | tr -d '\r' | cut -d' ' -f2)
153
-
154
- # 2. Complete the handshake
155
- curl -sS -o /dev/null -X POST http://127.0.0.1:3000/mcp \
156
- -H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" \
157
- -H "mcp-session-id: $SESSION" \
158
- -d '{"jsonrpc":"2.0","method":"notifications/initialized"}'
159
-
160
- # 3. Call resolve_skill
161
- curl -sS -X POST http://127.0.0.1:3000/mcp \
162
- -H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" \
163
- -H "mcp-session-id: $SESSION" \
164
- -d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"resolve_skill","arguments":{"query":"convert this spreadsheet to markdown"}}}'
165
- ```
166
-
167
- Against the `csv-formatter` skill authored above, that returns a real match — trimmed here for length:
168
-
169
- ```json
170
- {
171
- "result": {
172
- "structuredContent": {
173
- "outcome": "ambiguous",
174
- "retrieval": "hybrid",
175
- "candidates": [
176
- {
177
- "skill_id": "csv-formatter",
178
- "title": "CSV Formatter",
179
- "description": "Converts CSV or spreadsheet data..."
180
- }
181
- ]
182
- }
183
- }
184
- }
185
- ```
186
-
187
- `outcome` is `"ambiguous"` here specifically because the vault only has one skill in it — with more skills installed, a clear top match returns `"matched"` with the full `SKILL.md` body inline instead of a candidate list.
188
-
189
- ### Run from source instead
86
+ The planner detects clients, asks which skills belong in the core tier, and
87
+ shows every write before confirmation. Use explicit flags for automation:
190
88
 
191
89
  ```sh
192
- bun install --frozen-lockfile
193
- bun run src/cli.ts index
194
- bun run src/cli.ts serve
195
- ```
196
-
197
- ## Pinning skills across surfaces
90
+ skillmux init \
91
+ --client claude-code \
92
+ --client codex \
93
+ --core csv-formatter \
94
+ --dry-run
198
95
 
199
- Optional skip this if `resolve_skill` alone is enough (most setups). Use it once you want a small set of skills loaded _statically_ in every agent that reads from a given directory, instead of routed on demand — see [Tiers](#tiers-routed-vs-pinned).
200
-
201
- Run `skillmux init` with no arguments in a terminal for guided setup. It checks
202
- the vault, preselects clients with concrete filesystem evidence, asks for core
203
- skills, shows one review, and applies after one confirmation. The wizard uses
204
- line-oriented prompts and does not take over the terminal screen.
205
-
206
- ### 1. Check the plan
207
-
208
- ```sh
209
- skillmux init --client claude-code --client codex --core csv-formatter --dry-run
96
+ skillmux init \
97
+ --client claude-code \
98
+ --client codex \
99
+ --core csv-formatter \
100
+ --yes
210
101
  ```
211
102
 
212
- `--client` names the tool you use. Skillmux maps it to the correct user-level
213
- skill directory and merges clients that share one directory. Gemini CLI,
214
- OpenCode, GitHub Copilot, and Windsurf share `~/.agents/skills`. Claude Code
215
- uses `~/.claude/skills`; Codex uses `$CODEX_HOME/skills` or
216
- `~/.codex/skills`.
217
-
218
- Supported clients are `claude-code`, `codex`, `gemini-cli`, `opencode`,
219
- `github-copilot`, `windsurf`, `antigravity`, `goose`, `hermes`, and
220
- `skillmux-mcp`. Goose, Hermes, and Skillmux MCP report the manual registration
221
- work they need instead of inventing a target directory.
222
-
223
- The dry run prints target classification, config changes, instruction-file
224
- changes, readiness, and core pins. It does not prompt or write.
225
-
226
- ### 2. Apply the plan
227
-
228
- ```sh
229
- skillmux init --client claude-code --client codex --core csv-formatter --yes
230
- ```
231
-
232
- Skillmux writes `skillmux.toml`, adds host-scoped targets, adopts each selected
233
- directory, and installs one managed discovery block for each client with a
234
- known safe durable instruction file. Other clients report manual setup.
235
- Existing manifest entries and instruction text stay intact.
236
- Explicit flags keep the command deterministic. Without `--yes`, an interactive
237
- terminal reviews the plan before applying; a pipe or CI job must pass `--yes`.
238
- Use `--no-instructions` to leave instruction files untouched or `--no-sync` to
239
- save setup for a later `skillmux sync`.
240
-
241
- Use direct targets when you want paths rather than clients:
242
-
243
- ```sh
244
- skillmux init --target agent-skills --yes
245
- skillmux init --target claude-code --yes
246
- skillmux init --target codex --yes
247
- skillmux init --target custom --dir /srv/my-agent/skills --yes
248
- ```
249
-
250
- The old `agents` and `claude` target names still work and print a deprecation
251
- warning. Skillmux keeps those names in an existing manifest.
252
-
253
- If a target points at the whole vault, Skillmux classifies it as `full-vault`
254
- and refuses managed-pin adoption. Review the visibility change before
255
- converting it:
256
-
257
- ```sh
258
- skillmux init --client claude-code --migrate-full-vault \
259
- --core csv-formatter --dry-run
260
- skillmux init --client claude-code --migrate-full-vault \
261
- --core csv-formatter --yes
262
- ```
263
-
264
- After conversion and `skillmux sync`, the client sees the selected core pins
265
- instead of every vault skill.
266
-
267
- ### 3. Set up project-scoped skills
268
-
269
- From a project directory:
103
+ Core pins apply to each configured target and stay capped at 25 skills. Add
104
+ project-specific skills from a repository root:
270
105
 
271
106
  ```sh
272
107
  skillmux project init
273
108
  ```
274
109
 
275
- The guided flow detects the Git root, suggests a project-group name, asks which
276
- configured clients should receive the group, and accepts project skill IDs.
277
- For automation:
278
-
279
- ```sh
280
- skillmux project init ~/code/skillmux --name skillmux \
281
- --client claude-code --client codex \
282
- --skill sdd-tdd --skill code-context --yes
283
- ```
284
-
285
- Re-running the command adds missing paths, skills, and target attachments
286
- without duplicating existing entries. Project setup runs `sync` unless
287
- `--no-sync` is supplied.
288
-
289
- ### 4. Add or remove pins later
290
-
291
- `--core` seeds only the skill IDs you name. Without it, `init` preserves
292
- existing core pins and makes no guesses. Add another pin with:
293
-
294
- ```sh
295
- skillmux core pin csv-formatter --yes
296
- ```
297
-
298
- Pin several skills to `[core]` in one atomic call by passing more than one `skill_id`:
299
-
300
- ```sh
301
- skillmux core pin csv-formatter pdf-extractor terraform-plans --yes
302
- ```
303
-
304
- Unpin the same way: `skillmux core unpin csv-formatter --yes` (or `skillmux core unpin csv-formatter pdf-extractor --yes` for several at once). `skillmux project pin <group> <skill_id>...` pins into an existing `[project.<group>]` tier instead — create the group first with `skillmux project add-path <group> <path> --yes` if it doesn't exist yet. Every pin is validated before writing — the skill must actually resolve from the vault, and `[core]` stays under its 25-skill cap. When multiple `skill_id`s are given, they're applied to a single in-memory manifest and written once: if any one of them is already pinned elsewhere (or already unpinned, for `unpin`), the whole call fails and nothing is written.
305
-
306
- Hand-editing `skillmux.toml` still works if you prefer it:
307
-
308
- ```toml
309
- [core]
310
- skills = ["csv-formatter"]
311
-
312
- [targets.claude]
313
- dir = "/Users/you/.claude/skills"
314
- host = "workhorse" # optional; sync skips this target on every other hostname
315
- project_groups = []
316
- ```
317
-
318
- Full manifest schema, including `[project.<group>]` pins scoped to one or more local paths and machine-local overlay vaults via `local_vault_paths`, is in [`docs/configuration.md`](docs/configuration.md#tiers-and-the-manifest).
319
-
320
- ### 4. Materialize
110
+ ### Add local MCP retrieval
321
111
 
322
- ```sh
323
- skillmux sync
324
- # claude: +1 -0
325
- ```
326
-
327
- Each pinned skill becomes a symlink from the target dir into the vault. Re-running `sync` is idempotent (`+0 -0` once nothing changed); removing a skill from `[core]` removes its symlink on the next sync.
112
+ Prefetch the default GTE-small model, index the vault, and start stdio MCP:
328
113
 
329
114
  ```sh
330
- skillmux sync --dry-run # preview +added/-removed without touching disk
331
- skillmux sync --install-hook # add a git post-merge hook in the vault that runs `skillmux sync` automatically
332
- skillmux sync --restore-monolith # undo: replace a target dir with one symlink straight to the vault
333
- ```
334
-
335
- `--restore-monolith` drops the `.skillmux` marker along with the per-skill symlinks — re-adopt with `skillmux init --target <name> --yes` before that target can be `sync`'d again.
336
-
337
- ### 5. See what's actually getting used
338
-
339
- `skillmux report` reads the same audit log `resolve_skill` writes to (see [Guarantees](#guarantees)) — useful for deciding what belongs in `[core]` versus staying routed:
340
-
341
- ```sh
342
- skillmux report --since 7d # local: reads state_dir's audit db
343
- skillmux report --server http://host:3000 --since 7d # remote: hits a running server's /stats
344
- ```
345
-
346
- ```
347
- window: 2026-07-14T00:00:00Z .. 2026-07-21T00:00:00Z
348
- outcomes: matched=0 ambiguous=2 no_match=0 (ambiguous_rate=1.000)
349
- skills:
350
- csv-formatter matched=0 candidate=2
351
- pdf-extractor matched=0 candidate=2
352
- top no_match queries:
353
- (none)
115
+ skillmux models download
116
+ skillmux index
117
+ skillmux doctor
118
+ skillmux serve
354
119
  ```
355
120
 
356
- `--since` accepts a relative window (`1h`, `7d`, `1m`) or an absolute date/timestamp. A skill matched often but never pinned is a `[core]` candidate; a query that keeps showing up in `top_no_match_queries` means the vault is missing something.
357
-
358
- ## Docker Usage
359
-
360
- The `skillmux` is packaged and distributed as a Docker image in two variants:
361
-
362
- 1. **`skillmux:latest`**: Bundles the small quantized GTE embedding model for local hybrid retrieval.
363
- 2. **`skillmux:latest-slim`**: Excludes model weights and supports configured remote embeddings or lexical fallback.
364
-
365
- Both tags are multi-architecture manifests for Linux AMD64 and ARM64; Docker selects the correct image automatically. Images are published to both [`ghcr.io/klhq/skillmux`](https://github.com/klhq/skillmux/pkgs/container/skillmux) and [`docker.io/klhq/skillmux`](https://hub.docker.com/r/klhq/skillmux) — either registry works, examples below use GHCR.
121
+ The model cache lives at `~/.cache/skillmux/models`. If you skip the prefetch,
122
+ Skillmux downloads the model when local inference first needs it.
366
123
 
367
- ### Running HTTP Server (Docker Default)
124
+ ### Run a shared MCP service
368
125
 
369
- To run as an HTTP MCP service (default in Docker):
126
+ The full image includes GTE-small and serves Streamable HTTP on `/mcp`:
370
127
 
371
128
  ```sh
372
- # Battery-included (runs local in-process ONNX models)
373
129
  docker run -d \
374
130
  --name skillmux \
375
131
  -v ~/skills:/vault:ro \
376
132
  -v skillmux-data:/data \
377
133
  -p 3000:3000 \
378
134
  ghcr.io/klhq/skillmux:latest
379
-
380
- # Slim (configured remote embeddings, or lexical fallback)
381
- docker run -d \
382
- --name skillmux-slim \
383
- -v ~/skills:/vault:ro \
384
- -v skillmux-data:/data \
385
- -p 3000:3000 \
386
- -e EMBED_ENDPOINT="http://embeddings-host:8080/v1/embeddings" \
387
- ghcr.io/klhq/skillmux:latest-slim
388
135
  ```
389
136
 
390
- Connect your MCP client to the HTTP endpoint (e.g. standard Streamable HTTP transport):
391
-
392
- - POST messages to `http://localhost:3000/mcp`
393
-
394
- #### HTTP server: auth, CORS, rate limiting
137
+ Use `ghcr.io/klhq/skillmux:latest-slim` when you want remote embeddings or
138
+ lexical fallback instead of a bundled model. Docker Hub mirrors both variants
139
+ under `docker.io/klhq/skillmux`.
395
140
 
396
- All of the below is `[server]` config in `config.toml`, overridable by environment variable — see [Environment Variable Overrides](#environment-variable-overrides).
141
+ The [getting-started guide](docs/getting-started.md) provides complete recipes
142
+ for all three setups.
397
143
 
398
- - **Bind address** (`hostname`, default `127.0.0.1`) — HTTP transport binds loopback-only by default, so a zero-config `skillmux serve --transport http` isn't reachable from the network. Inside Docker (`RUNNING_IN_DOCKER=true`) this defaults to `0.0.0.0` instead, since the container's own loopback isn't reachable through port-mapping. Set `hostname` (or `HTTP_HOSTNAME`) explicitly to expose the server beyond localhost.
399
- - **Bearer token auth** (off by default) — set `auth_enabled = true` and the token via the env var named by `auth_token_env` (default `SKILLMUX_AUTH_TOKEN`). Requests need `Authorization: Bearer <token>`; missing/mismatched tokens get `401`, and a configured-but-empty token env var gets `500`.
400
- - **CORS** — `allowed_origins` (default `[]`, deny-by-default) is checked against the request's `Origin` header; disallowed origins get `403`. Requests with no `Origin` header (curl, MCP clients, server-to-server) are unaffected either way — only browser-issued cross-origin requests are gated. `/health` and `/metrics` are excluded from auth but still CORS-checked.
401
- - **Rate limiting** (off by default) — per-token (when auth is enabled) or per-IP (`server.requestIP`) token-bucket limiting. Enable with `rate_limit.enabled = true` and set `rate_limit.requests_per_minute` (default `60`). The `X-Forwarded-For` header is ignored unless `rate_limit.trust_proxy = true` — it's client-supplied and spoofable, so only opt in when a trusted reverse proxy sets it. Every response carries `X-RateLimit-Limit`/`X-RateLimit-Remaining`/`X-RateLimit-Reset`; over-limit requests get `429` plus `Retry-After`.
402
- - **`GET /health/live`** — lightweight liveness check. Legacy `GET /health` remains an alias.
403
- - **`GET /health/ready`** — readiness with active retrieval capability, skill count, index state, and inference status.
404
- - **`GET /metrics`** — Prometheus text exposition: `skill_router_requests_total`, `skill_router_resolve_outcomes_total`, `skill_router_resolve_latency_seconds` (histogram), `skill_router_errors_total`, `skill_router_rate_limits_exceeded_total`.
144
+ ## Add and inspect skills
405
145
 
406
- ### Running Stdio Server in Docker
407
-
408
- If your agent runs locally and expects a piped stdio process:
146
+ Install a skill from a Git repository:
409
147
 
410
148
  ```sh
411
- docker run -i --rm \
412
- -v ~/skills:/vault:ro \
413
- ghcr.io/klhq/skillmux:latest serve --transport stdio
149
+ skillmux install owner/repo
150
+ skillmux install owner/repo/path/to/skill
414
151
  ```
415
152
 
416
- ## Configuration
417
-
418
- No config is required for the battery-included local ONNX mode. See [`config.example.toml`](config.example.toml) for the minimal local setup, [`config.remote.example.toml`](config.remote.example.toml) for bring-your-own endpoints, and [`docs/configuration.md`](docs/configuration.md) for advanced settings.
419
-
420
- ### Inference Modes
421
-
422
- - The zero-config default combines SQLite FTS5 with the small `Xenova/gte-small` embedding model and returns an ordered shortlist.
423
- - Configured OpenAI-compatible embeddings replace the local embedder. An optional versioned reranker protocol adapter enables confident automatic matches without coupling configuration to a server product or URL shape.
424
-
425
- Run `skillmux doctor` to verify routing capability. Run `skillmux config show` to inspect effective configuration; it prints credential variable names, never values.
426
-
427
- ### Security scanning
153
+ Skillmux validates `SKILL.md` and scans candidate content before copying it into the vault. Use `--fail-on low|medium|high` to turn selected findings into an install gate.
428
154
 
429
- `skillmux scan [<path>]` inspects skill content for prompt-injection and data-exfiltration risk indicators
430
- before it's served over MCP or HTTP. It's offline (no network call, no inference config needed),
431
- read-only, and advisory-only — it never blocks `skillmux index`/`sync`/`init`, which don't call it
432
- automatically.
155
+ Useful management commands:
433
156
 
434
157
  ```sh
435
- skillmux scan # scan the configured vault_path
436
- skillmux scan ~/skills/some-skill # scan a single candidate skill dir before adding it
437
- skillmux scan --format json # machine-readable { scanned, findings } for CI
438
- skillmux scan --fail-on high # exit 1 if any finding is high severity (for CI gating)
158
+ skillmux scan ~/skills
159
+ skillmux core pin csv-formatter --yes
160
+ skillmux project pin my-project code-context --yes
161
+ skillmux skill which csv-formatter
162
+ skillmux report --since 7d
439
163
  ```
440
164
 
441
- The v1 rule set covers four categories, each attached to the finding as `rule_id` with a fixed
442
- `severity`:
443
-
444
- | `rule_id` | `severity` | Flags |
445
- | ------------------------- | ---------- | ------------------------------------------------------------------------------------------------- |
446
- | `prompt-injection-phrase` | `high` | Known instruction-override phrases (e.g. "ignore previous instructions") |
447
- | `invisible-unicode` | `high` | Zero-width/invisible Unicode code points, including hidden tag-character payloads |
448
- | `secret-pattern` | `high` | Hardcoded-credential-shaped strings (AWS-style keys, PEM blocks, `api_key=`/`token=` assignments) |
449
- | `suspicious-url` | `medium` | Bare-IP-address URLs, or URLs paired with exfiltration-suggesting text |
450
-
451
- `skillmux scan` is unrelated to the `audit` SQLite table / `skillmux report` — that's query telemetry (what got
452
- routed where); `skillmux scan` is content security (what's in the vault).
453
-
454
- ### Installing skills
455
-
456
- `skillmux install <repo>[/path]` fetches one skill from a git repo (GitHub shorthand, full HTTPS/SSH URL,
457
- or `file://`) into the configured `vault_path`, so onboarding doesn't require a second CLI just to
458
- pull a skill in. It's a convenience fetch, not a distribution system:
165
+ Read [Managing skills](docs/skill-management.md) for target ownership, project groups, local overrides, recovery, and reporting.
459
166
 
460
- ```sh
461
- skillmux install owner/repo # repo root must itself be a skill
462
- skillmux install owner/repo/path/to/skill # select one skill out of a multi-skill repo
463
- skillmux install owner/repo --dry-run # preview id, target path, and scan findings
464
- skillmux install owner/repo --force # overwrite an existing skill_id
465
- skillmux install owner/repo --fail-on high # abort the install if scan findings meet the threshold
466
- ```
167
+ ## MCP retrieval
467
168
 
468
- The fetched skill is validated the same way `skillmux scan` validates the vault — malformed `SKILL.md`
469
- aborts the install, and scan findings are printed before anything is written (advisory by default;
470
- `--fail-on` opts into blocking, matching `skillmux scan`'s severity levels). Materialization is a plain
471
- file copy, not a symlink — the temporary clone is deleted once the install completes.
472
-
473
- `skillmux install` intentionally does **not** handle updates, uninstalls, version pinning, or
474
- core/project/routed tier assignment (that's `skillmux sync`'s domain) — it only ever fetches one skill,
475
- once. Use `skillmux sync` afterward if the installed skill needs to be pinned into a tier.
476
-
477
- ### Environment Variable Overrides
478
-
479
- All core settings can be overridden via environment variables (handy for Docker):
480
-
481
- - `VAULT_PATH` — overrides `vault_path` (defaults to `/vault` inside Docker)
482
- - `STATE_DIR` — overrides `state_dir` (defaults to `/data` inside Docker)
483
- - `EMBED_ENDPOINT` / `SKILLMUX_EMBED_ENDPOINT` — overrides the complete remote `inference.embedding.endpoint`
484
- - `EMBED_MODEL` / `SKILLMUX_EMBED_MODEL` — overrides `embedding.model`
485
- - `EMBED_DIMENSION` / `SKILLMUX_EMBED_DIMENSION` — overrides `embedding.dimension`
486
- - `EMBED_DEVICE` / `EMBED_DTYPE` — overrides local `inference.embedding.device` / `inference.embedding.dtype`
487
- - `RERANK_ENDPOINT` / `SKILLMUX_RERANK_ENDPOINT` — overrides the complete remote `inference.reranker.endpoint`
488
- - `RERANK_ADAPTER` / `SKILLMUX_RERANK_ADAPTER` — overrides `inference.reranker.adapter`
489
- - `RERANK_MODEL` / `SKILLMUX_RERANK_MODEL` — overrides `inference.reranker.model`
490
- - `SKILLMUX_CONFIG` — path to custom `config.toml` (default `~/.config/skillmux/config.toml`)
491
- - `SKILLMUX_MODELS_DIR` — path to directory storing downloaded local models (default `~/.cache/skillmux/models`, `/models` inside Docker)
492
- - `PORT` — HTTP listen port (default `3000`, HTTP transport only)
493
- - `HTTP_HOSTNAME` — overrides `server.hostname` (default `127.0.0.1`, `0.0.0.0` inside Docker)
494
- - `HTTP_AUTH_ENABLED` — overrides `server.auth_enabled` (`"true"` to enable)
495
- - `HTTP_AUTH_TOKEN_ENV` — overrides `server.auth_token_env`
496
- - `HTTP_ALLOWED_ORIGINS` — comma-separated list, overrides `server.allowed_origins`
497
- - `HTTP_RATE_LIMIT_ENABLED` / `SKILLMUX_HTTP_RATE_LIMIT_ENABLED` — overrides `server.rate_limit.enabled` (`"true"` to enable)
498
- - `HTTP_RATE_LIMIT_RPM` / `SKILLMUX_HTTP_RATE_LIMIT_RPM` — overrides `server.rate_limit.requests_per_minute`
499
- - `HTTP_RATE_LIMIT_TRUST_PROXY` / `SKILLMUX_HTTP_RATE_LIMIT_TRUST_PROXY` — overrides `server.rate_limit.trust_proxy` (`"true"` to trust `X-Forwarded-For`)
500
-
501
- Remote API keys are read independently from the environment variables named by
502
- `inference.embedding.api_key_env` and `inference.reranker.api_key_env`. Omit
503
- `api_key_env` for an intentionally unauthenticated endpoint. If it is present,
504
- the named variable must be non-empty before clients are created and is sent as
505
- a Bearer token. Secret values never live in the config file or diagnostics.
506
-
507
- Rerankers use an exact endpoint plus an explicit adapter. `jina-v1` sends
508
- string documents; `bifrost-v1` sends Bifrost document objects. Skillmux never
509
- infers an adapter from the URL and never adds or removes path components.
510
-
511
- Embeddings likewise use an exact endpoint and the OpenAI-compatible
512
- `{ model, input }` contract. Skillmux never adds `/v1/embeddings` or rewrites
513
- the configured path or query string. Replace the removed `base_url` setting
514
- with the complete endpoint, for example `base_url = "http://host"` or
515
- `base_url = "http://host/v1"` becomes
516
- `endpoint = "http://host/v1/embeddings"`. The removed `EMBED_BASE_URL`,
517
- `SKILLMUX_EMBED_BASE_URL`, and `SKILL_ROUTER_EMBED_BASE_URL` variables are
518
- startup errors with migration guidance.
519
-
520
- > **Breaking reranker migration:** replace `provider = "infinity"` with
521
- > `adapter = "jina-v1"`, and replace `base_url` with the complete `endpoint`.
522
- > The old client appended `/rerank`. For example,
523
- > `base_url = "http://host/v1"` becomes
524
- > `endpoint = "http://host/v1/rerank"`; a bare `http://host` becomes
525
- > `http://host/rerank`. The removed `RERANK_BASE_URL`,
526
- > `SKILLMUX_RERANK_BASE_URL`, and `SKILL_ROUTER_RERANK_BASE_URL` variables are
527
- > startup errors with migration guidance.
528
-
529
- Verified reranker contracts:
530
-
531
- | Implementation | Tested version | Endpoint | Adapter | Verification |
532
- | ------------------- | -------------: | -------------------- | ------------ | ----------------------------------------------------------------- |
533
- | Bifrost | 1.6.6 | `/v1/rerank` | `bifrost-v1` | Live end-to-end request and recorded contract fixture, 2026-07-28 |
534
- | Jina-style contract | fixture | exact configured URL | `jina-v1` | Automated request/response contract suite |
535
-
536
- ## Benchmarks & Evaluation
537
-
538
- Skillmux includes a built-in evaluation framework to benchmark retrieval accuracy (lexical vs. hybrid vector search) against labeled intent datasets.
539
-
540
- Evaluate lexical and local hybrid retrieval against the checked-in labeled queries:
169
+ Register it with an MCP client:
541
170
 
542
- ```sh
543
- bun run src/cli.ts eval
544
- # holdout queries: 8
545
- # lexical recall@5: 1.000
546
- # hybrid recall@5: 1.000
171
+ ```json
172
+ {
173
+ "mcpServers": {
174
+ "skillmux": {
175
+ "command": "skillmux",
176
+ "args": ["serve"]
177
+ }
178
+ }
179
+ }
547
180
  ```
548
181
 
549
- Custom policy calibration can also be performed against domain-specific query logs using `skillmux calibrate` (see [`docs/cli.md`](docs/cli.md#policy-calibration-skillmux-calibrate)).
550
-
551
- ## FAQ & Troubleshooting
552
-
553
- <details>
554
- <summary><b>Why did my query return <code>ambiguous</code> instead of <code>matched</code>?</b></summary>
555
-
556
- <br>
557
-
558
- The router returns `"outcome": "ambiguous"` when multiple candidate skills meet retrieval confidence thresholds, or when no single candidate dominates by a sufficient score margin. In this state, up to 5 candidate skill summaries (`skill_id`, `title`, `description`) are returned so the calling LLM can choose the exact skill and invoke `fetch_skill`.
559
-
560
- </details>
561
-
562
- <details>
563
- <summary><b>Does skillmux require an active internet connection?</b></summary>
564
-
565
- <br>
566
-
567
- No. In default local inference mode (`inference.mode = "local"`), skillmux operates 100% offline. The default GTE-small embedding model is quantized to q8 and bundled within the binary/Docker image.
182
+ Skillmux exposes two tools:
568
183
 
569
- </details>
184
+ | Tool | Input | Result |
185
+ | --- | --- | --- |
186
+ | `resolve_skill` | Natural-language task description | A matched skill, an ordered shortlist, or no match |
187
+ | `fetch_skill` | Exact `skill_id` | The current `SKILL.md` body, SHA-256 digest, and supporting-file paths |
570
188
 
571
- <details>
572
- <summary><b>What happens when remote embedding or reranking endpoints fail?</b></summary>
189
+ Skillmux uses the best available capability:
573
190
 
574
- <br>
191
+ 1. SQLite FTS5 provides lexical retrieval and offline fallback.
192
+ 2. Local or remote embeddings add semantic recall.
193
+ 3. An optional reranker orders candidates and can produce calibrated automatic matches.
575
194
 
576
- Skillmux features automatic fallback degradation. If remote embedding APIs or rerankers become unreachable, the router gracefully falls back to SQLite FTS5 lexical search rather than throwing an error to the calling agent.
195
+ Without calibrated reranker thresholds, Skillmux returns an ordered shortlist and lets the calling model choose. Endpoint failures degrade to a healthy lower retrieval mode instead of taking the MCP server down.
577
196
 
578
- </details>
197
+ Read [MCP routing](docs/mcp-routing.md) for transports, outcomes, client instructions, retrieval modes, and the wire contract.
579
198
 
580
- <details>
581
- <summary><b>When should I use routed skills vs. pinned skills?</b></summary>
199
+ ## Supported clients
582
200
 
583
- <br>
201
+ | Client | Native skill delivery | MCP setup |
202
+ | --- | --- | --- |
203
+ | Claude Code | `~/.claude/skills` | Configure in the client |
204
+ | Codex | `$CODEX_HOME/skills` or `~/.codex/skills` | Configure in the client |
205
+ | Gemini CLI, OpenCode, GitHub Copilot, Windsurf | `~/.agents/skills` | Configure in the client |
206
+ | Antigravity | `~/.gemini/config/skills` | Configure in the client |
207
+ | Goose, Hermes | Manual full-vault setup | Manual registration |
208
+ | Custom clients | Any directory through a custom target | Stdio or Streamable HTTP |
584
209
 
585
- - **Routed (Default)**: Best for expanding vaults with dozens or hundreds of skills. Skills are loaded dynamically on demand via `resolve_skill`, keeping the agent's initial context window clean.
586
- - **Pinned (`skillmux sync`)**: Best when running multiple agent surfaces (e.g. Claude Code, opencode) that all require the same core set of 2–5 skills loaded statically at agent startup.
587
-
588
- </details>
589
-
590
- <details>
591
- <summary><b>How do I verify server readiness and routing health?</b></summary>
592
-
593
- <br>
594
-
595
- Run `skillmux doctor` locally or hit the HTTP readiness endpoint (`GET /health/ready`). `doctor` checks vault accessibility, state directory permissions, ONNX runtime binding status, and active retrieval lane status.
596
-
597
- </details>
210
+ Skillmux preserves existing instruction files and unmanaged target content. Run `skillmux init --dry-run` to inspect every planned filesystem change.
598
211
 
599
212
  ## Guarantees
600
213
 
601
- - **Read-only vault** no code path writes under the vault; all state is confined to `state_dir`. Covered by tests.
602
- - **Zero-loss delivery** delivered bytes always hash-match the file on disk at delivery time.
603
- - **Live index** a running server folds vault changes (create/modify/delete) into the index within seconds; an unparseable write keeps the previous good entry rather than evicting the skill.
604
- - **Audit log** every `resolve_skill` call is appended to a SQLite table in `state_dir` (timestamp, query, outcome, candidates with scores, latency).
214
+ - **Controlled sources:** pins come from the canonical vault, while routed delivery follows the configured overlay order.
215
+ - **Scoped writes:** management commands write only to documented config, vault, state, and adopted target paths.
216
+ - **Managed ownership:** sync removes only entries recorded in the target's `.skillmux` marker.
217
+ - **Current bytes:** MCP delivery hashes the file on disk and never serves a stale indexed body.
218
+ - **Graceful retrieval:** embedding and reranker failures fall back without hiding the active capability.
219
+ - **Auditable decisions:** each `resolve_skill` call records its outcome, candidates, scores, and latency in the state database.
220
+
221
+ ## Documentation
222
+
223
+ Start with the [documentation hub](docs/README.md).
224
+
225
+ | Guide | Covers |
226
+ | --- | --- |
227
+ | [Getting started](docs/getting-started.md) | Native management, local MCP, and shared-service recipes |
228
+ | [Concepts](docs/concepts.md) | Delivery tiers, deployment topologies, retrieval modes, and ownership |
229
+ | [Managing skills](docs/skill-management.md) | Install, scan, pin, sync, report, overlays, and recovery |
230
+ | [MCP routing](docs/mcp-routing.md) | Tools, outcomes, transports, retrieval, fallback, and integrity |
231
+ | [Deployment](docs/deployment.md) | Docker, HTTP, auth, CORS, rate limits, health, and metrics |
232
+ | [Configuration](docs/configuration.md) | Machine config, inference, manifests, and overlays |
233
+ | [CLI reference](docs/cli.md) | Commands, contexts, automation, JSON output, and exit codes |
234
+ | [Policy calibration](docs/calibration.md) | Labelled datasets, certification, and threshold application |
235
+ | [Troubleshooting](docs/troubleshooting.md) | `doctor`, common failures, and migration notes |
236
+ | [MCP schema](docs/schema.json) | JSON Schema 2020-12 tool contract |
605
237
 
606
238
  ## Development
607
239
 
240
+ Skillmux uses Bun for development:
241
+
608
242
  ```sh
609
- bun test # full suite (contract, hybrid recall, stdio e2e, watcher, eval)
610
- bun run build # single-file binary via bun build --compile
243
+ bun install --frozen-lockfile
244
+ bun test
245
+ bun run build
611
246
  ```
612
247
 
613
- Reference: [`docs/configuration.md`](docs/configuration.md) and [`docs/schema.json`](docs/schema.json).
248
+ Read [CONTRIBUTING.md](CONTRIBUTING.md) for the development workflow and [SECURITY.md](SECURITY.md) for vulnerability reporting.
249
+
250
+ ## License
251
+
252
+ [MIT](LICENSE)