@klhapp/skillmux 0.2.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/CHANGELOG.md ADDED
@@ -0,0 +1,147 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [0.2.0](https://github.com/klhq/skillmux/compare/skillmux-v0.1.1...skillmux-v0.2.0) (2026-07-21)
9
+
10
+
11
+ ### Added
12
+
13
+ * **cli:** add inference setup commands ([8fda80c](https://github.com/klhq/skillmux/commit/8fda80c24e5fc3ba6ddc969621630bebaa5b1580))
14
+ * **config:** add explicit inference modes ([4e73073](https://github.com/klhq/skillmux/commit/4e73073e026e8a37b7d6cac0f24c1100c0a050b3))
15
+ * **eval:** validate local hybrid retrieval ([172b703](https://github.com/klhq/skillmux/commit/172b703af63ed3826cafb7e461fe072c9d0c4dad))
16
+ * **health:** add liveness and readiness endpoints ([a1ac4e2](https://github.com/klhq/skillmux/commit/a1ac4e2590485d581b33d0b9c2e5f28d4316598a))
17
+ * **lifecycle:** initialize routing before readiness ([4526d5d](https://github.com/klhq/skillmux/commit/4526d5d6c25ddcda30b2550b9dff28c50956ceaf))
18
+ * **lifecycle:** stop server resources gracefully ([7deb6f2](https://github.com/klhq/skillmux/commit/7deb6f2ea5ed399911e829d5f6da1b371143de35))
19
+ * **npm-publish:** publish package on tagged releases ([#22](https://github.com/klhq/skillmux/issues/22)) ([4638027](https://github.com/klhq/skillmux/commit/46380275c5ec99388e4f6b405085995df17b016c))
20
+ * **ops:** add readiness-aware lifecycle ([391d9ff](https://github.com/klhq/skillmux/commit/391d9ffc4a5afd2140ae1279671d3cd058e1d187))
21
+ * **ops:** expose readiness metrics and Docker probe ([891eab5](https://github.com/klhq/skillmux/commit/891eab5bb30d0da9e084c5a1ec4e4f35deb63305))
22
+ * rename project to skillmux and implement compatibility shims ([bec8a11](https://github.com/klhq/skillmux/commit/bec8a115acae31ab939b3e0991fba1f668f94f97))
23
+ * **router-core:** add device and dtype config for local models ([3c08b3f](https://github.com/klhq/skillmux/commit/3c08b3fbaefdadafc0d108429fd3d44e3436e243))
24
+ * **router-core:** add HTTP auth and CORS controls ([395d94b](https://github.com/klhq/skillmux/commit/395d94b5214e55b146af6b6e5a01b6486aa7ef7f))
25
+ * **router-core:** add local ONNX clients and model downloader ([b387c1e](https://github.com/klhq/skillmux/commit/b387c1e1486a985f86f6261fb5ecceedde2a1c4f))
26
+ * **router-core:** add Streamable HTTP transport server ([6f6384e](https://github.com/klhq/skillmux/commit/6f6384e2ee5bbe756b3da393ac97db3c50ea48b9))
27
+ * **router-core:** hybrid skill routing MCP server ([0d4f25b](https://github.com/klhq/skillmux/commit/0d4f25b3db61732126a8935d11d30a2111e23794))
28
+ * **router-core:** make ambiguous shortlist configurable ([b362564](https://github.com/klhq/skillmux/commit/b3625648025eb8eff67d8fa4d85063277e66eef9))
29
+ * **router-core:** robust local ONNX config, HTTP security, and testing ([ff0a6c4](https://github.com/klhq/skillmux/commit/ff0a6c417a1cf42745d0a44dfb8bfe9dfb9ad5d3))
30
+ * **router-core:** scaffold TypeScript core and fixture tests ([41ae4a1](https://github.com/klhq/skillmux/commit/41ae4a180e0fc4529f37783e69cd3985ed2505f3))
31
+ * **router:** add GTE hybrid retrieval ([3496ae6](https://github.com/klhq/skillmux/commit/3496ae6ab9832097de65ab6bbb9339dc7dd8ba1d))
32
+ * **router:** add HTTP rate limiting and metrics ([#4](https://github.com/klhq/skillmux/issues/4)) ([c7840a4](https://github.com/klhq/skillmux/commit/c7840a4de5d57672e768a48ed8e2eac6b151ebc0))
33
+ * **router:** add hybrid retrieval ranking ([d28dd1e](https://github.com/klhq/skillmux/commit/d28dd1e3ea3f2192921d47a11c2f6b13d314d3f0))
34
+ * **router:** add model overrides and HTTP observability ([#3](https://github.com/klhq/skillmux/issues/3)) ([923c99c](https://github.com/klhq/skillmux/commit/923c99c2bdb0e47beb9b99079bf51a1e9172c34f))
35
+ * **skillmux:** complete rename with compatibility shims ([6ef3f0e](https://github.com/klhq/skillmux/commit/6ef3f0efbfd13454354bb7970a386e41fbad0d82))
36
+ * **skr-cli:** add --dry-run support to syncTarget ([e42f7a6](https://github.com/klhq/skillmux/commit/e42f7a6783194fea9ea1e3002672bb40392b516e))
37
+ * **skr-cli:** add --install-hook for an idempotent post-merge sync hook ([1df48d9](https://github.com/klhq/skillmux/commit/1df48d9374fe84509b8eb03d901e364e751d873f))
38
+ * **skr-cli:** add --restore-monolith to revert a marked target to a vault symlink ([49bef90](https://github.com/klhq/skillmux/commit/49bef90f2c6d9f2344f621ba7e5ccefd9f60a55e))
39
+ * **skr-cli:** add adoptTarget for skr init's in-place ownership marking ([d3757eb](https://github.com/klhq/skillmux/commit/d3757ebfd017a7d36d820073d856ac7f11bfe3f4))
40
+ * **skr-cli:** add serializeManifest to write skr.toml ([8675ff4](https://github.com/klhq/skillmux/commit/8675ff40f7b81426a6b7a99d9ae0d874a85be7f8))
41
+ * **skr-cli:** add skr.toml manifest parser ([d7a3d11](https://github.com/klhq/skillmux/commit/d7a3d11cb7f2b796132636cb901b98bdace27b0a))
42
+ * **skr-cli:** aggregate audit rows into GET /stats StatsResponse shape ([22e556a](https://github.com/klhq/skillmux/commit/22e556a1d48ebc6a1a7f56f4070b728ac60f48a1))
43
+ * **skr-cli:** applyInit writes skr.toml and adopts confirmed targets ([f8df561](https://github.com/klhq/skillmux/commit/f8df56129fe5fb36cbfea58f337d47225c8679d1))
44
+ * **skr-cli:** default vault_path to the neutral ~/skills ([a642249](https://github.com/klhq/skillmux/commit/a642249f645701ee644240795f810d51b7bd91a4))
45
+ * **skr-cli:** derive [targets.<name>] key from a detected surface path ([cef13a8](https://github.com/klhq/skillmux/commit/cef13a8c9183499b0dec910e3f6d4ec391fd423c))
46
+ * **skr-cli:** detect existing skill surfaces with evidence ([a2a5bad](https://github.com/klhq/skillmux/commit/a2a5badfe47ea96e78056ca54679c4e0ff434ea0))
47
+ * **skr-cli:** enforce core cap and skip missing repo paths with a note ([6e86abc](https://github.com/klhq/skillmux/commit/6e86abc5e582d2879fdd8cf9cc848b10e6061f6a))
48
+ * **skr-cli:** materialize a fresh skr sync target with core-skill symlinks ([c38c1ae](https://github.com/klhq/skillmux/commit/c38c1aef37cae5eb6650e970f0ba6e784e578699))
49
+ * **skr-cli:** parse relative/absolute --since windows for GET /stats ([e035fa5](https://github.com/klhq/skillmux/commit/e035fa56be576e9b2bcf7b0dc8e146c8b0fc5fd8))
50
+ * **skr-cli:** print the last mile (MCP registration + §3.3 discovery paragraph) ([f532c1a](https://github.com/klhq/skillmux/commit/f532c1aea3d59aba6d92f982c61d1b21842e710c))
51
+ * **skr-cli:** propose an empty core/project manifest (conservative default) ([f7324cf](https://github.com/klhq/skillmux/commit/f7324cfb68686a6a62df1b70dc6ed6d6ce7c0eec))
52
+ * **skr-cli:** query audit db and compose getStats(db, since) ([ccbcd43](https://github.com/klhq/skillmux/commit/ccbcd43aaa00515c1f70f7b6b0144fa399e13323))
53
+ * **skr-cli:** rebuild marked sync targets with add/remove diffing ([26ff381](https://github.com/klhq/skillmux/commit/26ff381203f00ba61b84f846b95472be27f519ec))
54
+ * **skr-cli:** rename installed binary to skr ([2b7adc2](https://github.com/klhq/skillmux/commit/2b7adc2183c3b130b82466a6ccaaf8c7799c5611))
55
+ * **skr-cli:** render StatsResponse as human-readable report text ([f431e70](https://github.com/klhq/skillmux/commit/f431e7082bd602b3f67342a7cf388f3e82002e26))
56
+ * **skr-cli:** replicate project-tier pin dirs into repos for project=true targets ([2005aa1](https://github.com/klhq/skillmux/commit/2005aa1df2837d89a6ac4003a6426481342ad99e))
57
+ * **skr-cli:** validate manifest skills exist and don't overlap core/project ([c3df818](https://github.com/klhq/skillmux/commit/c3df818724684eb479256358ef61f5c7751749cd))
58
+ * **skr-cli:** wire GET /stats, gated by server.auth_enabled ([7382f32](https://github.com/klhq/skillmux/commit/7382f32cdc557977d8eb020207a0ca173e13d057))
59
+ * **skr-cli:** wire skr init into the CLI ([c6bddc9](https://github.com/klhq/skillmux/commit/c6bddc9d6b4151f3322786a563954ed9d9b00a57))
60
+ * **skr-cli:** wire skr report --server/--db/--since CLI subcommand ([7d637d7](https://github.com/klhq/skillmux/commit/7d637d7e9148d6a80402124b0d093e7ebc1de3ee))
61
+ * **skr-cli:** wire skr sync into the CLI ([56e8deb](https://github.com/klhq/skillmux/commit/56e8deb37a70a7375df6c3dd07ed7f0b234f55f0))
62
+ * **skr-install:** add git-backed skill installer ([351bd10](https://github.com/klhq/skillmux/commit/351bd108d93111f108d182230d81dbdfccf26ef9))
63
+ * **skr-install:** clone a repo into a temp dir, clean up on failure ([2db0603](https://github.com/klhq/skillmux/commit/2db0603e5d41a2e3c4ec552ae48a01d07efe8e35))
64
+ * **skr-install:** copy skill into vault_path, guard against skill_id conflicts ([230c489](https://github.com/klhq/skillmux/commit/230c48922b6518fcef03a2eb7b107d347386b9d9))
65
+ * **skr-install:** resolve repo shorthand and git URLs to a clone source ([a799345](https://github.com/klhq/skillmux/commit/a79934588317abb496e481a4695dae8a56b5e755))
66
+ * **skr-install:** resolve target skill dir, list candidates when ambiguous ([c6b7635](https://github.com/klhq/skillmux/commit/c6b7635f503a6f265984016cdce8b6de7487d692))
67
+ * **skr-install:** validate SKILL.md and run skr scan rules before install ([a8de983](https://github.com/klhq/skillmux/commit/a8de98312b03dad6f0f9589300e972f46e934237))
68
+ * **skr-install:** wire skr install into the CLI ([0de3223](https://github.com/klhq/skillmux/commit/0de3223fb926e67e06b3771df2d9ffbd8868663b))
69
+ * **skr-scan:** --fail-on severity-gated exit code ([3a6789f](https://github.com/klhq/skillmux/commit/3a6789fe74f44503ef0d1f374100728a2a290619))
70
+ * **skr-scan:** add advisory skill security scanner ([8be8e88](https://github.com/klhq/skillmux/commit/8be8e887cb1c0dc5dfeb3860e8130c9ed99e75f3))
71
+ * **skr-scan:** rule-evaluation module with 4 v1 categories ([f98121c](https://github.com/klhq/skillmux/commit/f98121cac3ea5bae7e471380e9e645af14a44344))
72
+ * **skr-scan:** text and JSON renderers ([3aed4a9](https://github.com/klhq/skillmux/commit/3aed4a9b5a8f2db2ceac5d9bb000ea979be0be2a))
73
+ * **skr-scan:** vault/path enumeration for scan targets ([1784e8e](https://github.com/klhq/skillmux/commit/1784e8ecd388fb8f3d48c9444c9cf95ff21879c5))
74
+ * **skr-scan:** wire skr scan into the CLI ([0f7a616](https://github.com/klhq/skillmux/commit/0f7a6160529e59eae2ebc07a0505590e3bd49a88))
75
+
76
+
77
+ ### Fixed
78
+
79
+ * **config:** require calibrated reranker thresholds ([42f1c8d](https://github.com/klhq/skillmux/commit/42f1c8d4945a53218a69321ebf7fa39139e42bb4))
80
+ * **contract:** distinguish exact retrieval ([09cb15c](https://github.com/klhq/skillmux/commit/09cb15c167689e3278454f5b29275fd5c721374e))
81
+ * **doctor:** vault check always reported failure regardless of state ([fd434bb](https://github.com/klhq/skillmux/commit/fd434bb7d579453632a4a017c506b0c3433f23f9))
82
+ * **health:** report absent reranker accurately ([26fcd28](https://github.com/klhq/skillmux/commit/26fcd281dbcfa2de12e1cec0aee39f3fa76f8f7f))
83
+ * **index:** invalidate vectors by model fingerprint ([4b69765](https://github.com/klhq/skillmux/commit/4b6976540339a662398f279e9b98bb0c0595d4db))
84
+ * **release:** align AMD64 and ARM64 naming ([5d3d56a](https://github.com/klhq/skillmux/commit/5d3d56a5dd51e7690b59272c04cd35f7e8e3054c))
85
+ * **release:** publish after all artifacts succeed ([7e737cb](https://github.com/klhq/skillmux/commit/7e737cb2d6185c675b1d0a53429ed3d1053dadf0))
86
+ * **release:** support private repository publishing ([#11](https://github.com/klhq/skillmux/issues/11)) ([a7ee045](https://github.com/klhq/skillmux/commit/a7ee0450d3d2401313adfd4f17ca9cd8d95763c2))
87
+ * **router-core:** add on-demand vault index sync ([8316bd4](https://github.com/klhq/skillmux/commit/8316bd44f10cb78b5cc4592ee3c44cd869b9ad0d))
88
+ * **router-core:** broaden ONNX device and dtype typing ([d1ff775](https://github.com/klhq/skillmux/commit/d1ff7754f38aaa6793f46e5d415d2b9a983306e4))
89
+ * **router-core:** make optional server config safe ([f113076](https://github.com/klhq/skillmux/commit/f11307650b506f5a1c60a65ec40300bda4014c5f))
90
+ * **router-core:** short-circuit exact skill matches ([ba84004](https://github.com/klhq/skillmux/commit/ba84004169c1153af52f2717b2287bc28cedded5))
91
+ * **server:** secure HTTP defaults — loopback bind, deny CORS, no XFF trust ([#18](https://github.com/klhq/skillmux/issues/18)) ([ecc5ecf](https://github.com/klhq/skillmux/commit/ecc5ecf227c50fcdd14a3871f9d9990e24f7d9d1))
92
+ * **server:** timing-safe HTTP auth token comparison ([#17](https://github.com/klhq/skillmux/issues/17)) ([ea601c8](https://github.com/klhq/skillmux/commit/ea601c89f1b209818f4b4f7a447eaa94bdf57923))
93
+ * **skillmux-rename:** regenerate lockfile, fix stale skr-era comments ([d059abc](https://github.com/klhq/skillmux/commit/d059abc8aca87b8e9e98deb85ffb775c4de010ae))
94
+ * **skr-cli:** copy rate-limit headers onto /stats responses for consistency ([b42d0c0](https://github.com/klhq/skillmux/commit/b42d0c03d3adfbf82581c9731992509b86820de4))
95
+ * **skr-cli:** guard resolveProjectPinDir against escaping the repo ([5a78828](https://github.com/klhq/skillmux/commit/5a78828f7308ccdc11497ffee26645abf2725880))
96
+ * **skr-cli:** satisfy noUncheckedIndexedAccess in init.test.ts ([97c578a](https://github.com/klhq/skillmux/commit/97c578aa47d9de2d5b2d76c9ef1cd89a706a28ba))
97
+ * **skr-cli:** usage messages and docs name the skr binary, not skill-router ([5d6490a](https://github.com/klhq/skillmux/commit/5d6490a8e28fd5b14ced4402249f789256c8e8a7))
98
+ * **skr-install:** exclude .git from the copy into the vault ([1c5be6c](https://github.com/klhq/skillmux/commit/1c5be6c6b8922bfcb32afbde2b9ae3432a65a1a1))
99
+ * **skr-scan:** flag unparseable SKILL.md instead of silently dropping it ([b55e2a7](https://github.com/klhq/skillmux/commit/b55e2a7e28cce1e4ecc43ef680bec1ef1b9e8335))
100
+ * **test:** isolate server lifecycle configuration ([257bb41](https://github.com/klhq/skillmux/commit/257bb4109a4fbb7d5f7a99e1449a758df4111977))
101
+
102
+
103
+ ### Changed
104
+
105
+ * **cli:** validate serve options ([b344453](https://github.com/klhq/skillmux/commit/b34445372d51c053282755240301d5dc4a071853))
106
+ * **config:** validate normalized settings ([124d54b](https://github.com/klhq/skillmux/commit/124d54bba59e0a171f817c001eaf527cfb7dd76e))
107
+ * **core:** harden typed boundaries ([33be42f](https://github.com/klhq/skillmux/commit/33be42f18076cbf6b1ea4aa5097e448278c701ee))
108
+ * **core:** remove stale contract comments ([66f1025](https://github.com/klhq/skillmux/commit/66f1025a114ca35ff6559c89ba3a45fd192d8308))
109
+ * **eval:** validate query fixtures ([e57ce43](https://github.com/klhq/skillmux/commit/e57ce43eab7ec36906fe8c3d3b3bb34816a3070b))
110
+ * **http:** type rate limiter server boundary ([a710f8b](https://github.com/klhq/skillmux/commit/a710f8bc262dd734b234f80a4a36721759ab33b7))
111
+ * **models:** type embedding pipeline output ([c394e08](https://github.com/klhq/skillmux/commit/c394e08c85b5bb26aa97572f08c88f2c7b654a1b))
112
+
113
+ ## [Unreleased]
114
+
115
+ ## [0.1.1] - 2026-07-17
116
+
117
+ ### Added
118
+ - GitHub Actions CI for tests, type checking, binary builds, schema validation, and slim container builds.
119
+ - Tag-driven GitHub releases with consistently named Linux AMD64/ARM64 binaries, checksums, multi-architecture GHCR images, SBOMs, and build provenance.
120
+ - Weekly Dependabot updates for Bun/npm dependencies and GitHub Actions.
121
+ - Separate liveness and readiness endpoints, readiness metrics, startup initialization, and graceful shutdown.
122
+ - HTTP rate limiting (token-bucket, per-token/IP, `429` + `Retry-After`/`X-RateLimit-*` headers) and request metrics.
123
+ - Model configuration overrides (`EMBED_MODEL`, `EMBED_DIMENSION`, `RERANK_MODEL`), a dynamic model downloader, and `/health` + `/metrics` (Prometheus) HTTP endpoints.
124
+ - On-demand vault index sync so a running server folds vault changes into the index without a restart.
125
+ - Exact skill-match short-circuiting in the recall path.
126
+ - Docker packaging: `slim` (model-free) and `latest` (battery-included ONNX models) image variants.
127
+ - Streamable HTTP transport alongside the original stdio transport.
128
+ - HTTP auth and CORS controls; device/dtype configuration for local ONNX inference.
129
+
130
+ ### Fixed
131
+ - Skip unsupported GitHub artifact attestations while the repository is private and allow full/slim image builds to finish independently.
132
+ - Consolidated tag publishing into one GHCR release workflow, replacing the legacy Docker Hub-only workflow.
133
+ - Optional server config handling made safe for partially-specified `config.toml` files.
134
+ - ONNX device/dtype typing broadened to match `@huggingface/transformers`' accepted values.
135
+
136
+ ## [0.1.0] - 2026-07-14
137
+
138
+ ### Added
139
+ - Initial `router-core`: hybrid recall (SQLite FTS5/BM25 ∪ embedding cosine) with cross-encoder reranking, exposed via two MCP tools — `resolve_skill` and `fetch_skill`.
140
+ - Zero-loss delivery: `sha256(body)` verified against the file on disk at delivery time.
141
+ - Graceful fallback to lexical retrieval when embedding is unavailable.
142
+ - `skillmux eval` CLI command for recall@5 threshold calibration against a vault's holdout queries.
143
+ - Read-only vault guarantee and a SQLite-backed audit log of every `resolve_skill` call.
144
+
145
+ [Unreleased]: https://github.com/klhq/skillmux/compare/v0.1.1...HEAD
146
+ [0.1.1]: https://github.com/klhq/skillmux/compare/v0.1.0...v0.1.1
147
+ [0.1.0]: https://github.com/klhq/skillmux/releases/tag/v0.1.0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Lance Hsu
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,410 @@
1
+ # skillmux
2
+
3
+ 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.
4
+
5
+ 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.
6
+
7
+ - [How it works](#how-it-works)
8
+ - [Tiers: routed vs. pinned](#tiers-routed-vs-pinned)
9
+ - [Install](#install)
10
+ - [Quick start](#quick-start) — the fastest path to seeing it respond
11
+ - [Pinning skills across surfaces](#pinning-skills-across-surfaces) — optional: statically load a curated set across multiple agents
12
+ - [Docker Usage](#docker-usage)
13
+ - [Configuration](#configuration) — inference modes, security scanning, installing skills, env vars
14
+ - [Guarantees](#guarantees)
15
+ - [Development](#development)
16
+
17
+ ## How it works
18
+
19
+ ```
20
+ resolve_skill("convert this spreadsheet to markdown")
21
+
22
+
23
+ hybrid recall: SQLite FTS5 (BM25) ∪ embedding cosine (brute-force)
24
+
25
+
26
+ reciprocal-rank fusion → shortlist
27
+ optional reranker → matched | ambiguous | no_match
28
+ ```
29
+
30
+ - **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.
31
+ - **ambiguous** — up to 10 candidates (id, title, description). The calling LLM picks and calls `fetch_skill`.
32
+ - **no_match** — proceed under your normal workflow; don't load an unrelated skill.
33
+
34
+ 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.
35
+
36
+ ### Tools
37
+
38
+ | Tool | Input | Returns |
39
+ |------|-------|---------|
40
+ | `resolve_skill` | `query` | outcome + metadata in `structuredContent`; on match the verbatim body as text content (exactly once on the wire) |
41
+ | `fetch_skill` | `skill_id` | verbatim body, `content_sha256`, supporting-file paths |
42
+
43
+ The full contract lives in [`docs/schema.json`](docs/schema.json) (JSON Schema 2020-12, language-neutral).
44
+
45
+ ## Tiers: routed vs. pinned
46
+
47
+ Skills live in one vault, but there are two ways an agent gets one:
48
+
49
+ - **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.
50
+ - **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.
51
+
52
+ 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).
53
+
54
+ ## Install
55
+
56
+ ### Linux Binary
57
+
58
+ Download the latest release for your architecture:
59
+
60
+ ```sh
61
+ # AMD64
62
+ gh release download --repo klhq/skillmux \
63
+ --pattern 'skillmux-linux-*' \
64
+ --pattern 'SHA256SUMS'
65
+
66
+ sha256sum --check SHA256SUMS
67
+ # Install the binary matching your machine (amd64 or arm64)
68
+ chmod +x skillmux-linux-amd64
69
+ sudo install skillmux-linux-amd64 /usr/local/bin/skillmux
70
+ skillmux config show
71
+ ```
72
+
73
+ Release assets are also available at <https://github.com/klhq/skillmux/releases/latest>.
74
+
75
+ Requirements at runtime:
76
+
77
+ - A skill vault: one directory per skill with a `SKILL.md` in [agentskills.io](https://agentskills.io) format. Default: `~/skills`.
78
+ - Optional remote OpenAI-compatible embeddings and Infinity-native reranking. The full binary uses local GTE-small embeddings by default.
79
+
80
+ ## Quick start
81
+
82
+ 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.
83
+
84
+ ### 1. Put a skill in your vault
85
+
86
+ A skill is just a directory with a `SKILL.md`. Author one by hand to try against:
87
+
88
+ ```sh
89
+ mkdir -p ~/skills/csv-formatter
90
+ cat > ~/skills/csv-formatter/SKILL.md <<'EOF'
91
+ ---
92
+ name: CSV Formatter
93
+ 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.
94
+ ---
95
+
96
+ # CSV Formatter
97
+
98
+ Given raw CSV input, emit a well-aligned Markdown table: infer column headers
99
+ from the first row, right-align numeric columns, left-align text columns.
100
+ EOF
101
+ ```
102
+
103
+ (Or fetch an existing skill from a git repo instead — see [Installing skills](#installing-skills) below.)
104
+
105
+ ### 2. Index and verify
106
+
107
+ ```sh
108
+ skillmux index
109
+ skillmux doctor
110
+ ```
111
+
112
+ `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.
113
+
114
+ ### 3. Serve it
115
+
116
+ ```sh
117
+ skillmux serve
118
+ ```
119
+
120
+ Register with your MCP client directly, e.g.:
121
+
122
+ ```json
123
+ {
124
+ "mcpServers": {
125
+ "skillmux": {
126
+ "command": "skillmux",
127
+ "args": ["serve"]
128
+ }
129
+ }
130
+ }
131
+ ```
132
+
133
+ ### Try it without an MCP client
134
+
135
+ 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:
136
+
137
+ ```sh
138
+ # 1. Initialize a session, capture the session id from the response header
139
+ SESSION=$(curl -sS -D - -o /dev/null http://127.0.0.1:3000/mcp \
140
+ -H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" \
141
+ -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"try-it","version":"1.0.0"}}}' \
142
+ | grep -i '^mcp-session-id:' | tr -d '\r' | cut -d' ' -f2)
143
+
144
+ # 2. Complete the handshake
145
+ curl -sS -o /dev/null -X POST http://127.0.0.1:3000/mcp \
146
+ -H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" \
147
+ -H "mcp-session-id: $SESSION" \
148
+ -d '{"jsonrpc":"2.0","method":"notifications/initialized"}'
149
+
150
+ # 3. Call resolve_skill
151
+ curl -sS -X POST http://127.0.0.1:3000/mcp \
152
+ -H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" \
153
+ -H "mcp-session-id: $SESSION" \
154
+ -d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"resolve_skill","arguments":{"query":"convert this spreadsheet to markdown"}}}'
155
+ ```
156
+
157
+ Against the `csv-formatter` skill authored above, that returns a real match — trimmed here for length:
158
+
159
+ ```json
160
+ {"result":{"structuredContent":{"outcome":"ambiguous","retrieval":"hybrid","candidates":[
161
+ {"skill_id":"csv-formatter","title":"CSV Formatter","description":"Converts CSV or spreadsheet data..."}
162
+ ]}}}
163
+ ```
164
+
165
+ `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.
166
+
167
+ ### Run from source instead
168
+
169
+ ```sh
170
+ bun install --frozen-lockfile
171
+ bun run src/cli.ts index
172
+ bun run src/cli.ts serve
173
+ ```
174
+
175
+ ## Pinning skills across surfaces
176
+
177
+ 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).
178
+
179
+ ### 1. Discover surfaces and adopt one
180
+
181
+ ```sh
182
+ skillmux init
183
+ ```
184
+
185
+ Lists candidate surfaces (default: `~/.claude/skills`, `~/.agents/skills`) with their dir/symlink status and skill count. Nothing is written until you pass `--target`:
186
+
187
+ ```sh
188
+ skillmux init --target claude --yes
189
+ ```
190
+
191
+ This writes `skillmux.toml` at the vault root and marks `~/.claude/skills` as skillmux-owned — a `.skillmux` marker file that `sync` requires before it will touch the directory.
192
+
193
+ ### 2. Pick which skills are pinned
194
+
195
+ `init` always starts `[core]` empty — there's no heuristic yet for which skills belong there. Edit `skillmux.toml` by hand:
196
+
197
+ ```toml
198
+ [core]
199
+ skills = ["csv-formatter"]
200
+
201
+ [targets.claude]
202
+ dir = "/Users/you/.claude/skills"
203
+ project = false
204
+ ```
205
+
206
+ Cap: 25 skills in `[core]`. Full manifest schema, including `[project.<group>]` pins scoped to one repo, is in [`docs/configuration.md`](docs/configuration.md#tiers-and-the-manifest).
207
+
208
+ ### 3. Materialize
209
+
210
+ ```sh
211
+ skillmux sync
212
+ # claude: +1 -0
213
+ ```
214
+
215
+ 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.
216
+
217
+ ```sh
218
+ skillmux sync --dry-run # preview +added/-removed without touching disk
219
+ skillmux sync --install-hook # add a git post-merge hook in the vault that runs `skillmux sync` automatically
220
+ skillmux sync --restore-monolith # undo: replace a target dir with one symlink straight to the vault
221
+ ```
222
+
223
+ `--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.
224
+
225
+ ### 4. See what's actually getting used
226
+
227
+ `skillmux report` reads the same audit log `resolve_skill` writes to (see [Guarantees](#guarantees)) — useful for deciding what belongs in `[core]` versus staying routed:
228
+
229
+ ```sh
230
+ skillmux report --since 7d # local: reads state_dir's audit db
231
+ skillmux report --server http://host:3000 --since 7d # remote: hits a running server's /stats
232
+ ```
233
+
234
+ ```
235
+ window: 2026-07-14T00:00:00Z .. 2026-07-21T00:00:00Z
236
+ outcomes: matched=0 ambiguous=2 no_match=0 (ambiguous_rate=1.000)
237
+ skills:
238
+ csv-formatter matched=0 candidate=2
239
+ pdf-extractor matched=0 candidate=2
240
+ top no_match queries:
241
+ (none)
242
+ ```
243
+
244
+ `--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.
245
+
246
+ ## Docker Usage
247
+
248
+ The `skillmux` is packaged and distributed as a Docker image in two variants:
249
+
250
+ 1. **`skillmux:latest`**: Bundles the small quantized GTE embedding model for local hybrid retrieval.
251
+ 2. **`skillmux:latest-slim`**: Excludes model weights and supports configured remote embeddings or lexical fallback.
252
+
253
+ 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/lazyskyline/skillmux`](https://hub.docker.com/r/lazyskyline/skillmux) — either registry works, examples below use GHCR.
254
+
255
+ ### Running HTTP Server (Docker Default)
256
+
257
+ To run as an HTTP MCP service (default in Docker):
258
+
259
+ ```sh
260
+ # Battery-included (runs local in-process ONNX models)
261
+ docker run -d \
262
+ --name skillmux \
263
+ -v ~/skills:/vault:ro \
264
+ -v skillmux-data:/data \
265
+ -p 3000:3000 \
266
+ ghcr.io/klhq/skillmux:latest
267
+
268
+ # Slim (configured remote embeddings, or lexical fallback)
269
+ docker run -d \
270
+ --name skillmux-slim \
271
+ -v ~/skills:/vault:ro \
272
+ -v skillmux-data:/data \
273
+ -p 3000:3000 \
274
+ -e EMBED_BASE_URL="http://embeddings-host:8080" \
275
+ ghcr.io/klhq/skillmux:latest-slim
276
+ ```
277
+
278
+ Connect your MCP client to the HTTP endpoint (e.g. standard Streamable HTTP transport):
279
+ - POST messages to `http://localhost:3000/mcp`
280
+
281
+ #### HTTP server: auth, CORS, rate limiting
282
+
283
+ All of the below is `[server]` config in `config.toml`, overridable by environment variable — see [Environment Variable Overrides](#environment-variable-overrides).
284
+
285
+ - **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.
286
+ - **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`.
287
+ - **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.
288
+ - **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`.
289
+ - **`GET /health/live`** — lightweight liveness check. Legacy `GET /health` remains an alias.
290
+ - **`GET /health/ready`** — readiness with active retrieval capability, skill count, index state, and inference status.
291
+ - **`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`.
292
+
293
+ ### Running Stdio Server in Docker
294
+
295
+ If your agent runs locally and expects a piped stdio process:
296
+
297
+ ```sh
298
+ docker run -i --rm \
299
+ -v ~/skills:/vault:ro \
300
+ ghcr.io/klhq/skillmux:latest serve --transport stdio
301
+ ```
302
+
303
+ ## Configuration
304
+
305
+ 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.
306
+
307
+ ### Inference Modes
308
+
309
+ - The zero-config default combines SQLite FTS5 with the small `Xenova/gte-small` embedding model and returns an ordered shortlist.
310
+ - Configured OpenAI-compatible embeddings replace the local embedder. An optional Infinity-compatible reranker enables confident automatic matches.
311
+
312
+ Run `skillmux doctor` to verify routing capability. Run `skillmux config show` to inspect effective configuration; it prints credential variable names, never values.
313
+
314
+ ### Security scanning
315
+
316
+ `skillmux scan [<path>]` inspects skill content for prompt-injection and data-exfiltration risk indicators
317
+ before it's served over MCP or HTTP. It's offline (no network call, no inference config needed),
318
+ read-only, and advisory-only — it never blocks `skillmux index`/`sync`/`init`, which don't call it
319
+ automatically.
320
+
321
+ ```sh
322
+ skillmux scan # scan the configured vault_path
323
+ skillmux scan ~/skills/some-skill # scan a single candidate skill dir before adding it
324
+ skillmux scan --format json # machine-readable { scanned, findings } for CI
325
+ skillmux scan --fail-on high # exit 1 if any finding is high severity (for CI gating)
326
+ ```
327
+
328
+ The v1 rule set covers four categories, each attached to the finding as `rule_id` with a fixed
329
+ `severity`:
330
+
331
+ | `rule_id` | `severity` | Flags |
332
+ |---|---|---|
333
+ | `prompt-injection-phrase` | `high` | Known instruction-override phrases (e.g. "ignore previous instructions") |
334
+ | `invisible-unicode` | `high` | Zero-width/invisible Unicode code points, including hidden tag-character payloads |
335
+ | `secret-pattern` | `high` | Hardcoded-credential-shaped strings (AWS-style keys, PEM blocks, `api_key=`/`token=` assignments) |
336
+ | `suspicious-url` | `medium` | Bare-IP-address URLs, or URLs paired with exfiltration-suggesting text |
337
+
338
+ `skillmux scan` is unrelated to the `audit` SQLite table / `skillmux report` — that's query telemetry (what got
339
+ routed where); `skillmux scan` is content security (what's in the vault).
340
+
341
+ ### Installing skills
342
+
343
+ `skillmux install <repo>[/path]` fetches one skill from a git repo (GitHub shorthand, full HTTPS/SSH URL,
344
+ or `file://`) into the configured `vault_path`, so onboarding doesn't require a second CLI just to
345
+ pull a skill in. It's a convenience fetch, not a distribution system:
346
+
347
+ ```sh
348
+ skillmux install owner/repo # repo root must itself be a skill
349
+ skillmux install owner/repo/path/to/skill # select one skill out of a multi-skill repo
350
+ skillmux install owner/repo --dry-run # preview id, target path, and scan findings
351
+ skillmux install owner/repo --force # overwrite an existing skill_id
352
+ skillmux install owner/repo --fail-on high # abort the install if scan findings meet the threshold
353
+ ```
354
+
355
+ The fetched skill is validated the same way `skillmux scan` validates the vault — malformed `SKILL.md`
356
+ aborts the install, and scan findings are printed before anything is written (advisory by default;
357
+ `--fail-on` opts into blocking, matching `skillmux scan`'s severity levels). Materialization is a plain
358
+ file copy, not a symlink — the temporary clone is deleted once the install completes.
359
+
360
+ `skillmux install` intentionally does **not** handle updates, uninstalls, version pinning, or
361
+ core/project/routed tier assignment (that's `skillmux sync`'s domain) — it only ever fetches one skill,
362
+ once. Use `skillmux sync` afterward if the installed skill needs to be pinned into a tier.
363
+
364
+ ### Environment Variable Overrides
365
+ All core settings can be overridden via environment variables (handy for Docker):
366
+ - `VAULT_PATH` — overrides `vault_path` (defaults to `/vault` inside Docker)
367
+ - `STATE_DIR` — overrides `state_dir` (defaults to `/data` inside Docker)
368
+ - `EMBED_BASE_URL` / `SKILLMUX_EMBED_BASE_URL` — overrides remote `inference.embedding.base_url`
369
+ - `EMBED_MODEL` / `SKILLMUX_EMBED_MODEL` — overrides `embedding.model`
370
+ - `EMBED_DIMENSION` / `SKILLMUX_EMBED_DIMENSION` — overrides `embedding.dimension`
371
+ - `EMBED_DEVICE` / `EMBED_DTYPE` — overrides local `inference.embedding.device` / `inference.embedding.dtype`
372
+ - `RERANK_BASE_URL` / `SKILLMUX_RERANK_BASE_URL` — overrides remote `inference.reranker.base_url`
373
+ - `RERANK_MODEL` / `SKILLMUX_RERANK_MODEL` — overrides `rerank.model`
374
+ - `SKILLMUX_CONFIG` — path to custom `config.toml` (default `~/.config/skillmux/config.toml`)
375
+ - `SKILLMUX_MODELS_DIR` — path to directory storing downloaded local models (default `~/.cache/skillmux/models`, `/models` inside Docker)
376
+ - `PORT` — HTTP listen port (default `3000`, HTTP transport only)
377
+ - `HTTP_HOSTNAME` — overrides `server.hostname` (default `127.0.0.1`, `0.0.0.0` inside Docker)
378
+ - `HTTP_AUTH_ENABLED` — overrides `server.auth_enabled` (`"true"` to enable)
379
+ - `HTTP_AUTH_TOKEN_ENV` — overrides `server.auth_token_env`
380
+ - `HTTP_ALLOWED_ORIGINS` — comma-separated list, overrides `server.allowed_origins`
381
+ - `HTTP_RATE_LIMIT_ENABLED` / `SKILLMUX_HTTP_RATE_LIMIT_ENABLED` — overrides `server.rate_limit.enabled` (`"true"` to enable)
382
+ - `HTTP_RATE_LIMIT_RPM` / `SKILLMUX_HTTP_RATE_LIMIT_RPM` — overrides `server.rate_limit.requests_per_minute`
383
+ - `HTTP_RATE_LIMIT_TRUST_PROXY` / `SKILLMUX_HTTP_RATE_LIMIT_TRUST_PROXY` — overrides `server.rate_limit.trust_proxy` (`"true"` to trust `X-Forwarded-For`)
384
+
385
+ Remote API keys are read from the environment variables named by `inference.embedding.api_key_env` and `inference.reranker.api_key_env`; no secret ever lives in the config file.
386
+
387
+ Evaluate lexical and local hybrid retrieval against the checked-in labeled queries:
388
+
389
+ ```sh
390
+ bun run src/cli.ts eval
391
+ # holdout queries: 8
392
+ # lexical recall@5: 1.000
393
+ # hybrid recall@5: 1.000
394
+ ```
395
+
396
+ ## Guarantees
397
+
398
+ - **Read-only vault** — no code path writes under the vault; all state is confined to `state_dir`. Covered by tests.
399
+ - **Zero-loss delivery** — delivered bytes always hash-match the file on disk at delivery time.
400
+ - **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.
401
+ - **Audit log** — every `resolve_skill` call is appended to a SQLite table in `state_dir` (timestamp, query, outcome, candidates with scores, latency).
402
+
403
+ ## Development
404
+
405
+ ```sh
406
+ bun test # full suite (contract, hybrid recall, stdio e2e, watcher, eval)
407
+ bun run build # single-file binary via bun build --compile
408
+ ```
409
+
410
+ Reference: [`docs/configuration.md`](docs/configuration.md) and [`docs/schema.json`](docs/schema.json).
@@ -0,0 +1,5 @@
1
+ # This entire file is optional. Skill Router indexes an externally managed
2
+ # agentskills.io-compatible vault and uses local GTE-small semantic retrieval.
3
+ # vault_path is a neutral path, never a harness's static-loading scan
4
+ # surface (e.g. ~/.agents/skills) — see docs/sdd/skr-cli/think.md.
5
+ vault_path = "~/skills"
@@ -0,0 +1,25 @@
1
+ # Connect stronger inference endpoints. The reranker section is optional.
2
+ vault_path = "~/.agents/skills"
3
+
4
+ [inference]
5
+ mode = "remote"
6
+ timeout_ms = 5000
7
+
8
+ [inference.embedding]
9
+ provider = "openai"
10
+ base_url = "https://embedding.example.com"
11
+ model = "your-embedding-model"
12
+ dimension = 1024
13
+ api_key_env = "EMBEDDING_API_KEY"
14
+
15
+ [inference.reranker]
16
+ provider = "infinity"
17
+ base_url = "https://reranker.example.com"
18
+ model = "your-reranker-model"
19
+ api_key_env = "RERANKER_API_KEY"
20
+
21
+ # Required when a reranker is configured. Calibrate these for your model.
22
+ [inference.thresholds]
23
+ match_score = 0.90
24
+ match_margin = 0.30
25
+ candidate_floor = 0.50